/**
  * Create a deep copy of the current packet.
  *
  * <p>This will perform a full copy of the entire object tree, only skipping known immutable
  * objects and primitive types.
  *
  * <p>Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
  *
  * @return A deep copy of the current packet.
  */
 public PacketContainer deepClone() {
   Object clonedPacket = DEEP_CLONER.clone(getHandle());
   return new PacketContainer(getID(), clonedPacket);
 }
/**
 * Represents a Minecraft packet indirectly.
 *
 * @author Kristian
 */
public class PacketContainer implements Serializable {

  /** Generated by Eclipse. */
  private static final long serialVersionUID = 2074805748222377230L;

  protected int id;
  protected transient Object handle;

  // Current structure modifier
  protected transient StructureModifier<Object> structureModifier;

  // Support for serialization
  private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap();
  private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap();

  // Used to clone packets
  private static final AggregateCloner DEEP_CLONER =
      AggregateCloner.newBuilder()
          .instanceProvider(DefaultInstances.DEFAULT)
          .andThen(BukkitCloner.class)
          .andThen(ImmutableDetector.class)
          .andThen(CollectionCloner.class)
          .andThen(getSpecializedDeepClonerFactory())
          .build();

  private static final AggregateCloner SHALLOW_CLONER =
      AggregateCloner.newBuilder()
          .instanceProvider(DefaultInstances.DEFAULT)
          .andThen(
              new Function<BuilderParameters, Cloner>() {
                @Override
                public Cloner apply(@Nullable BuilderParameters param) {
                  return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {
                    {
                      // Use a default writer with no concept of cloning
                      writer = new ObjectWriter();
                    }
                  };
                }
              })
          .build();

  /**
   * Creates a packet container for a new packet.
   *
   * @param id - ID of the packet to create.
   */
  public PacketContainer(int id) {
    this(id, StructureCache.newPacket(id));
  }

  /**
   * Creates a packet container for an existing packet.
   *
   * @param id - ID of the given packet.
   * @param handle - contained packet.
   */
  public PacketContainer(int id, Object handle) {
    this(id, handle, StructureCache.getStructure(id).withTarget(handle));
  }

  /**
   * Creates a packet container for an existing packet.
   *
   * @param id - ID of the given packet.
   * @param handle - contained packet.
   * @param structure - structure modifier.
   */
  public PacketContainer(int id, Object handle, StructureModifier<Object> structure) {
    if (handle == null) throw new IllegalArgumentException("handle cannot be null.");

    this.id = id;
    this.handle = handle;
    this.structureModifier = structure;
  }

  /** For serialization. */
  protected PacketContainer() {}

  /**
   * Retrieves the underlying Minecraft packet.
   *
   * @return Underlying Minecraft packet.
   */
  public Object getHandle() {
    return handle;
  }

  /**
   * Retrieves the generic structure modifier for this packet.
   *
   * @return Structure modifier.
   */
  public StructureModifier<Object> getModifier() {
    return structureModifier;
  }

