package fi.dy.masa.enderutilities.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.oredict.OreDictionary;
import fi.dy.masa.enderutilities.inventory.IItemHandlerSize;
import fi.dy.masa.enderutilities.inventory.ItemStackHandlerBasic;
import fi.dy.masa.enderutilities.inventory.container.base.SlotRange;
import fi.dy.masa.enderutilities.util.nbt.NBTUtils;

public class InventoryUtils
{
    public static final int SLOT_ITER_LIMIT = 256;
    public static final ItemStackHandlerBasic NULL_INV = new ItemStackHandlerBasic(0);

    public static int calcRedstoneFromInventory(IItemHandler inv)
    {
        final int slots = inv.getSlots();
        int items = 0;
        int capacity = 0;

        for (int slot = 0; slot < slots; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if ((inv instanceof IItemHandlerSize) && stack.isEmpty() == false)
            {
                capacity += ((IItemHandlerSize)inv).getItemStackLimit(slot, stack);
            }
            else
            {
                capacity += inv.getSlotLimit(slot);
            }

            if (stack.isEmpty() == false)
            {
                items += stack.getCount();
            }
        }

        if (capacity > 0)
        {
            int strength = (14 * items) / capacity;

            // Emit a signal strength of 1 as soon as there is one item in the inventory
            if (items > 0)
            {
                strength += 1;
            }

            return strength;
        }

        return 0;
    }

