package fi.dy.masa.enderutilities.item;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import fi.dy.masa.enderutilities.EnderUtilities;
import fi.dy.masa.enderutilities.event.PlayerItemPickupEvent;
import fi.dy.masa.enderutilities.inventory.container.ContainerNullifier;
import fi.dy.masa.enderutilities.inventory.item.InventoryItem;
import fi.dy.masa.enderutilities.item.base.IKeyBound;
import fi.dy.masa.enderutilities.item.base.ItemEnderUtilities;
import fi.dy.masa.enderutilities.reference.HotKeys;
import fi.dy.masa.enderutilities.reference.HotKeys.EnumKey;
import fi.dy.masa.enderutilities.reference.ReferenceGuiIds;
import fi.dy.masa.enderutilities.registry.EnderUtilitiesItems;
import fi.dy.masa.enderutilities.util.EntityUtils;
import fi.dy.masa.enderutilities.util.InventoryUtils;
import fi.dy.masa.enderutilities.util.nbt.NBTUtils;
import fi.dy.masa.enderutilities.util.nbt.UtilItemModular;

public class ItemNullifier extends ItemEnderUtilities implements IKeyBound
{
    public static final String TAG_NAME_CONTAINER = "Nullifier";
    public static final String TAG_NAME_DISABLED = "Disabled";
    public static final String TAG_NAME_SLOT_SELECTION = "Slot";
    public static final int GUI_ACTION_SELECT_SLOT      = 0;
    public static final int GUI_ACTION_TOGGLE_DISABLED  = 1;
    public static final int NUM_SLOTS = 9;
    public static final int MAX_STACK_SIZE = 1024;

    public ItemNullifier(String name)
    {
        super(name);

        this.setMaxStackSize(1);
        this.setMaxDamage(0);
    }

    @Override
    public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos,
            EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
    {
        ItemStack stack = player.getHeldItem(hand);
        ItemStack useStack = this.getItemForUse(stack, player);

        if (useStack.isEmpty() == false && world.isBlockModifiable(player, pos.offset(facing)))
        {
            EntityUtils.setHeldItemWithoutEquipSound(player, hand, useStack);
            EnumActionResult result = useStack.onItemUse(player, world, pos, hand, facing, hitX, hitY, hitZ);

            if (world.isRemote == false && player.capabilities.isCreativeMode == false && useStack.isEmpty() == false)
            {
                tryInsertItemsToNullifier(useStack, stack, player);
            }

            EntityUtils.setHeldItemWithoutEquipSound(player, hand, stack);

            return result;
        }

        return super.onItemUse(player, world, pos, hand, facing, hitX, hitY, hitZ);
    }

    public static boolean isNullifierEnabled(ItemStack stack)
    {
        return NBTUtils.getBoolean(stack, TAG_NAME_CONTAINER, TAG_NAME_DISABLED) == false;
    }

    private ItemStack getItemForUse(ItemStack stackNullifier, EntityPlayer player)
    {
        // Create the inventory here by pretending to be on the server side,
        // so that the readFromContainerItemStack() call actually reads the items on the client side too.
        // This is (mostly only?) required to get the block placing sounds to work on the client.
        // Ie. in other words, to get the correct item to use also on the client side.
        ItemHandlerNullifier inv = createInventoryForItem(stackNullifier, false);

        int slot = MathHelper.clamp(NBTUtils.getByte(stackNullifier, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION), 0, inv.getSlots() - 1);
        boolean simulate = player.getEntityWorld().isRemote || player.capabilities.isCreativeMode;
        return inv.extractItem(slot, 1, simulate);
    }

    public static ItemStack getSelectedStack(ItemStack stackNullifier)
    {
        // Create the inventory here by pretending to be on the server side,
        // so that the readFromContainerItemStack() call actually reads the items on the client side too.
        // This is (mostly only?) required to get the block placing sounds to work on the client.
        // Ie. in other words, to get the correct item to use also on the client side.
        ItemHandlerNullifier inv = createInventoryForItem(stackNullifier, false);

        int slot = MathHelper.clamp(NBTUtils.getByte(stackNullifier, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION), 0, inv.getSlots() - 1);
        return inv.getStackInSlot(slot);
    }

