package com.github.kaskadee.knack.nbt;

import com.github.kaskadee.knack.Helper;
import com.github.kaskadee.knack.Main;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;

public class NBTHandlerFallback extends NBTHandler {

  private final Object nmsStack;
  private final String version;
  private final Plugin plugin;
  private Object compound;

  /*
   * Creates a new instance of the fallback handler
   */
  NBTHandlerFallback(ItemStack stack) {
    this.plugin = Main.getInstance();
    // Get server version
    this.version = Helper.getServerVersion();
    // Get NMS copy of ItemStack
    nmsStack = this.getNmsCopy(stack);
  }

  /*
   * Returns an integer value from a nbt path
   */
  @Override
  public int getInt(String path) {
    // Get nbt compound
    if (compound == null) {
      compound = (hasNbtTag(nmsStack)) ? getNbtTag(nmsStack) : createNewTagCompound();
      if (compound == null) {
        return -1;
      }
    }
    try {
      // Get "getInt" method by reflection
      Method m = compound.getClass().getMethod("getInt", String.class);
      // Invoke and return result
      return (Integer) m.invoke(compound, path);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return -1;
    }
  }

  /*
   * Returns a boolean value from a nbt path
   */
  @Override
  public boolean getBoolean(String path) {
    // Get nbt compound
    if (compound == null) {
      compound = (hasNbtTag(nmsStack)) ? getNbtTag(nmsStack) : createNewTagCompound();
      if (compound == null) {
        return false;
      }
    }
    try {
      // Get "getBoolean" method by reflection
      Method m = compound.getClass().getMethod("getBoolean", String.class);
      // Invoke and return result
      return (Boolean) m.invoke(compound, path);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return false;
    }
  }

  /*
   * Sets a nbt integer value at the given path
   */
  @Override
  public void setInt(String path, int value) {
    // Get nbt compound
    if (compound == null) {
      compound = (hasNbtTag(nmsStack)) ? getNbtTag(nmsStack) : createNewTagCompound();
      if (compound == null) {
        return;
      }
    }
    try {
      // Get target method by reflection
      Method m = compound.getClass().getMethod("setInt", String.class, int.class);
      // Invoke method with values
      m.invoke(compound, path, value);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
    }
  }

  /*
   * Sets a nbt boolean value at the given path
   */
  @Override
  public void setBoolean(String path, boolean value) {
    // Get nbt compound
    if (compound == null) {
      compound = (hasNbtTag(nmsStack)) ? getNbtTag(nmsStack) : createNewTagCompound();
      if (compound == null) {
        return;
      }
    }

    try {
      // Get target method by reflection
      Method m = compound.getClass().getMethod("setBoolean", String.class, boolean.class);
      // Invoke method with values
      m.invoke(compound, path, value);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public void pickupItem(org.bukkit.entity.Item item, Player p) {
    try {
      Class c = Class
          .forName("net.minecraft.server." + Helper.getServerVersion() + ".PacketPlayOutCollect");
      Object packet = c.getConstructor(int.class, int.class, int.class)
          .newInstance(item.getEntityId(), p.getEntityId(), item.getItemStack().getAmount());
      Method m = p.getClass().getMethod("getHandle");
      Object playerHandle = m.invoke(p);
      Object connection = playerHandle.getClass().getField("playerconnection").get(playerHandle);
      Method sendPacket = connection.getClass().getMethod("sendPacket", Object.class);
      sendPacket.invoke(connection, packet);
    } catch (ClassNotFoundException | InstantiationException | NoSuchMethodException | NoSuchFieldException | InvocationTargetException | IllegalAccessException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
    }
    item.remove();
  }

  /*
   * Converts the NMS item stack back to a Bukkit item stack
   */
  @Override
  public ItemStack convertBack() {
    // If compound is null try to get compound
    if (this.compound == null && hasNbtTag(this.nmsStack)) {
      this.compound = getNbtTag(this.nmsStack);
    }
    // Set new nbt tag compound
    this.setNbtTag(this.nmsStack, this.compound);
    // Return bukkit copy of ItemStack
    return getBukkitCopy(this.nmsStack);
  }

  /*
   * Gets the CraftItemStack class by reflection
   */
  private Class getCraftItemStackClass() {
    try {
      // Get class by name
      return Class.forName("org.bukkit.craftbukkit." + version + ".inventory.CraftItemStack");
    } catch (ClassNotFoundException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return null;
    }
  }

  /*
   * Gets a NMS copy of a ItemStack by reflection (CraftItemStack.asNMSCopy(ItemStack))
   */
  @SuppressWarnings("unchecked")
  private Object getNmsCopy(ItemStack stack) {
    // Get CraftItemStack class
    Class cis = getCraftItemStackClass();
    try {
      if (cis != null) {
        // Get method by reflection
        Method method = cis.getMethod("asNMSCopy", ItemStack.class);
        // Invoke method and return result
        return method.invoke(cis, stack);
      }
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NullPointerException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
    }
    return null;
  }

  /*
   * Gets a Bukkit copy of a NMS ItemStack by reflection (CraftItemStack.asBukkitCopy(net.minecraft.server.v1_x_Rx.ItemStack))
   */
  @SuppressWarnings("unchecked")
  private ItemStack getBukkitCopy(Object nmsCopy) {
    Class cis = getCraftItemStackClass();
    try {
      if (cis != null) {
        Method method = cis.getMethod("asBukkitCopy", nmsCopy.getClass());
        return (ItemStack) method.invoke(cis, nmsCopy);
      }
      return null;
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NullPointerException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return null;
    }
  }

  /*
   * Returns a value indicating whether the current NMS item stack contains a NBTTagCompound
   */
  private boolean hasNbtTag(Object nmsCopy) {
    try {
      Method m = nmsCopy.getClass().getMethod("hasTag");
      return (Boolean) m.invoke(m);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return false;
    }
  }

  /*
   * Creates a new NBTTagCompound by reflection
   */
  private Object createNewTagCompound() {
    try {
      Class c = Class.forName("net.minecraft.server." + version + ".NBTTagCompound");
      return c.newInstance();
    } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return null;
    }
  }

  /*
   * Gets the NBTTagCompound from a NMS item stack
   */
  @SuppressWarnings("unchecked")
  private Object getNbtTag(Object nmsCopy) {
    try {
      Class c = nmsCopy.getClass();
      Method m = c.getMethod("getTag");
      return m.invoke(nmsCopy);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
      return null;
    }
  }

  /*
   * Sets the NBTTagCompound for a NMS item stack
   */
  private void setNbtTag(Object nmsCopy, Object compound) {
    try {
      Method m = nmsCopy.getClass().getMethod("setTag", compound.getClass());
      m.invoke(nmsCopy, compound);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
      plugin.getLogger().log(Level.SEVERE, "Fallback method failed!", e);
    }
  }
}