    /**
     * Drops all the ItemStacks from the given inventory into the world as EntityItems
     * @param world
     * @param pos
     * @param inv
     */
    public static void dropInventoryContentsInWorld(World world, BlockPos pos, IItemHandler inv)
    {
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false)
            {
                EntityUtils.dropItemStacksInWorld(world, pos, stack, -1, true);
            }
        }
    }

    /**
     * Tries to move all items from the inventory invSrc into invDst.
     */
    public static InvResult tryMoveAllItems(IItemHandler invSrc, IItemHandler invDst)
    {
        return tryMoveAllItemsWithinSlotRange(invSrc, invDst, new SlotRange(invSrc), new SlotRange(invDst));
    }

    /**
     * Tries to move all items from the inventory invSrc into invDst within the provided slot range.
     */
    public static InvResult tryMoveAllItemsWithinSlotRange(IItemHandler invSrc, IItemHandler invDst, SlotRange slotsSrc, SlotRange slotsDst)
    {
        boolean movedAll = true;
        boolean movedSome = false;
        final int lastSlot = Math.min(slotsSrc.lastInc, invSrc.getSlots() - 1);

        for (int slot = slotsSrc.first; slot <= lastSlot; slot++)
        {
            ItemStack stack;

            int limit = SLOT_ITER_LIMIT;

            while (limit-- > 0)
            {
                stack = invSrc.extractItem(slot, 64, false);

                if (stack.isEmpty())
                {
                    break;
                }

                int origSize = stack.getCount();

                stack = tryInsertItemStackToInventoryWithinSlotRange(invDst, stack, slotsDst);

                if (stack.isEmpty() || stack.getCount() != origSize)
                {
                    movedSome = true;
                }

                // Can't insert anymore items
                if (stack.isEmpty() == false)
                {
                    // Put the rest of the items back to the source inventory
                    invSrc.insertItem(slot, stack, false);
                    movedAll = false;
                    break;
                }
            }
        }

        return movedAll ? InvResult.MOVED_ALL : (movedSome ? InvResult.MOVED_SOME : InvResult.MOVED_NOTHING);
    }

    /**
     * Tries to move matching/existing items from the inventory invSrc into invDst.
     */
    public static InvResult tryMoveMatchingItems(IItemHandler invSrc, IItemHandler invDst)
    {
        return tryMoveMatchingItemsWithinSlotRange(invSrc, invDst, new SlotRange(invSrc), new SlotRange(invDst));
    }

    /**
     * Tries to move matching/existing items from the inventory invSrc into invDst within the provided slot range.
     */
    public static InvResult tryMoveMatchingItemsWithinSlotRange(IItemHandler invSrc, IItemHandler invDst, SlotRange slotsSrc, SlotRange slotsDst)
    {
        boolean movedAll = true;
        boolean movedSome = false;
        InvResult result = InvResult.MOVED_NOTHING;
        final int lastSlot = Math.min(slotsSrc.lastInc, invSrc.getSlots() - 1);

        for (int slot = slotsSrc.first; slot <= lastSlot; slot++)
        {
            ItemStack stack = invSrc.getStackInSlot(slot);

            if (stack.isEmpty() == false)
            {
                if (getSlotOfFirstMatchingItemStackWithinSlotRange(invDst, stack, slotsDst) != -1)
                {
                    result = tryMoveAllItemsWithinSlotRange(invSrc, invDst, new SlotRange(slot, 1), slotsDst);
                }

                if (result != InvResult.MOVED_NOTHING)
                {
                    movedSome = true;
                }
                else
                {
                    movedAll = false;
                }
            }
        }

        return movedAll ? InvResult.MOVED_ALL : (movedSome ? InvResult.MOVED_SOME : InvResult.MOVED_NOTHING);
    }

    /**
     * Tries to fill all the existing stacks in invDst from invSrc.
     */
    public static InvResult fillStacksOfMatchingItems(IItemHandler invSrc, IItemHandler invDst)
    {
        return fillStacksOfMatchingItemsWithinSlotRange(invSrc, invDst, new SlotRange(invSrc), new SlotRange(invDst));
    }

    /**
     * Tries to fill all the existing stacks in invDst from invSrc within the provided slot ranges.
     */
    public static InvResult fillStacksOfMatchingItemsWithinSlotRange(IItemHandler invSrc, IItemHandler invDst, SlotRange slotsSrc, SlotRange slotsDst)
    {
        boolean movedAll = true;
        boolean movedSome = false;
        InvResult result = InvResult.MOVED_NOTHING;
        final int lastSlot = Math.min(slotsSrc.lastInc, invSrc.getSlots() - 1);

        for (int slot = slotsSrc.first; slot <= lastSlot; slot++)
        {
            ItemStack stack = invSrc.getStackInSlot(slot);

            if (stack.isEmpty() == false)
            {
                List<Integer> matchingSlots = getSlotNumbersOfMatchingStacksWithinSlotRange(invDst, stack, slotsDst);

                for (int dstSlot : matchingSlots)
                {
                    result = tryMoveAllItemsWithinSlotRange(invSrc, invDst, new SlotRange(slot, 1), new SlotRange(dstSlot, 1));

                    if (result != InvResult.MOVED_NOTHING)
                    {
                        movedSome = true;
                    }
                    else
                    {
                        movedAll = false;
                    }
                }
            }
        }

        return movedAll ? InvResult.MOVED_ALL : (movedSome ? InvResult.MOVED_SOME : InvResult.MOVED_NOTHING);
    }

    /**
     * Tries to insert the given ItemStack stack to the target inventory.
     * The return value is a stack of the remaining items that couldn't be inserted.
     * If all items were successfully inserted, then null is returned.
     */
    public static ItemStack tryInsertItemStackToInventory(IItemHandler inv, @Nonnull ItemStack stackIn)
    {
        return tryInsertItemStackToInventoryWithinSlotRange(inv, stackIn, new SlotRange(inv));
    }

    /**
     * Tries to insert the given ItemStack stack to the target inventory, inside the given slot range.
     * The return value is a stack of the remaining items that couldn't be inserted.
     * If all items were successfully inserted, then null is returned.
     */
    public static ItemStack tryInsertItemStackToInventoryWithinSlotRange(IItemHandler inv, @Nonnull ItemStack stackIn, SlotRange slotRange)
    {
        final int lastSlot = Math.min(slotRange.lastInc, inv.getSlots() - 1);

        // First try to add to existing stacks
        for (int slot = slotRange.first; slot <= lastSlot; slot++)
        {
            if (inv.getStackInSlot(slot).isEmpty() == false)
            {
                stackIn = inv.insertItem(slot, stackIn, false);

                if (stackIn.isEmpty())
                {
                    return ItemStack.EMPTY;
                }
            }
        }

        // Second round, try to add to any slot
        for (int slot = slotRange.first; slot <= lastSlot; slot++)
        {
            stackIn = inv.insertItem(slot, stackIn, false);

            if (stackIn.isEmpty())
            {
                return ItemStack.EMPTY;
            }
        }

        return stackIn;
    }

    /**
     * Try insert the items in <b>stackIn</b> into existing stacks with identical items in the inventory <b>inv</b>.
     * @return null if all items were successfully inserted, otherwise the stack containing the remaining items
     */
    public static ItemStack tryInsertItemStackToExistingStacksInInventory(IItemHandler inv, @Nonnull ItemStack stackIn)
    {
        List<Integer> slots = getSlotNumbersOfMatchingStacks(inv, stackIn);

        for (int slot : slots)
        {
            stackIn = inv.insertItem(slot, stackIn, false);

            // If the entire (remaining) stack was inserted to the current slot, then we are done
            if (stackIn.isEmpty())
            {
                return ItemStack.EMPTY;
            }
        }

        return stackIn;
    }

    /**
     * Checks if the given ItemStacks have the same item, damage and NBT. Ignores stack sizes.
     * Can be given null ItemStacks as input.
     * @param stack1
     * @param stack2
     * @return Returns true if the ItemStacks have the same item, damage and NBT tags.
     */
    public static boolean areItemStacksEqual(@Nonnull ItemStack stack1, @Nonnull ItemStack stack2)
    {
        if (stack1.isEmpty() || stack2.isEmpty())
        {
            return stack1.isEmpty() == stack2.isEmpty();
        }

        return stack1.isItemEqual(stack2) && ItemStack.areItemStackTagsEqual(stack1, stack2);
    }

    /**
     * Checks if the ItemStack <b>stackTarget</b> is valid to be used as a substitution
     * for <b>stackReference</b> via the OreDictionary keys.
     * @param stackTarget
     * @param stackReference
     * @return
     */
    public static boolean areItemStacksOreDictMatch(@Nonnull ItemStack stackTarget, @Nonnull ItemStack stackReference)
    {
        int[] ids = OreDictionary.getOreIDs(stackReference);

        for (int id : ids)
        {
            List<ItemStack> oreStacks = OreDictionary.getOres(OreDictionary.getOreName(id), false);

            for (ItemStack oreStack : oreStacks)
            {
                if (OreDictionary.itemMatches(stackTarget, oreStack, false))
                {
                    return true;
                }
            }
        }

        return false;
    }

    public static boolean doesStackMatchOreDictName(ItemStack stack, String name)
    {
        int[] ids = OreDictionary.getOreIDs(stack);

        for (int id : ids)
        {
            String oreName = OreDictionary.getOreName(id);

            if (oreName.startsWith(name))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns the slot number of the first empty slot in the given inventory, or -1 if there are no empty slots.
     */
    public static int getFirstEmptySlot(IItemHandler inv)
    {
        return getSlotOfFirstMatchingItemStack(inv, ItemStack.EMPTY);
    }

    /**
     * Returns the slot number of the last empty slot in the given inventory, or -1 if there are no empty slots.
     */
    public static int getLastEmptySlot(IItemHandler inv)
    {
        return getSlotOfLastMatchingItemStack(inv, ItemStack.EMPTY);
    }

    /**
     * Returns the slot number of the first non-empty slot in the given inventory, or -1 if there are no items.
     */
    public static int getFirstNonEmptySlot(IItemHandler inv)
    {
        for (int i = 0; i < inv.getSlots(); i++)
        {
            if (inv.getStackInSlot(i).isEmpty() == false)
            {
                return i;
            }
        }

        return -1;
    }

    /**
     * Returns an ItemStack of up to maxAmount items from the first slot possible to extract from.
     */
    public static ItemStack getItemsFromFirstNonEmptySlot(IItemHandler inv, int maxAmount, boolean simulate)
    {
        ItemStack stack;

        for (int i = 0; i < inv.getSlots(); i++)
        {
            stack = inv.extractItem(i, maxAmount, simulate);

            if (stack.isEmpty() == false)
            {
                return stack;
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Get the slot number of the first slot containing a matching item, or -1 if there are no such items in the inventory.
     */
    public static int getSlotOfFirstMatchingItem(IItemHandler inv, Item item)
    {
        return getSlotOfFirstMatchingItem(inv, item, OreDictionary.WILDCARD_VALUE);
    }

    /**
     * Get the slot number of the first slot containing a matching item and damage value.
     * If <b>damage</b> is OreDictionary.WILDCARD_VALUE, then the item damage is ignored.
     * @return The slot number of the first slot with a matching item and damage value, or -1 if there are no such items in the inventory.
     */
    public static int getSlotOfFirstMatchingItem(IItemHandler inv, Item item, int meta)
    {
        int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && stack.getItem() == item &&
                (stack.getMetadata() == meta || meta == OreDictionary.WILDCARD_VALUE))
            {
                return slot;
            }
        }

        return -1;
    }

    /**
     * Returns the first ItemStack from the inventory that has the given Item in it, or null.
     */
    public static ItemStack getFirstMatchingItem(IItemHandler inv, Item item)
    {
        int slot = getSlotOfFirstMatchingItem(inv, item);
        return slot != -1 ? inv.getStackInSlot(slot) : ItemStack.EMPTY;
    }

    /**
     * Returns the first matching item from the player's inventory, or an empty stack.
     */
    public static ItemStack getFirstItemOfType(EntityPlayer player, Class<?> clazz)
    {
        IItemHandler inv = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
        final int numSlots = inv.getSlots();

        for (int slot = 0; slot < numSlots; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && clazz.isAssignableFrom(stack.getItem().getClass()))
            {
                return stack;
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Returns the first matching item from the player's inventory, or an empty stack.
     */
    public static ItemStack getFirstItemOfType(EntityPlayer player, Item item)
    {
        IItemHandler inv = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
        final int numSlots = inv.getSlots();

        for (int slot = 0; slot < numSlots; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && stack.getItem() == item)
            {
                return stack;
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Get the slot number of the last slot containing a matching item, or -1 if there are no such items in the inventory.
     */
    public static int getSlotOfLastMatchingItem(IItemHandler inv, Item item)
    {
        return getSlotOfLastMatchingItem(inv, item, OreDictionary.WILDCARD_VALUE);
    }

    /**
     * Get the slot number of the last slot containing a matching item and damage value.
     * If <b>damage</b> is OreDictionary.WILDCARD_VALUE, then the item damage is ignored.
     * @param inv
     * @param item
     * @return The slot number of the last slot with a matching item and damage value, or -1 if there are no such items in the inventory.
     */
    public static int getSlotOfLastMatchingItem(IItemHandler inv, Item item, int meta)
    {
        for (int slot = inv.getSlots() - 1; slot >= 0; slot--)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && stack.getItem() == item &&
                (stack.getMetadata() == meta || meta == OreDictionary.WILDCARD_VALUE))
            {
                return slot;
            }
        }

        return -1;
    }

    /**
     * Get the slot number of the first slot containing a matching ItemStack (including NBT, ignoring stackSize).
     * Note: stackIn can be empty.
     * @return The slot number of the first slot with a matching ItemStack, or -1 if there were no matches.
     */
    public static int getSlotOfFirstMatchingItemStack(IItemHandler inv, @Nonnull ItemStack stackIn)
    {
        return getSlotOfFirstMatchingItemStackWithinSlotRange(inv, stackIn, new SlotRange(inv));
    }

    /**
     * Get the slot number of the first slot containing a matching ItemStack (including NBT, ignoring stackSize) within the given slot range.
     * Note: stackIn can be empty.
     * @return The slot number of the first slot with a matching ItemStack, or -1 if there were no matches.
     */
    public static int getSlotOfFirstMatchingItemStackWithinSlotRange(IItemHandler inv, @Nonnull ItemStack stackIn, SlotRange slotRange)
    {
        final int lastSlot = Math.min(inv.getSlots() - 1, slotRange.lastInc);

        for (int slot = slotRange.first; slot <= lastSlot; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (areItemStacksEqual(stack, stackIn))
            {
                return slot;
            }
        }

        return -1;
    }

    /**
     * Get the slot number of the last slot containing a matching ItemStack (including NBT, ignoring stackSize).
     * Note: stackIn can be empty.
     * @param inv
     * @param item
     * @return The slot number of the last slot with a matching ItemStack, or -1 if there were no matches.
     */
    public static int getSlotOfLastMatchingItemStack(IItemHandler inv, @Nonnull ItemStack stackIn)
    {
        return getSlotOfLastMatchingItemStackWithinSlotRange(inv, stackIn, new SlotRange(inv));
    }

    /**
     * Get the slot number of the last slot containing a matching ItemStack (including NBT, ignoring stackSize) within the given slot range.
     * Note: stackIn can be empty.
     * @return The slot number of the last slot with a matching ItemStack, or -1 if there were no matches.
     */
    public static int getSlotOfLastMatchingItemStackWithinSlotRange(IItemHandler inv, @Nonnull ItemStack stackIn, SlotRange slotRange)
    {
        final int lastSlot = Math.min(inv.getSlots() - 1, slotRange.lastInc);

        for (int slot = lastSlot; slot >= slotRange.first; slot--)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (areItemStacksEqual(stack, stackIn))
            {
                return slot;
            }
        }

        return -1;
    }

    /**
     * Get all the slot numbers that have matching items in the given inventory.
     * @param inv
     * @param item
     * @return an ArrayList containing the slot numbers of the slots with matching items
     */
    public static List<Integer> getSlotNumbersOfMatchingItems(IItemHandler inv, Item item)
    {
        return getSlotNumbersOfMatchingItems(inv, item, OreDictionary.WILDCARD_VALUE);
    }

    /**
     * Get all the slot numbers that have matching items in the given inventory.
     * If <b>damage</b> is OreDictionary.WILDCARD_VALUE, then the item damage is ignored.
     * @param inv
     * @param item
     * @return an ArrayList containing the slot numbers of the slots with matching items
     */
    public static List<Integer> getSlotNumbersOfMatchingItems(IItemHandler inv, Item item, int meta)
    {
        List<Integer> slots = new ArrayList<Integer>();
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; ++slot)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && stack.getItem() == item &&
                (stack.getMetadata() == meta || meta == OreDictionary.WILDCARD_VALUE))
            {
                slots.add(Integer.valueOf(slot));
            }
        }

        return slots;
    }

    /**
     * Get all the slot numbers that have matching ItemStacks (including NBT, ignoring stackSize).
     * Note: stackIn can be empty.
     * @return an ArrayList containing the slot numbers of the slots with matching ItemStacks
     */
    public static List<Integer> getSlotNumbersOfMatchingStacks(IItemHandler inv, @Nonnull ItemStack stackIn)
    {
        return getSlotNumbersOfMatchingStacksWithinSlotRange(inv, stackIn, new SlotRange(inv));
    }

    /**
     * Get all the slot numbers that have matching ItemStacks (including NBT, ignoring stackSize) within the given slot range.
     * Note: stackIn can be empty.
     * @return an ArrayList containing the slot numbers of the slots with matching ItemStacks
     */
    public static List<Integer> getSlotNumbersOfMatchingStacksWithinSlotRange(IItemHandler inv, @Nonnull ItemStack stackIn, SlotRange slotRange)
    {
        List<Integer> slots = new ArrayList<Integer>();
        final int lastSlot = Math.min(inv.getSlots() - 1, slotRange.lastInc);

        for (int slot = slotRange.first; slot <= lastSlot; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (areItemStacksEqual(stack, stackIn))
            {
                slots.add(Integer.valueOf(slot));
            }
        }

        return slots;
    }

    public static List<Integer> getSlotNumbersOfMatchingStacks(IItemHandler inv, @Nonnull ItemStack stackTemplate, boolean useOreDict)
    {
        List<Integer> slots = new ArrayList<Integer>();
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false &&
                    (areItemStacksEqual(stack, stackTemplate) ||
                    (useOreDict && areItemStacksOreDictMatch(stack, stackTemplate))))
            {
                slots.add(Integer.valueOf(slot));
            }
        }

        return slots;
    }

    /**
     * Extracts up to <b>amount</b> of <b>item</b> from the first slot in the inventory that
     * has <b>item</b> in it and returns them. Does not try to fill the stack up to <b>amount</b>!
     */
    public static ItemStack extractItems(IItemHandler inv, Item item, int amount)
    {
        int slot = getSlotOfFirstMatchingItem(inv, item);
        return slot >= 0 ? inv.extractItem(slot, amount, false) : ItemStack.EMPTY;
    }

    /**
     * Extracts up to <b>amount</b> items from the first found slot with items matching <b>templateStack</b>.
     * Does not try to fill the stack up to amount if the first found slot has less than <b>amount</b> items!
     */
    public static ItemStack extractMatchingItems(IItemHandler inv, @Nonnull ItemStack templateStack, int amount, boolean simulate)
    {
        int slot = getSlotOfFirstMatchingItemStack(inv, templateStack);
        return slot >= 0 ? inv.extractItem(slot, amount, simulate) : ItemStack.EMPTY;
    }

    /**
     * Get the ItemStack that has the given UUID stored in its NBT. If <b>containerTagName</b>
     * is not null, then the UUID is read from a compound tag by that name.
     */
    public static ItemStack getItemStackByUUID(IItemHandler inv, UUID uuid, @Nullable String containerTagName)
    {
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && uuid.equals(NBTUtils.getUUIDFromItemStack(stack, containerTagName, false)))
            {
                return stack;
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Extract items from the given slot until the resulting stack's stackSize equals amount
     */
    public static ItemStack extractItemsFromSlot(IItemHandler inv, int slot, int amount)
    {
        ItemStack stackExtract = inv.extractItem(slot, amount, false);

        if (stackExtract.isEmpty())
        {
            return ItemStack.EMPTY;
        }

        if ((stackExtract.getMaxStackSize() * SLOT_ITER_LIMIT) < amount && inv instanceof IItemHandlerModifiable)
        {
            amount -= stackExtract.getCount();
            ItemStack stackSlot = inv.getStackInSlot(slot);

            if (stackSlot.isEmpty() == false)
            {
                if (stackSlot.getCount() <= amount)
                {
                    stackExtract.grow(stackSlot.getCount());
                    ((IItemHandlerModifiable) inv).setStackInSlot(slot, ItemStack.EMPTY);
                }
                else
                {
                    stackExtract.grow(amount);
                    stackSlot = stackSlot.copy();
                    stackSlot.shrink(amount);
                    ((IItemHandlerModifiable) inv).setStackInSlot(slot, stackSlot);
                }
            }

            return stackExtract;
        }

        int loops = 0;

        while (stackExtract.getCount() < amount && loops < SLOT_ITER_LIMIT)
        {
            ItemStack stackTmp = inv.extractItem(slot, amount - stackExtract.getCount(), false);

            if (stackTmp.isEmpty())
            {
                break;
            }

            stackExtract.grow(stackTmp.getCount());
            loops++;
        }

        //System.out.printf("extractItemsFromSlot(): slot: %d, requested amount: %d, loops %d, extracted: %s\n", slot, amount, loops, stack);
        return stackExtract;
    }

    /**
     * Collects items from the inventory that are identical to stackTemplate and makes a new ItemStack
     * out of them, up to stackSize = maxAmount. If <b>reverse</b> is true, then the items are collected
     * starting from the end of the given inventory.
     * If no matching items are found, null is returned.
     */
    public static ItemStack collectItemsFromInventory(IItemHandler inv, @Nonnull ItemStack stackTemplate, int maxAmount, boolean reverse)
    {
        return collectItemsFromInventory(inv, stackTemplate, maxAmount, reverse, false);
    }

    /**
     * Collects items from the inventory that are identical to stackTemplate and makes a new ItemStack
     * out of them, up to stackSize = maxAmount. If <b>reverse</b> is true, then the items are collected
     * starting from the end of the given inventory.
     * If no matching items are found, null is returned.
     */
    public static ItemStack collectItemsFromInventory(IItemHandler inv, @Nonnull ItemStack stackTemplate,
            int maxAmount, boolean reverse, boolean useOreDict)
    {
        return collectItemsFromInventoryFromSlotRange(inv, stackTemplate, new SlotRange(inv), maxAmount, reverse, useOreDict);
    }

    /**
     * Collects items from the inventory from within the given SlotRange that are identical to stackTemplate and makes a new ItemStack
     * out of them, up to stackSize = maxAmount. If <b>reverse</b> is true, then the items are collected
     * starting from the end of the given inventory.
     * If no matching items are found, null is returned.
     */
    public static ItemStack collectItemsFromInventoryFromSlotRange(IItemHandler inv, @Nonnull ItemStack stackTemplate,
            SlotRange range, int amount, boolean reverse, boolean useOreDict)
    {
        if (range.first >= inv.getSlots())
        {
            return ItemStack.EMPTY;
        }

        int inc = reverse ? -1 : 1;
        final int lastSlot = Math.min(range.lastInc, inv.getSlots() - 1);
        final int start = reverse ? lastSlot : range.first;
        ItemStack stack = stackTemplate.copy();
        stack.setCount(0);
        //System.out.printf("amount: %d range: %s stack: %s start: %d inc: %d\n", amount, range, stack, start, inc);

        for (int slot = start; slot >= range.first && slot <= lastSlot && stack.getCount() < amount; slot += inc)
        {
            ItemStack stackTmp = inv.getStackInSlot(slot);

            if (stackTmp.isEmpty())
            {
                continue;
            }

            if (areItemStacksEqual(stackTmp, stackTemplate))
            {
                stackTmp = extractItemsFromSlot(inv, slot, amount - stack.getCount());
                //System.out.printf("extracted %s from slot %d\n", stackTmp, slot);

                if (stackTmp.isEmpty() == false)
                {
                    stack.grow(stackTmp.getCount());
                }
            }
            else if (useOreDict && areItemStacksOreDictMatch(stackTmp, stackTemplate))
            {
                // This is the first match, and since it's an OreDictionary match ie. different actual
                // item, we convert the stack to the matched item.
                if (stack.getCount() == 0)
                {
                    stack = stackTmp.copy();
                    stack.setCount(0);
                }

                stackTmp = extractItemsFromSlot(inv, slot, amount - stack.getCount());

                if (stackTmp.isEmpty() == false)
                {
                    stack.grow(stackTmp.getCount());
                }
            }
        }

        return stack.getCount() > 0 ? stack : ItemStack.EMPTY;
    }

    /**
     * Collects one stack of items that are identical to stackTemplate, and fills that stack as full as possible
     * first from invTarget and if it still isn't full, then also from invStorage. All the remaining items
     * in invTarget that are identical to stackTemplate will be moved to the other inventory, invStorage.
     */
    public static ItemStack collectOneStackAndMoveOthers(IItemHandler invTarget, IItemHandler invStorage, @Nonnull ItemStack stackTemplate)
    {
        final int maxStackSize = stackTemplate.getMaxStackSize();

        // Get our initial collected stack from the target inventory
        ItemStack stack = collectItemsFromInventory(invTarget, stackTemplate, maxStackSize, true);

        // Move all the remaining identical items to the storage inventory
        List<Integer> slots = getSlotNumbersOfMatchingStacks(invTarget, stackTemplate);

        for (int slot : slots)
        {
            ItemStack stackTmp;

            int limit = SLOT_ITER_LIMIT;

            while (limit-- > 0)
            {
                stackTmp = invTarget.extractItem(slot, maxStackSize, false);

                if (stackTmp.isEmpty())
                {
                    break;
                }

                stackTmp = tryInsertItemStackToInventory(invStorage, stackTmp);

                // Can't insert all of the items, return the rest
                if (stackTmp.isEmpty() == false)
                {
                    invTarget.insertItem(slot, stackTmp, false);
                    break;
                }
            }
        }

        // If the initial collected stack wasn't full, try to fill it from the storage inventory
        if (stack.isEmpty() == false && stack.getCount() < maxStackSize)
        {
            ItemStack stackTmp = collectItemsFromInventory(invStorage, stack, maxStackSize - stack.getCount(), true);

            if (stackTmp.isEmpty() == false)
            {
                stack.grow(stackTmp.getCount());
            }
        }

        return stack;
    }

    /**
     * Loops through the invTarget inventory and leaves one stack of every item type found
     * and moves the rest to invStorage. The stacks are also first collected from invTarget
     * and filled as full as possible and if it's still not full, then more items are moved from invStorage.
     * @param invTarget the target inventory that will be cleaned up and where the filled stacks are left in
     * @param invStorage the "external" inventory where the excess items are moved to
     * @param reverse set to true to start the looping from the end of invTarget and thus leave the last stack of each item
     */
    public static void leaveOneFullStackOfEveryItem(IItemHandler invTarget, IItemHandler invStorage, boolean reverse)
    {
        final int inc = (reverse ? -1 : 1);
        final int start = (reverse ? (invTarget.getSlots() - 1) : 0);
        final int invSize = invTarget.getSlots();

        for (int slot = start; slot >= 0 && slot < invSize; slot += inc)
        {
            ItemStack stack = invTarget.getStackInSlot(slot);

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

            int maxSize = stack.getMaxStackSize();

            // Get all slots that have this item
            List<Integer> matchingSlots = getSlotNumbersOfMatchingStacks(invTarget, stack);

            if (matchingSlots.size() > 1)
            {
                for (int tmp : matchingSlots)
                {
                    if (tmp == slot)
                    {
                        continue;
                    }

                    // Move items from the other slots to the first slot as long as they can fit
                    int limit = SLOT_ITER_LIMIT;

                    while (limit-- > 0)
                    {
                        stack = invTarget.extractItem(tmp, maxSize, false);

                        if (stack.isEmpty())
                        {
                            break;
                        }

                        stack = invTarget.insertItem(slot, stack, false);

                        if (stack.isEmpty() == false)
                        {
                            break;
                        }
                    }

                    // If there are items that didn't fit into the first slot, then move those to the other inventory
                    limit = SLOT_ITER_LIMIT;

                    while (stack.isEmpty() == false && limit-- > 0)
                    {
                        stack = tryInsertItemStackToInventory(invStorage, stack);

                        // Couldn't move more items to the invStorage inventory,
                        // return the remaining stack to the original inventory and abort
                        if (stack.isEmpty() == false)
                        {
                            tryInsertItemStackToInventory(invTarget, stack);
                            return;
                        }

                        stack = invTarget.extractItem(tmp, maxSize, false);
                    }
                }
            }

            // All matching stacks inside the invTarget handled, now check if we still
            // need to re-stock more items into the first stack from invStorage.
            stack = invTarget.getStackInSlot(slot);

            if (stack.isEmpty() == false)
            {
                maxSize = stack.getMaxStackSize();

                if (stack.getCount() < maxSize)
                {
                    ItemStack stackTmp = collectItemsFromInventory(invStorage, stack, maxSize - stack.getCount(), true);

                    if (stackTmp.isEmpty() == false)
                    {
                        stackTmp = invTarget.insertItem(slot, stackTmp, false);

                        // If some of them didn't fit into the first slot after all, then return those
                        if (stackTmp.isEmpty() == false)
                        {
                            tryInsertItemStackToInventory(invStorage, stackTmp);
                        }
                    }
                }
            }
        }
    }

    /**
     * Checks if there is a matching ItemStack in the inventory inside the given slot range.
     */
    public static boolean matchingStackFoundInSlotRange(IItemHandler inv, SlotRange slotRange,
            @Nonnull ItemStack stackTemplate, boolean ignoreMeta, boolean ignoreNbt)
    {
        Item item = stackTemplate.getItem();
        int meta = stackTemplate.getMetadata();
        final int lastSlot = Math.min(slotRange.lastInc, inv.getSlots() - 1);

        for (int slot = slotRange.first; slot <= lastSlot; slot++)
        {
            ItemStack stackTmp = inv.getStackInSlot(slot);

            if (stackTmp.isEmpty() || stackTmp.getItem() != item)
            {
                continue;
            }

            if (ignoreMeta == false && (meta != OreDictionary.WILDCARD_VALUE && stackTmp.getMetadata() != meta))
            {
                continue;
            }

            if (ignoreNbt == false && ItemStack.areItemStackTagsEqual(stackTemplate, stackTmp) == false)
            {
                continue;
            }

            return true;
        }

        return false;
    }

    /**
     * Checks if there is a matching ItemStack in the provided array of stacks
     */
    public static boolean matchingStackFoundOnList(NonNullList<ItemStack> list,
            @Nonnull ItemStack stackTemplate, boolean ignoreMeta, boolean ignoreNbt)
    {
        Item item = stackTemplate.getItem();
        int meta = stackTemplate.getMetadata();
        final int size = list.size();

        for (int i = 0; i < size; i++)
        {
            ItemStack stackTmp = list.get(i);

            if (stackTmp.isEmpty() || stackTmp.getItem() != item)
            {
                continue;
            }

            if (ignoreMeta == false && (meta != OreDictionary.WILDCARD_VALUE && stackTmp.getMetadata() != meta))
            {
                continue;
            }

            if (ignoreNbt == false && ItemStack.areItemStackTagsEqual(stackTemplate, stackTmp) == false)
            {
                continue;
            }

            return true;
        }

        return false;
    }

    /**
     * @param inv
     * @return true if all the slots in the inventory are empty
     */
    public static boolean isInventoryEmpty(IItemHandler inv)
    {
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            if (inv.getStackInSlot(slot).isEmpty() == false)
            {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns the largest existing stack size from the inventory <b>inv</b>.
     * @param inv
     * @return largest existing stack size from the inventory, or -1 if all stacks are empty
     */
    public static int getLargestExistingStackSize(IItemHandler inv)
    {
        int largestSize = -1;
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && stack.getCount() > largestSize)
            {
                largestSize = stack.getCount();
            }
        }

        return largestSize;
    }

    /**
     * Returns the minimum stack size from the inventory <b>inv</b> from
     * stacks that are not empty, or -1 if all stacks are empty.
     * @param inv
     * @return minimum stack size from the inventory, or -1 if all stacks are empty
     */
    public static int getMinNonEmptyStackSize(IItemHandler inv)
    {
        int minSize = -1;
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stack = inv.getStackInSlot(slot);

            if (stack.isEmpty() == false && (stack.getCount() < minSize || minSize < 0))
            {
                minSize = stack.getCount();
            }
        }

        return minSize;
    }

    /**
     * Counts the number of items in the inventory <b>inv</b> that are identical to <b>stackTemplate</b>.
     * If <b>useOreDict</b> is true, then Ore Dictionary matches are also accepted.
     */
    public static int getNumberOfMatchingItemsInInventory(IItemHandler inv, @Nonnull ItemStack stackTemplate, boolean useOreDict)
    {
        int found = 0;
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stackTmp = inv.getStackInSlot(slot);

            if (stackTmp.isEmpty() == false)
            {
                if (areItemStacksEqual(stackTmp, stackTemplate) ||
                    (useOreDict && areItemStacksOreDictMatch(stackTmp, stackTemplate)))
                {
                    found += stackTmp.getCount();
                }
            }
        }

        return found;
    }

    /**
     * Checks if the given inventory <b>inv</b> has at least <b>amount</b> number of items
     * matching the item in <b>stackTemplate</b>.
     * If useOreDict is true, then any matches via OreDictionary are also accepted.
     */
    public static boolean checkInventoryHasItems(IItemHandler inv, @Nonnull ItemStack stackTemplate, int amount, boolean useOreDict)
    {
        int found = 0;
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stackTmp = inv.getStackInSlot(slot);

            if (stackTmp.isEmpty() == false)
            {
                if (areItemStacksEqual(stackTmp, stackTemplate) ||
                    (useOreDict && areItemStacksOreDictMatch(stackTmp, stackTemplate)))
                {
                    found += stackTmp.getCount();
                }
            }

            if (found >= amount)
            {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks if the inventory <b>invStorage</b> has all the items from the other inventory <b>invTemplate</b>
     * in at least the amountPerStack quantity per each stack from the template inventory.
     * If useOreDict is true, then any matches via OreDictionary are also accepted.
     */
    public static boolean checkInventoryHasAllItems(IItemHandler invStorage, IItemHandler invTemplate, int amountPerStack, boolean useOreDict)
    {
        Map<ItemType, Integer> quantities = new HashMap<ItemType, Integer>();
        final int invSize = invTemplate.getSlots();

        // First get the sum of all the items required based on the template inventory
        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stackTmp = invTemplate.getStackInSlot(slot);

            if (stackTmp.isEmpty() == false)
            {
                ItemType item = new ItemType(stackTmp);
                Integer amount = quantities.get(item);
                amount = (amount != null) ? amount + amountPerStack : amountPerStack;
                quantities.put(item, Integer.valueOf(amount));
            }
        }

        // Then check if the storage inventory has the required amount of each of those items
        Set<ItemType> items = quantities.keySet();

        for (ItemType item : items)
        {
            Integer amount = quantities.get(item);

            if (amount != null)
            {
                if (checkInventoryHasItems(invStorage, item.getStack(), amount, useOreDict) == false)
                {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Returns a map of how many slots contain the same item, for each item found in the inventory.
     */
    public static Map<ItemType, Integer> getSlotCountPerItem(IItemHandler inv)
    {
        Map<ItemType, Integer> slots = new HashMap<ItemType, Integer>();
        final int invSize = inv.getSlots();

        for (int slot = 0; slot < invSize; slot++)
        {
            ItemStack stackTmp = inv.getStackInSlot(slot);

            if (stackTmp.isEmpty() == false)
            {
                ItemType item = new ItemType(stackTmp);
                Integer count = slots.get(item);
                count = (count != null) ? count + 1 : 1;
                slots.put(item, Integer.valueOf(count));
            }
        }

        return slots;
    }

    /**
     * Creates a copy of the whole inventory and returns it in a new NonNullList.
     * @param inv
     * @return a NonNullList containing a copy of the entire inventory
     */
    public static NonNullList<ItemStack> createInventorySnapshot(IItemHandler inv)
    {
        final int invSize = inv.getSlots();
        NonNullList<ItemStack> items = NonNullList.withSize(invSize, ItemStack.EMPTY);

        for (int i = 0; i < invSize; i++)
        {
            ItemStack stack = inv.getStackInSlot(i);

            if (stack.isEmpty() == false)
            {
                items.set(i, stack.copy());
            }
        }

        return items;
    }

    /**
     * Creates a copy of the non-empty stacks in the inventory and returns it in a new NonNullList.
     * @param inv
     * @return a NonNullList containing copies of the non-empty stacks
     */
    public static NonNullList<ItemStack> createInventorySnapshotOfNonEmptySlots(IItemHandler inv)
    {
        final int invSize = inv.getSlots();
        NonNullList<ItemStack> items = NonNullList.create();

        for (int i = 0; i < invSize; i++)
        {
            ItemStack stack = inv.getStackInSlot(i);

            if (stack.isEmpty() == false)
            {
                items.add(stack.copy());
            }
        }

        return items;
    }

    /**
     * If there are items in the invTarget inventory that do not match the template list,
     * then those are attempted to move to invStorage
     * @param invTarget
     * @param invStorage
     * @param template
     */
    public static void clearInventoryToMatchTemplate(IItemHandler invTarget, IItemHandler invStorage, NonNullList<ItemStack> template)
    {
        SlotRange rangeStorage = new SlotRange(invStorage);
        final int slots = Math.min(invTarget.getSlots(), template.size());

        for (int i = 0; i < slots; i++)
        {
            if (areItemStacksEqual(template.get(i), invTarget.getStackInSlot(i)) == false)
            {
                tryMoveAllItemsWithinSlotRange(invTarget, invStorage, new SlotRange(i, 1), rangeStorage);
            }
        }
    }

    /**
     * Adds amountPerStack items to all the stacks in invTarget based on the template inventory in <b>template</b>.
     * If the existing stack doesn't match the template, then nothing will be added to that stack.
     * If the existing stack is empty, then it will be set to a new stack based on the template.
     * All the items are taken from the inventory <b>invStorage</b>.
     * If emptySlotsOnly is true, then only slots that are empty in the target inventory will be re-stocked.
     * If useOreDict is true, then any matches via OreDictionary are also accepted.
     * @param invTarget
     * @param invStorage
     * @param template
     * @param amountPerStack
     * @param emptySlotsOnly
     * @param useOreDict
     * @return true if ALL the items from the template inventory and in the quantity amountPerStack were successfully added
     */
    public static boolean restockInventoryBasedOnTemplate(IItemHandler invTarget, IItemHandler invStorage,
            NonNullList<ItemStack> template, int amountPerStack, boolean emptySlotsOnly, boolean useOreDict)
    {
        final int slots = Math.min(invTarget.getSlots(), template.size());
        int amount = 0;
        boolean allSuccess = true;

        for (int i = 0; i < slots; i++)
        {
            ItemStack stackTemplate = template.get(i);

            if (stackTemplate.isEmpty())
            {
                continue;
            }

            ItemStack stackExisting = invTarget.getStackInSlot(i);

            if (emptySlotsOnly && stackExisting.isEmpty() == false)
            {
                continue;
            }

            amount = Math.min(amountPerStack, stackTemplate.getMaxStackSize());

            // The existing stack doesn't match the template, skip it
            if (stackExisting.isEmpty() == false)
            {
                if ((useOreDict == false && areItemStacksEqual(stackExisting, stackTemplate) == false) ||
                    (useOreDict && areItemStacksOreDictMatch(stackExisting, stackTemplate) == false))
                {
                    allSuccess = false;
                    continue;
                }

                amount = Math.max(amount - stackExisting.getCount(), 0);
            }

            if (amount <= 0)
            {
                allSuccess = false;
                continue;
            }

            ItemStack stackNew = collectItemsFromInventory(invStorage, stackTemplate, amount, false, useOreDict);

            if (stackNew.isEmpty())
            {
                allSuccess = false;
                continue;
            }

            if (stackNew.getCount() < amount)
            {
                allSuccess = false;
            }

            // Used oreDict matches to collect the items, and they are not identical to the existing items
            // => we need to convert the new items to the existing item's type before they can be inserted
            if (useOreDict && stackExisting.isEmpty() == false && areItemStacksEqual(stackExisting, stackNew) == false)
            {
                int size = stackNew.getCount();
                stackNew = stackExisting.copy();
                stackNew.setCount(size);
            }

            // Try to insert the collected stack to the target slot
            stackNew = invTarget.insertItem(i, stackNew, false);

            // Failed to insert all the items, return them to the original inventory
            if (stackNew.isEmpty() == false)
            {
                tryInsertItemStackToInventory(invStorage, stackNew);
            }
        }

        return allSuccess;
    }

    public static void sortInventoryWithinRange(IItemHandlerModifiable inv, SlotRange range)
    {
        List<ItemTypeByName> blocks = new ArrayList<ItemTypeByName>();
        List<ItemTypeByName> items = new ArrayList<ItemTypeByName>();
        final int lastSlot = Math.min(range.lastInc, inv.getSlots() - 1);

        // Get the different items that are present in the inventory
        for (int i = range.first; i <= lastSlot; i++)
        {
            ItemStack stack = inv.getStackInSlot(i);

            if (stack.isEmpty() == false)
            {
                ItemTypeByName type = new ItemTypeByName(stack);

                if (stack.getItem() instanceof ItemBlock)
                {
                    if (blocks.contains(type) == false)
                    {
                        blocks.add(type);
                    }
                }
                else if (items.contains(type) == false)
                {
                    items.add(type);
                }
            }
        }

        Collections.sort(blocks);
        Collections.sort(items);

        int slots = sortInventoryWithinRangeByTypes(inv, blocks, range);
        sortInventoryWithinRangeByTypes(inv, items, new SlotRange(range.first + slots, range.lastExc - (range.first + slots)));
    }

    private static int sortInventoryWithinRangeByTypes(IItemHandlerModifiable inv, List<ItemTypeByName> types, SlotRange range)
    {
        int slot = range.first;
        int slots = 0;

        for (ItemTypeByName type : types)
        {
            ItemStack stack = type.getStack();

            while (true)
            {
                final int max = inv instanceof IItemHandlerSize ? ((IItemHandlerSize) inv).getItemStackLimit(slot, stack) : Math.min(inv.getSlotLimit(slot), stack.getMaxStackSize());
                //System.out.printf("sorting for: %s - slot: %d, max: %d\n", stack.toString(), slot, max);

                if (slot >= range.lastInc)
                {
                    //System.out.printf("slot >= range.lastInc\n");
                    return slots;
                }

                SlotRange rangeTmp = new SlotRange(slot, range.lastExc - slot);
                stack = collectItemsFromInventoryFromSlotRange(inv, stack, rangeTmp, max, false, false);
                //System.out.printf("collected stack: %s from range: %s\n", stack, rangeTmp.toString());

                if (stack.isEmpty())
                {
                    break;
                }

                ItemStack stackTmp = inv.getStackInSlot(slot);

                // There is a stack in the slot that we are moving items to, try to move the stack towards the end of the inventory
                if (stackTmp.isEmpty() == false)
                {
                    //System.out.printf("existing stack: %s\n", inv.getStackInSlot(slot).toString());
                    rangeTmp = new SlotRange(slot + 1, range.lastExc - (slot + 1));
                    stackTmp = tryInsertItemStackToInventoryWithinSlotRange(inv, stackTmp, rangeTmp);
                    //System.out.printf("tried moving stack to range: %s - remaining: %s\n", rangeTmp.toString(), stackTmp);

                    // Failed to move the stack - this shouldn't happen, we are in trouble now!
                    if (stackTmp.isEmpty() == false)
                    {
                        //System.out.printf("failed moving existing stack, panic mode!\n");
                        // Try to return all the items currently being worked on and then bail out
                        tryInsertItemStackToInventoryWithinSlotRange(inv, stackTmp, range);
                        tryInsertItemStackToInventoryWithinSlotRange(inv, stack, range);
                        return slots;
                    }
                }

                //System.out.printf("setting stack: %s to slot: %d - slots: %d\n", stack, slot, slots + 1);
                // Put the stack (collected starting from this slot towards the end of the inventory) into this slot
                inv.setStackInSlot(slot, stack);

                /*if (inv instanceof IItemHandlerModifiable)
                {
                    ((IItemHandlerModifiable)inv).setStackInSlot(slot, stack);
                }
                else
                {
                    tryToEmptySlot(inv, slots, 128);
                    inv.insertItem(slot, stack, false);
                }*/

                slot++;
                slots++;
            }
        }

        return slots;
    }

    /**
     * Tries to empty out the given slot by repeatedly calling extractItem() on it and just ignoring the items
     */
    public static boolean tryToEmptySlot(IItemHandler inv, int slot, int maxIterations)
    {
        for (int i = 0; i < maxIterations && inv.getStackInSlot(slot).isEmpty() == false; i++)
        {
            inv.extractItem(slot, 1048576, false); // 1M because why not :p
        }

        return inv.getStackInSlot(slot).isEmpty();
    }

    public static class ItemTypeByName extends ItemType implements Comparable<ItemTypeByName>
    {
        public ItemTypeByName(ItemStack stack)
        {
            super(stack);
        }

        @Override
        public int compareTo(ItemTypeByName other)
        {
            if (other == null)
            {
                throw new NullPointerException();
            }

            String name1 = this.getStack().getItem().getRegistryName().toString();
            String name2 = other.getStack().getItem().getRegistryName().toString();
            int comp = name1.compareToIgnoreCase(name2);

            if (comp != 0)
            {
                return comp;
            }

            int meta1 = this.getStack().getMetadata();
            int meta2 = other.getStack().getMetadata();

            if (meta1 != meta2)
            {
                return meta1 < meta2 ? -1 : 1;
            }

            return 0;
        }
    }

    public static enum InvResult
    {
        MOVED_NOTHING,
        MOVED_SOME,
        MOVED_ALL;
    }
}