    private static ItemHandlerNullifier getInventoryForItem(ItemStack stackNullifier, EntityPlayer player)
    {
        ItemHandlerNullifier inv = null;

        // If this bag is currently open, then use that inventory instead of creating a new one,
        // otherwise the open GUI/inventory will overwrite the changes from the picked up items.
        if (player.openContainer instanceof ContainerNullifier &&
            ((ContainerNullifier) player.openContainer).getContainerItem() == stackNullifier)
        {
            inv = ((ContainerNullifier) player.openContainer).inventoryItem;
        }
        else
        {
            inv = createInventoryForItem(stackNullifier, player.getEntityWorld().isRemote);
        }

        if (inv.isAccessibleBy(player) == false)
        {
            return null;
        }

        return inv;
    }

    public static ItemHandlerNullifier createInventoryForItem(ItemStack stack, boolean isRemote)
    {
        ItemHandlerNullifier inv = new ItemHandlerNullifier(stack, NUM_SLOTS, MAX_STACK_SIZE, true, isRemote);
        inv.readFromContainerItemStack();
        return inv;
    }

    /**
     * Tries to first fill the matching stacks in the player's inventory,
     * and then depending on the bag's mode, tries to add the remaining items
     * to the bag's inventory.
     * @param event
     * @return true if all items were handled and further processing of the event should not occur
     */
    public static boolean onEntityItemPickupEvent(EntityItemPickupEvent event)
    {
        EntityItem entityItem = event.getItem();
        ItemStack stack = entityItem.getItem();
        EntityPlayer player = event.getEntityPlayer();

        if (player.getEntityWorld().isRemote || entityItem.isDead || stack.isEmpty())
        {
            return true;
        }

        ItemStack origStack = ItemStack.EMPTY;
        final int origStackSize = stack.getCount();
        int stackSizeLast = origStackSize;
        boolean ret = false;

        IItemHandler playerInv = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
        // Not all the items could fit into existing stacks in the player's inventory, move them directly to the nullifier
        List<Integer> slots = InventoryUtils.getSlotNumbersOfMatchingItems(playerInv, EnderUtilitiesItems.NULLIFIER);

        for (int slot : slots)
        {
            ItemStack nullifierStack = playerInv.getStackInSlot(slot);

            // Nullifier is not disabled
            if (nullifierStack.isEmpty() == false && isNullifierEnabled(nullifierStack))
            {
                // Delayed the stack copying until we know if there is a valid bag,
                // so check if the stack was copied already or not.
                if (origStack == ItemStack.EMPTY)
                {
                    origStack = stack.copy();
                }

                stack = handleItems(stack, nullifierStack, player);

                if (stack.isEmpty() || stack.getCount() != stackSizeLast)
                {
                    if (stack.isEmpty())
                    {
                        entityItem.setDead();
                        event.setCanceled(true);
                        ret = true;
                        break;
                    }

                    ItemStack pickedUpStack = origStack.copy();
                    pickedUpStack.setCount(stackSizeLast - stack.getCount());

                    FMLCommonHandler.instance().firePlayerItemPickupEvent(player, entityItem, pickedUpStack);
                    player.onItemPickup(entityItem, origStackSize);
                }

                stackSizeLast = stack.getCount();
            }
        }

        // Not everything was handled, update the stack
        if (entityItem.isDead == false && stack.getCount() != origStackSize)
        {
            entityItem.setItem(stack);
        }

        // At least some items were picked up
        if (entityItem.isSilent() == false && (entityItem.isDead || stack.getCount() != origStackSize))
        {
            player.getEntityWorld().playSound(null, player.getPosition(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.MASTER,
                    0.2F, ((itemRand.nextFloat() - itemRand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
        }

        return ret;
    }

    /**
     * Tries to first fill the matching stacks in the player's inventory,
     * and then depending on the bag's mode, tries to add the remaining items
     * to the bag's inventory.
     * @param event
     * @return true if all items were handled and further processing of the event should not occur
     */
    public static boolean onItemPickupEvent(PlayerItemPickupEvent event)
    {
        if (event.getEntityPlayer().getEntityWorld().isRemote)
        {
            return false;
        }

        boolean pickedUp = false;
        EntityPlayer player = event.getEntityPlayer();
        IItemHandler playerInv = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
        List<Integer> nullifierSlots = InventoryUtils.getSlotNumbersOfMatchingItems(playerInv, EnderUtilitiesItems.NULLIFIER);
        Iterator<ItemStack> iter = event.drops.iterator();

        while (iter.hasNext())
        {
            ItemStack stack = iter.next();

            if (stack.isEmpty())
            {
                iter.remove();
                continue;
            }

            // Not all the items could fit into existing stacks in the player's inventory, move them directly to the nullifier
            for (int slot : nullifierSlots)
            {
                ItemStack nullifierStack = playerInv.getStackInSlot(slot);

                // Nullifier is not disabled
                if (nullifierStack.isEmpty() == false && isNullifierEnabled(nullifierStack))
                {
                    ItemStack stackOrig = stack;
                    stack = handleItems(stack, nullifierStack, player);

                    if (stack.isEmpty())
                    {
                        iter.remove();
                        pickedUp = true;
                    }
                    else if (stackOrig.getCount() != stack.getCount())
                    {
                        stackOrig.setCount(stack.getCount());
                        pickedUp = true;
                    }
                }
            }
        }

        // At least some items were picked up
        if (pickedUp)
        {
            player.getEntityWorld().playSound(null, player.getPosition(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.MASTER, 0.2F,
                    ((itemRand.nextFloat() - itemRand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
        }

        if (event.drops.isEmpty())
        {
            event.setCanceled(true);
            return true;
        }

        return false;
    }

    private static ItemStack handleItems(ItemStack itemsIn, ItemStack nullifierStack, EntityPlayer player)
    {
        IItemHandler playerInv = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);

        // First try to fill all existing stacks in the player's inventory
        itemsIn = InventoryUtils.tryInsertItemStackToExistingStacksInInventory(playerInv, itemsIn);

        if (itemsIn.isEmpty() == false)
        {
            itemsIn = tryInsertItemsToNullifier(itemsIn, nullifierStack, player);
        }

        return itemsIn;
    }

    private static ItemStack tryInsertItemsToNullifier(ItemStack itemsIn, ItemStack nullifierStack, EntityPlayer player)
    {
        ItemHandlerNullifier nullifierInv = getInventoryForItem(nullifierStack, player);

        if (nullifierInv != null)
        {
            itemsIn = InventoryUtils.tryInsertItemStackToExistingStacksInInventory(nullifierInv, itemsIn);

            // Couldn't insert all items, check if there are matching items in the nullifier
            // and if so, then we just delete the excess items that didn't fit.
            if (itemsIn.isEmpty() == false && InventoryUtils.getSlotOfFirstMatchingItemStack(nullifierInv, itemsIn) != -1)
            {
                return ItemStack.EMPTY;
            }
        }

        return itemsIn;
    }

    public static void performGuiAction(EntityPlayer player, int action, int element)
    {
        if (player.openContainer instanceof ContainerNullifier)
        {
            ItemStack stack = ((ContainerNullifier) player.openContainer).getContainerItem();

            if (stack.isEmpty() == false)
            {
                if (action == GUI_ACTION_SELECT_SLOT)
                {
                    NBTUtils.setByte(stack, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION, (byte) MathHelper.clamp(element, 0, NUM_SLOTS - 1));
                }
                else if (action == GUI_ACTION_TOGGLE_DISABLED)
                {
                    NBTUtils.toggleBoolean(stack, TAG_NAME_CONTAINER, TAG_NAME_DISABLED);
                }
            }
        }
    }

    @Override
    public boolean doKeyBindingAction(EntityPlayer player, ItemStack stack, int key)
    {
        // Just Toggle mode: Open the GUI
        if (EnumKey.TOGGLE.matches(key, HotKeys.MOD_NONE))
        {
            openGui(stack, player);
            return true;
        }
        // Shift + Toggle: Toggle disabled state
        else if (EnumKey.TOGGLE.matches(key, HotKeys.MOD_SHIFT))
        {
            NBTUtils.toggleBoolean(stack, TAG_NAME_CONTAINER, TAG_NAME_DISABLED);
            return true;
        }
        else if (EnumKey.SCROLL.matches(key, HotKeys.MOD_CTRL) || EnumKey.TOGGLE.matches(key, HotKeys.MOD_CTRL))
        {
            NBTUtils.cycleByteValue(stack, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION, NUM_SLOTS - 1,
                    EnumKey.keypressActionIsReversed(key) || EnumKey.keypressContainsShift(key));
            return true;
        }

        return false;
    }

    @Override
    public String getItemStackDisplayName(ItemStack stack)
    {
        String name = super.getItemStackDisplayName(stack);
        int slot = NBTUtils.getByte(stack, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION) + 1;
        ItemStack selectedStack = getSelectedStack(stack);

        if (selectedStack.isEmpty() == false)
        {
            return name + " - " + slot + " / " + NUM_SLOTS + " - " + selectedStack.getDisplayName();
        }
        else
        {
            return name + " - " + slot + " / " + NUM_SLOTS;
        }
    }

    @Override
    public void addTooltipLines(ItemStack stack, EntityPlayer player, List<String> list, boolean verbose)
    {
        if (stack.getTagCompound() == null)
        {
            return;
        }

        String preBlue = TextFormatting.BLUE.toString();
        String preGreen = TextFormatting.GREEN.toString();
        String preRed = TextFormatting.RED.toString();
        String rst = TextFormatting.RESET.toString() + TextFormatting.GRAY.toString();
        String str;

        if (isNullifierEnabled(stack))
        {
            str = preGreen + I18n.format("enderutilities.tooltip.item.yes") + rst;
        }
        else
        {
            str = preRed + I18n.format("enderutilities.tooltip.item.no") + rst;
        }

        list.add(I18n.format("enderutilities.tooltip.item.enabled") + ": " + str);

        int slot = NBTUtils.getByte(stack, TAG_NAME_CONTAINER, TAG_NAME_SLOT_SELECTION) + 1;
        list.add(I18n.format("enderutilities.tooltip.item.selected") + ": " + preBlue + slot + " / " + NUM_SLOTS + rst);

        ArrayList<String> lines = new ArrayList<String>();
        int itemCount = UtilItemModular.getFormattedItemListFromContainerItem(stack, lines, 20);

        if (lines.size() > 0)
        {
            NBTTagList tagList = NBTUtils.getStoredItemsList(stack, false);
            int stackCount = tagList != null ? tagList.tagCount() : 0;
            list.add(I18n.format("enderutilities.tooltip.item.memorycard.items.stackcount", stackCount, itemCount));
            list.addAll(lines);
        }
        else
        {
            list.add(I18n.format("enderutilities.tooltip.item.memorycard.noitems"));
        }
    }

    public static void openGui(ItemStack stack, EntityPlayer player)
    {
        // These two lines are to fix the UUID being missing the first time the GUI opens,
        // if the item is grabbed from the creative inventory or from JEI or from /give
        NBTUtils.getUUIDFromItemStack(stack, "UUID", true);
        player.openContainer.detectAndSendChanges();

        player.openGui(EnderUtilities.instance, ReferenceGuiIds.GUI_ID_NULLIFIER, player.getEntityWorld(), 0, 0, 0);
    }

    public static class ItemHandlerNullifier extends InventoryItem
    {
        public ItemHandlerNullifier(ItemStack containerStack, int invSize, int stackLimit,
                boolean allowCustomStackSizes, boolean isRemote)
        {
            super(containerStack, invSize, stackLimit, allowCustomStackSizes, isRemote);
        }

        @Override
        public boolean isItemValidForSlot(int slot, ItemStack stack)
        {
            // Only allow in stackable, non-damageable items, so that there hopefully
            // won't be many issues with item usage not consuming the item or changing it...
            return stack.isEmpty() == false &&
                   stack.getMaxStackSize() > 1 &&
                   stack.isItemStackDamageable() == false;
        }
    }
}