  /**
   * Retrieves a read/write structure for every field with the given type.
   *
   * @param primitiveType - the type to find.
   * @return A modifier for this specific type.
   */
  public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
    return structureModifier.withType(primitiveType);
  }

  /**
   * Retrieves a read/write structure for every byte field.
   *
   * @return A modifier for every byte field.
   */
  public StructureModifier<Byte> getBytes() {
    return structureModifier.withType(byte.class);
  }

  /**
   * Retrieves a read/write structure for every short field.
   *
   * @return A modifier for every short field.
   */
  public StructureModifier<Short> getShorts() {
    return structureModifier.withType(short.class);
  }

  /**
   * Retrieves a read/write structure for every integer field.
   *
   * @return A modifier for every integer field.
   */
  public StructureModifier<Integer> getIntegers() {
    return structureModifier.withType(int.class);
  }
  /**
   * Retrieves a read/write structure for every long field.
   *
   * @return A modifier for every long field.
   */
  public StructureModifier<Long> getLongs() {
    return structureModifier.withType(long.class);
  }

  /**
   * Retrieves a read/write structure for every float field.
   *
   * @return A modifier for every float field.
   */
  public StructureModifier<Float> getFloat() {
    return structureModifier.withType(float.class);
  }

  /**
   * Retrieves a read/write structure for every double field.
   *
   * @return A modifier for every double field.
   */
  public StructureModifier<Double> getDoubles() {
    return structureModifier.withType(double.class);
  }

  /**
   * Retrieves a read/write structure for every String field.
   *
   * @return A modifier for every String field.
   */
  public StructureModifier<String> getStrings() {
    return structureModifier.withType(String.class);
  }

  /**
   * Retrieves a read/write structure for every String array field.
   *
   * @return A modifier for every String array field.
   */
  public StructureModifier<String[]> getStringArrays() {
    return structureModifier.withType(String[].class);
  }

  /**
   * Retrieves a read/write structure for every byte array field.
   *
   * @return A modifier for every byte array field.
   */
  public StructureModifier<byte[]> getByteArrays() {
    return structureModifier.withType(byte[].class);
  }

  /**
   * Retrieve a serializer for reading and writing ItemStacks stored in a byte array.
   *
   * @return A instance of the serializer.
   */
  public StreamSerializer getByteArraySerializer() {
    return new StreamSerializer();
  }

  /**
   * Retrieves a read/write structure for every int array field.
   *
   * @return A modifier for every int array field.
   */
  public StructureModifier<int[]> getIntegerArrays() {
    return structureModifier.withType(int[].class);
  }

  /**
   * Retrieves a read/write structure for ItemStack.
   *
   * <p>This modifier will automatically marshall between the Bukkit ItemStack and the internal
   * Minecraft ItemStack.
   *
   * @return A modifier for ItemStack fields.
   */
  public StructureModifier<ItemStack> getItemModifier() {
    // Convert to and from the Bukkit wrapper
    return structureModifier.<ItemStack>withType(
        MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter());
  }

  /**
   * Retrieves a read/write structure for arrays of ItemStacks.
   *
   * <p>This modifier will automatically marshall between the Bukkit ItemStack and the internal
   * Minecraft ItemStack.
   *
   * @return A modifier for ItemStack array fields.
   */
  public StructureModifier<ItemStack[]> getItemArrayModifier() {

    final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();

    // Convert to and from the Bukkit wrapper
    return structureModifier.<ItemStack[]>withType(
        MinecraftReflection.getItemStackArrayClass(),
        BukkitConverters.getIgnoreNull(
            new EquivalentConverter<ItemStack[]>() {

              public Object getGeneric(Class<?> genericType, ItemStack[] specific) {
                Class<?> nmsStack = MinecraftReflection.getItemStackClass();
                Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);

                // Unwrap every item
                for (int i = 0; i < result.length; i++) {
                  result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
                }
                return result;
              }

              @Override
              public ItemStack[] getSpecific(Object generic) {
                Object[] input = (Object[]) generic;
                ItemStack[] result = new ItemStack[input.length];

                // Add the wrapper
                for (int i = 0; i < result.length; i++) {
                  result[i] = stackConverter.getSpecific(input[i]);
                }
                return result;
              }

              @Override
              public Class<ItemStack[]> getSpecificType() {
                return ItemStack[].class;
              }
            }));
  }

  /**
   * Retrieves a read/write structure for the world type enum.
   *
   * <p>This modifier will automatically marshall between the Bukkit world type and the internal
   * Minecraft world type.
   *
   * @return A modifier for world type fields.
   */
  public StructureModifier<WorldType> getWorldTypeModifier() {
    // Convert to and from the Bukkit wrapper
    return structureModifier.<WorldType>withType(
        MinecraftReflection.getWorldTypeClass(), BukkitConverters.getWorldTypeConverter());
  }

  /**
   * Retrieves a read/write structure for data watchers.
   *
   * @return A modifier for data watchers.
   */
  public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
    // Convert to and from the Bukkit wrapper
    return structureModifier.<WrappedDataWatcher>withType(
        MinecraftReflection.getDataWatcherClass(), BukkitConverters.getDataWatcherConverter());
  }

  /**
   * Retrieves a read/write structure for entity objects.
   *
   * <p>Note that entities are transmitted by integer ID, and the type may not be enough to
   * distinguish between entities and other values. Thus, this structure modifier MAY return null or
   * invalid entities for certain fields. Using the correct index is essential.
   *
   * @return A modifier entity types.
   */
  public StructureModifier<Entity> getEntityModifier(World world) {
    // Convert to and from the Bukkit wrapper
    return structureModifier.<Entity>withType(
        int.class, BukkitConverters.getEntityConverter(world));
  }

  /**
   * Retrieves a read/write structure for chunk positions.
   *
   * @return A modifier for a ChunkPosition.
   */
  public StructureModifier<ChunkPosition> getPositionModifier() {
    // Convert to and from the Bukkit wrapper
    return structureModifier.withType(
        MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter());
  }

  /**
   * Retrieves a read/write structure for NBT classes.
   *
   * @return A modifier for NBT classes.
   */
  public StructureModifier<NbtBase<?>> getNbtModifier() {
    // Allow access to the NBT class in packet 130
    return structureModifier.withType(
        MinecraftReflection.getNBTBaseClass(), BukkitConverters.getNbtConverter());
  }

  /**
   * Retrieves a read/write structure for collections of chunk positions.
   *
   * <p>This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and
   * the internal Minecraft ChunkPosition.
   *
   * @return A modifier for ChunkPosition list fields.
   */
  public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
    // Convert to and from the ProtocolLib wrapper
    return structureModifier.withType(
        Collection.class,
        BukkitConverters.getListConverter(
            MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter()));
  }

  /**
   * Retrieves a read/write structure for collections of watchable objects.
   *
   * <p>This modifier will automatically marshall between the visible WrappedWatchableObject and the
   * internal Minecraft WatchableObject.
   *
   * @return A modifier for watchable object list fields.
   */
  public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
    // Convert to and from the ProtocolLib wrapper
    return structureModifier.withType(
        Collection.class,
        BukkitConverters.getListConverter(
            MinecraftReflection.getWatchableObjectClass(),
            BukkitConverters.getWatchableObjectConverter()));
  }

  /**
   * Retrieves the ID of this packet.
   *
   * @return Packet ID.
   */
  public int getID() {
    return id;
  }

  /**
   * Create a shallow copy of the current packet.
   *
   * <p>This merely writes the content of each field to the new class directly, without performing
   * any expensive copies.
   *
   * @return A shallow copy of the current packet.
   */
  public PacketContainer shallowClone() {
    Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
    return new PacketContainer(getID(), clonedPacket);
  }

  /**
   * Create a deep copy of the current packet.
   *
   * <p>This will perform a full copy of the entire object tree, only skipping known immutable
   * objects and primitive types.
   *
   * <p>Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
   *
   * @return A deep copy of the current packet.
   */
  public PacketContainer deepClone() {
    Object clonedPacket = DEEP_CLONER.clone(getHandle());
    return new PacketContainer(getID(), clonedPacket);
  }

  // To save space, we'll skip copying the inflated buffers in packet 51 and 56
  private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
    // Look at what you've made me do Java, look at it!!
    return new Function<BuilderParameters, Cloner>() {
      @Override
      public Cloner apply(@Nullable BuilderParameters param) {
        return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {
          {
            this.writer =
                new ObjectWriter() {
                  protected void transformField(
                      StructureModifier<Object> modifierSource,
                      StructureModifier<Object> modifierDest,
                      int fieldIndex) {
                    // No need to clone inflated buffers
                    if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
                      modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
                    else
                      defaultTransform(
                          modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
                  };
                };
          }
        };
      }
    };
  }

  private void writeObject(ObjectOutputStream output) throws IOException {
    // Default serialization
    output.defaultWriteObject();

    // We'll take care of NULL packets as well
    output.writeBoolean(handle != null);

    try {
      // Call the write-method
      getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class)
          .invoke(handle, new DataOutputStream(output));

    } catch (IllegalArgumentException e) {
      throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Insufficient security privileges.", e);
    } catch (InvocationTargetException e) {
      throw new IOException("Could not serialize Minecraft packet.", e);
    }
  }

  private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
    // Default deserialization
    input.defaultReadObject();

    // Get structure modifier
    structureModifier = StructureCache.getStructure(id);

    // Don't read NULL packets
    if (input.readBoolean()) {

      // Create a default instance of the packet
      handle = StructureCache.newPacket(id);

      // Call the read method
      try {
        getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class)
            .invoke(handle, new DataInputStream(input));

      } catch (IllegalArgumentException e) {
        throw new IOException("Minecraft packet doesn't support DataInputStream", e);
      } catch (IllegalAccessException e) {
        throw new RuntimeException("Insufficient security privileges.", e);
      } catch (InvocationTargetException e) {
        throw new IOException("Could not deserialize Minecraft packet.", e);
      }

      // And we're done
      structureModifier = structureModifier.withTarget(handle);
    }
  }

  /**
   * Retrieve the cached method concurrently.
   *
   * @param lookup - a lazy lookup cache.
   * @param handleClass - class type of the current packet.
   * @param methodName - name of method to retrieve.
   * @param parameterClass - the one parameter type in the method.
   * @return Reflected method.
   */
  private Method getMethodLazily(
      ConcurrentMap<Class<?>, Method> lookup,
      Class<?> handleClass,
      String methodName,
      Class<?> parameterClass) {
    Method method = lookup.get(handleClass);

    // Atomic operation
    if (method == null) {
      Method initialized =
          FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass);
      method = lookup.putIfAbsent(handleClass, initialized);

      // Use our version if we succeeded
      if (method == null) {
        method = initialized;
      }
    }

    return method;
  }
}
 /**
  * Create a shallow copy of the current packet.
  *
  * <p>This merely writes the content of each field to the new class directly, without performing
  * any expensive copies.
  *
  * @return A shallow copy of the current packet.
  */
 public PacketContainer shallowClone() {
   Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
   return new PacketContainer(getID(), clonedPacket);
 }