package codechicken.nei;

import codechicken.nei.guihook.GuiContainerManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;

import java.util.*;

import static codechicken.nei.NEIServerUtils.*;

public class FastTransferManager
{
    /**
     * Based on the general assumption that we want to fill top to bottom, left to right
     */
    public static class SlotPositionComparator implements Comparator<Integer>
    {
        Container container;

        public SlotPositionComparator(Container c) {
            container = c;
        }

        @Override
        public int compare(Integer arg0, Integer arg1) {
            Slot slot1 = container.getSlot(arg0);
            Slot slot2 = container.getSlot(arg1);

            if (slot2.yDisplayPosition != slot1.yDisplayPosition)
                return slot1.yDisplayPosition - slot2.yDisplayPosition;
            return slot1.xDisplayPosition - slot2.xDisplayPosition;
        }
    }

    public LinkedList<LinkedList<Integer>> slotZones = new LinkedList<LinkedList<Integer>>();
    public HashMap<Integer, Integer> slotZoneMap = new HashMap<Integer, Integer>();

    private void generateSlotMap(Container container, ItemStack stack) {
        stack = stack.copy();
        stack.stackSize = 1;

        for (int slotNo = 0; slotNo < container.inventorySlots.size(); slotNo++) {
            if (slotZoneMap.containsKey(slotNo) || !container.getSlot(slotNo).isItemValid(stack))
                continue;

            HashSet<Integer> connectedSlots = new HashSet<Integer>();
            findConnectedSlots(container, slotNo, connectedSlots);

            LinkedList<Integer> zoneSlots = new LinkedList<Integer>(connectedSlots);
            Collections.sort(zoneSlots, new SlotPositionComparator(container));
            slotZones.add(zoneSlots);

            for (int i : zoneSlots) {
                slotZoneMap.put(i, slotZones.size() - 1);
            }
        }
    }

    private void findConnectedSlots(Container container, int slotNo, HashSet<Integer> connectedSlots) {
        connectedSlots.add(slotNo);
        Slot slot = container.getSlot(slotNo);
        final int threshold = 18;

        for (int i = 0; i < container.inventorySlots.size(); i++) {
            if (connectedSlots.contains(i))
                continue;

            Slot slot1 = container.getSlot(i);
            if (Math.abs(slot.xDisplayPosition - slot1.xDisplayPosition) <= threshold && Math.abs(slot.yDisplayPosition - slot1.yDisplayPosition) <= threshold) {
                findConnectedSlots(container, i, connectedSlots);
            }
        }
    }

    public static int findSlotWithItem(Container container, ItemStack teststack) {
        for (int slotNo = 0; slotNo < container.inventorySlots.size(); slotNo++) {
            ItemStack stack = container.getSlot(slotNo).getStack();
            if (stack != null && areStacksSameType(stack, teststack))
                return slotNo;
        }
        return -1;
    }

    public static void clearSlots(Container container) {
        for (int slotNo = 0; slotNo < container.inventorySlots.size(); slotNo++)
            ((Slot) container.inventorySlots.get(slotNo)).putStack(null);
    }

    public void performMassTransfer(GuiContainer window, int fromSlot, int toSlot, ItemStack heldStack) {
        generateSlotMap(window.inventorySlots, heldStack);

        Integer fromZone = slotZoneMap.get(fromSlot);
        Integer toZone = slotZoneMap.get(toSlot);

        if (fromZone == null || toZone == null || fromZone.equals(toZone))
            return;

        if (NEIClientUtils.getHeldItem() != null && !areStacksSameType(heldStack, NEIClientUtils.getHeldItem()))
            return;

        if (!fillZoneWithHeldItem(window, toZone))
            return;

        for (int transferFrom : slotZones.get(fromZone)) {
            ItemStack transferStack = window.inventorySlots.getSlot(transferFrom).getStack();

            if (!areStacksSameType(heldStack, transferStack))
                continue;

            clickSlot(window, transferFrom);
            if (!fillZoneWithHeldItem(window, toZone)) {
                clickSlot(window, transferFrom);
                return;
            }
        }
    }

    /**
     * @return The slot that one item from the source slot will end up in apon shift clicking, -1 if none.
     */
    public int findShiftClickDestinationSlot(Container container, int fromSlot) {
        LinkedList<ItemStack> save = saveContainer(container);

        Slot slot = container.getSlot(fromSlot);
        ItemStack stack = slot.getStack();
        if (stack == null)
            return -1;

        stack.stackSize = 1;
        slot.putStack(stack.copy());

        LinkedList<ItemStack> compareBefore = saveContainer(container);
        container.slotClick(fromSlot, 0, 1, Minecraft.getMinecraft().thePlayer);
        LinkedList<ItemStack> compareAfter = saveContainer(container);

        try {
            //if(compareAfter.get(fromSlot) != null)//transfer failed
            //return -1;

            for (int i = 0; i < compareBefore.size(); i++) {
                if (i == fromSlot)
                    continue;

                ItemStack before = compareBefore.get(i);
                ItemStack after = compareAfter.get(i);

                if (!areStacksIdentical(before, after) && after != null)
                    if (before == null ? areStacksSameType(stack, after) ://transfered into this empty slot
                            areStacksSameType(stack, after) && after.stackSize - before.stackSize > 0)//it added to this stack
                        return i;
            }

            return -1;
        } finally {
            restoreContainer(container, save);
        }
    }

    public LinkedList<ItemStack> saveContainer(Container container) {
        LinkedList<ItemStack> stacks = new LinkedList<ItemStack>();
        for (int i = 0; i < container.inventorySlots.size(); i++)
            stacks.add(copyStack(container.getSlot(i).getStack()));

        return stacks;
    }

    public void restoreContainer(Container container, LinkedList<ItemStack> items) {
        for (int i = 0; i < container.inventorySlots.size(); i++) {
            container.getSlot(i).putStack(items.get(i));
        }

        container.slotClick(-999, 0, 0, Minecraft.getMinecraft().thePlayer);
    }

    public void transferItem(GuiContainer window, int fromSlot) {
        int toSlot = findShiftClickDestinationSlot(window.inventorySlots, fromSlot);
        if (toSlot == -1)
            return;

        Slot from = window.inventorySlots.getSlot(fromSlot);

        if (from.isItemValid(from.getStack()))
            moveOneItem(window, fromSlot, toSlot);
        else//slots that you can't put stuff in
            moveOutputSet(window, fromSlot, toSlot);
    }

    public void moveOutputSet(GuiContainer window, int fromSlot, int toSlot) {
        if (NEIClientUtils.getHeldItem() != null)
            return;

        clickSlot(window, fromSlot);//pickup fromSlot
        if (NEIClientUtils.getHeldItem() == null)//maybe this container does auto transfers. No need to pick up the final item
            return;
        clickSlot(window, toSlot);//place one in toSlot
    }

    public void moveOneItem(GuiContainer window, int fromSlot, int toSlot) {
        clickSlot(window, fromSlot);//pickup fromSlot
        clickSlot(window, toSlot, 1);//place one in toSlot
        clickSlot(window, fromSlot);//place down in fromSlot
    }

    public void retrieveItem(GuiContainer window, int toSlot) {
        Slot slot = window.inventorySlots.getSlot(toSlot);
        ItemStack slotStack = slot.getStack();
        if (slotStack == null ||
                slotStack.stackSize == slot.getSlotStackLimit() ||
                slotStack.stackSize == slotStack.getMaxStackSize())
            return;

        generateSlotMap(window.inventorySlots, slotStack);

        Integer destZone = slotZoneMap.get(toSlot);
        if (destZone == null)//slots that don't accept
            return;

        int firstZoneSlot = findShiftClickDestinationSlot(window.inventorySlots, toSlot);
        int firstZone = -1;
        if (firstZoneSlot != -1) {
            Integer integer = slotZoneMap.get(firstZoneSlot);
            if (integer != null) {
                firstZone = integer;
                if (retrieveItemFromZone(window, firstZone, toSlot))
                    return;
            }
        }

        for (int zone = 0; zone < slotZones.size(); zone++) {
            if (zone == destZone || zone == firstZone)
                continue;

            if (retrieveItemFromZone(window, zone, toSlot))
                return;
        }

        retrieveItemFromZone(window, destZone, toSlot);
    }

    private boolean retrieveItemFromZone(GuiContainer window, int zone, int toSlot) {
        ItemStack stack = window.inventorySlots.getSlot(toSlot).getStack();
        for (int i : slotZones.get(zone)) {
            if (i == toSlot)
                continue;

            Slot slot = window.inventorySlots.getSlot(i);
            ItemStack stack1 = slot.getStack();

            if (areStacksSameType(stack, stack1) &&
                    stack1.stackSize != slot.getSlotStackLimit() && //get from full stacks on second pass
                    stack1.stackSize != stack1.getMaxStackSize()) {
                moveOneItem(window, i, toSlot);
                return true;
            }
        }

        for (int i : slotZones.get(zone)) {
            if (i == toSlot)
                continue;

            Slot slot = window.inventorySlots.getSlot(i);
            ItemStack stack1 = slot.getStack();

            if (areStacksSameType(stack, stack1)) {
                moveOneItem(window, i, toSlot);
                return true;
            }
        }
        return false;
    }

    public static void clickSlot(GuiContainer window, int slotIndex) {
        clickSlot(window, slotIndex, 0);
    }

    public static void clickSlot(GuiContainer window, int slotIndex, int button) {
        clickSlot(window, slotIndex, button, 0);
    }

    public static void clickSlot(GuiContainer window, int slotIndex, int button, int modifier) {
        GuiContainerManager.getManager(window).handleSlotClick(slotIndex, button, modifier);
    }

    private boolean fillZoneWithHeldItem(GuiContainer window, int zoneIndex) {
        for (int transferTo : slotZones.get(zoneIndex)) {
            ItemStack held = NEIClientUtils.getHeldItem();

            if (held == null)
                break;

            ItemStack inToSlot = window.inventorySlots.getSlot(transferTo).getStack();

            if (!areStacksSameType(inToSlot, held))
                continue;

            clickSlot(window, transferTo);
        }

        for (int transferTo : slotZones.get(zoneIndex))//repeat on empty slots
        {
            ItemStack held = NEIClientUtils.getHeldItem();

            if (held == null)
                break;

            ItemStack inToSlot = window.inventorySlots.getSlot(transferTo).getStack();

            if (inToSlot != null)
                continue;

            clickSlot(window, transferTo);
        }

        return NEIClientUtils.getHeldItem() == null;
    }

    public void throwAll(GuiContainer window, int pickedUpFromSlot) {
        ItemStack held = NEIClientUtils.getHeldItem();
        if (held == null)
            return;

        clickSlot(window, -999);

        generateSlotMap(window.inventorySlots, held);
        Integer zone = slotZoneMap.get(pickedUpFromSlot);
        if(zone == null) //something went wrong and we can't work out where the item was picked up from
            return;

        for (int slotIndex : slotZones.get(zone)) {
            Slot slot = window.inventorySlots.getSlot(slotIndex);
            if (areStacksSameType(held, slot.getStack())) {
                clickSlot(window, slotIndex);
                clickSlot(window, -999);
            }
        }
    }
}