package pneumaticCraft.common.util;

import net.minecraft.block.Block;
import net.minecraft.block.BlockChest;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;

/*
 * This file is part of Blue Power.
 *
 *     Blue Power is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Blue Power is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Blue Power.  If not, see <http://www.gnu.org/licenses/>
 */

/**
 * @author MineMaarten
 * @author Dynious
 */
public class IOHelper{

    public static IInventory getInventoryForTE(TileEntity te){

        if(te instanceof IInventory) {
            IInventory inv = (IInventory)te;
            Block block = te.getBlockType();
            if(block instanceof BlockChest) {
                inv = ((BlockChest)block).func_149951_m(te.getWorldObj(), te.xCoord, te.yCoord, te.zCoord);
            }
            return inv;
        } else {
            return null;
        }
    }

    public static TileEntity getNeighbor(TileEntity te, ForgeDirection dir){
        return te.getWorldObj().getTileEntity(te.xCoord + dir.offsetX, te.yCoord + dir.offsetY, te.zCoord + dir.offsetZ);
    }

    /**
     * Extracts an exact amount on all sides
     * @param tile
     * @param itemStack
     * @param simulate
     * @return
     */
    public static ItemStack extract(TileEntity tile, ItemStack itemStack, boolean simulate){
        ItemStack extracted = null;
        for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
            extracted = extract(tile, d, itemStack, true, simulate);
            if(extracted != null) return extracted;
        }
        return null;
    }

    public static ItemStack extract(TileEntity inventory, ForgeDirection direction, boolean simulate){

        IInventory inv = getInventoryForTE(inventory);
        if(inv != null) return extract(inv, direction, simulate);
        return null;
    }

    public static ItemStack extract(IInventory inventory, ForgeDirection direction, boolean simulate){

        if(inventory instanceof ISidedInventory) {
            ISidedInventory isidedinventory = (ISidedInventory)inventory;
            int[] accessibleSlotsFromSide = isidedinventory.getAccessibleSlotsFromSide(direction.ordinal());

            for(int anAccessibleSlotsFromSide : accessibleSlotsFromSide) {
                ItemStack stack = extract(inventory, direction, anAccessibleSlotsFromSide, simulate);
                if(stack != null) return stack;
            }
        } else {
            int j = inventory.getSizeInventory();

            for(int k = 0; k < j; ++k) {
                ItemStack stack = extract(inventory, direction, k, simulate);
                if(stack != null) return stack;
            }
        }
        return null;
    }

    public static ItemStack extract(IInventory inventory, ForgeDirection direction, int slot, boolean simulate){

        ItemStack itemstack = inventory.getStackInSlot(slot);

        if(itemstack != null && canExtractItemFromInventory(inventory, itemstack, slot, direction.ordinal())) {
            if(!simulate) inventory.setInventorySlotContents(slot, null);
            return itemstack;
        }
        return null;
    }

    public static ItemStack extract(TileEntity tile, ForgeDirection direction, ItemStack requestedStack, boolean useItemCount, boolean simulate){

        return extract(tile, direction, requestedStack, useItemCount, simulate, 0);
    }

    public static int[] getAccessibleSlotsForInventory(IInventory inv, ForgeDirection side){

        int[] accessibleSlots;
        if(inv != null) {
            if(inv instanceof ISidedInventory) {
                accessibleSlots = ((ISidedInventory)inv).getAccessibleSlotsFromSide(side.ordinal());
            } else {
                accessibleSlots = new int[inv.getSizeInventory()];
                for(int i = 0; i < accessibleSlots.length; i++)
                    accessibleSlots[i] = i;
            }
            return accessibleSlots;
        } else {
            return new int[0];
        }
    }

    /**
     * Retrieves an item from the specified inventory. This item can be specified.
     * 
     * @param tile
     * @param direction
     * @param requestedStack
     * @param useItemCount
     *            if true, it'll only retrieve the stack of the exact item count given. it'll look in multiple slots of the inventory. if false, the
     *            first matching stack, ignoring item count, will be returned.
     * @param simulate
     * @param fuzzySetting
     *            ,
     * @return
     */
    public static ItemStack extract(TileEntity tile, ForgeDirection direction, ItemStack requestedStack, boolean useItemCount, boolean simulate, int fuzzySetting){

        if(requestedStack == null) return requestedStack;
        IInventory inv = getInventoryForTE(tile);
        if(inv != null) {
            int[] accessibleSlots;
            if(inv instanceof ISidedInventory) {
                accessibleSlots = ((ISidedInventory)inv).getAccessibleSlotsFromSide(direction.ordinal());
            } else {
                accessibleSlots = new int[inv.getSizeInventory()];
                for(int i = 0; i < accessibleSlots.length; i++)
                    accessibleSlots[i] = i;
            }
            int itemsFound = 0;
            for(int slot : accessibleSlots) {
                ItemStack stack = inv.getStackInSlot(slot);
                if(stack != null && stack.stackSize > 0 && stack.isItemEqual(requestedStack) && IOHelper.canExtractItemFromInventory(inv, stack, slot, direction.ordinal())) {
                    if(!useItemCount) {
                        if(!simulate) {
                            inv.setInventorySlotContents(slot, null);
                        }
                        return stack;
                    }
                    itemsFound += stack.stackSize;
                }
            }
            if(itemsFound >= requestedStack.stackSize) {
                ItemStack exportedStack = null;
                int itemsNeeded = requestedStack.stackSize;
                for(int slot : accessibleSlots) {
                    ItemStack stack = inv.getStackInSlot(slot);
                    if(stack != null && stack.isItemEqual(requestedStack) && IOHelper.canExtractItemFromInventory(inv, stack, slot, direction.ordinal())) {
                        int itemsSubstracted = Math.min(itemsNeeded, stack.stackSize);
                        if(itemsSubstracted > 0) exportedStack = stack;
                        itemsNeeded -= itemsSubstracted;
                        if(!simulate) {
                            stack.stackSize -= itemsSubstracted;
                            if(stack.stackSize == 0) {
                                inv.setInventorySlotContents(slot, null);
                            }
                            tile.markDirty();
                        }
                    }
                }
                exportedStack = exportedStack.copy();
                exportedStack.stackSize = requestedStack.stackSize;
                return exportedStack;
            }
        }
        return null;

    }

    public static ItemStack extractOneItem(TileEntity tile, ForgeDirection dir, boolean simulate){

        IInventory inv = getInventoryForTE(tile);
        if(inv != null) {
            int[] accessibleSlots;
            if(inv instanceof ISidedInventory) {
                accessibleSlots = ((ISidedInventory)inv).getAccessibleSlotsFromSide(dir.ordinal());
            } else {
                accessibleSlots = new int[inv.getSizeInventory()];
                for(int i = 0; i < accessibleSlots.length; i++)
                    accessibleSlots[i] = i;
            }
            for(int slot : accessibleSlots) {
                ItemStack stack = inv.getStackInSlot(slot);
                if(stack != null && stack.stackSize > 0 && IOHelper.canExtractItemFromInventory(inv, stack, slot, dir.ordinal())) {
                    if(simulate) {
                        ItemStack ret = stack.copy();
                        ret.stackSize = 1;
                        return ret;
                    }
                    ItemStack ret = stack.splitStack(1);
                    if(stack.stackSize == 0) inv.setInventorySlotContents(slot, null);
                    tile.markDirty();
                    return ret;
                }
            }
        }
        return null;
    }

    /**
     * Inserts on all sides
     * @param tile
     * @param itemStack
     * @param simulate
     * @return
     */
    public static ItemStack insert(TileEntity tile, ItemStack itemStack, boolean simulate){
        IInventory inv = getInventoryForTE(tile);
        ItemStack insertingStack = itemStack.copy();
        for(int i = 0; i < 6; i++) {
            insertingStack = insert(inv, insertingStack, i, simulate);
            if(insertingStack == null || insertingStack.stackSize != itemStack.stackSize) return insertingStack;
        }
        return insertingStack;
    }

    public static ItemStack insert(TileEntity tile, ItemStack itemStack, ForgeDirection direction, boolean simulate){

        IInventory inv = getInventoryForTE(tile);
        if(inv != null) return insert(inv, itemStack, direction.ordinal(), simulate);
        return itemStack;
    }

    public static ItemStack insert(IInventory inventory, ItemStack itemStack, int side, boolean simulate){

        if(inventory instanceof ISidedInventory && side > -1) {
            ISidedInventory isidedinventory = (ISidedInventory)inventory;
            int[] aint = isidedinventory.getAccessibleSlotsFromSide(side);

            for(int j = 0; j < aint.length && itemStack != null && itemStack.stackSize > 0; ++j) {
                itemStack = insert(inventory, itemStack, aint[j], side, simulate);
            }
        } else if(inventory != null) {
            int k = inventory.getSizeInventory();

            for(int l = 0; l < k && itemStack != null && itemStack.stackSize > 0; ++l) {
                itemStack = insert(inventory, itemStack, l, side, simulate);
            }
        }

        if(itemStack != null && itemStack.stackSize == 0) {
            itemStack = null;
        }

        return itemStack;
    }

    public static ItemStack insert(IInventory inventory, ItemStack itemStack, int slot, int side, boolean simulate){

        ItemStack itemstack1 = inventory.getStackInSlot(slot);

        if(canInsertItemToInventory(inventory, itemStack, slot, side)) {
            boolean flag = false;

            if(itemstack1 == null) {
                int max = Math.min(itemStack.getMaxStackSize(), inventory.getInventoryStackLimit());
                if(max >= itemStack.stackSize) {
                    if(!simulate) {
                        inventory.setInventorySlotContents(slot, itemStack);
                        flag = true;
                    }
                    itemStack = null;
                } else {
                    if(!simulate) {
                        inventory.setInventorySlotContents(slot, itemStack.splitStack(max));
                        flag = true;
                    } else {
                        itemStack.splitStack(max);
                    }
                }
            } else if(itemstack1.isItemEqual(itemStack) && ItemStack.areItemStackTagsEqual(itemstack1, itemStack)) {
                int max = Math.min(itemStack.getMaxStackSize(), inventory.getInventoryStackLimit());
                if(max > itemstack1.stackSize) {
                    int l = Math.min(itemStack.stackSize, max - itemstack1.stackSize);
                    itemStack.stackSize -= l;
                    if(!simulate) {
                        itemstack1.stackSize += l;
                        flag = l > 0;
                    }
                }
            }
            if(flag) {
                inventory.markDirty();
            }
        }

        return itemStack;
    }

    public static boolean canInsertItemToInventory(IInventory inventory, ItemStack itemStack, int slot, boolean[] sides){
        for(int i = 0; i < sides.length; i++) {
            if(sides[i] && canInsertItemToInventory(inventory, itemStack, slot, i)) return true;
        }
        return false;
    }

    public static boolean canExtractItemFromInventory(IInventory inventory, ItemStack itemStack, int slot, boolean[] sides){
        for(int i = 0; i < sides.length; i++) {
            if(sides[i] && canExtractItemFromInventory(inventory, itemStack, slot, i)) return true;
        }
        return false;
    }

    public static boolean canInsertItemToInventory(IInventory inventory, ItemStack itemStack, int slot, int side){

        return inventory.isItemValidForSlot(slot, itemStack) && (!(inventory instanceof ISidedInventory) || ((ISidedInventory)inventory).canInsertItem(slot, itemStack, side));
    }

    public static boolean canExtractItemFromInventory(IInventory inventory, ItemStack itemStack, int slot, int side){

        return !(inventory instanceof ISidedInventory) || ((ISidedInventory)inventory).canExtractItem(slot, itemStack, side);
    }

    public static void dropInventory(World world, int x, int y, int z){

        TileEntity tileEntity = world.getTileEntity(x, y, z);

        if(!(tileEntity instanceof IInventory)) {
            return;
        }

        IInventory inventory = (IInventory)tileEntity;

        for(int i = 0; i < inventory.getSizeInventory(); i++) {
            ItemStack itemStack = inventory.getStackInSlot(i);

            if(itemStack != null && itemStack.stackSize > 0) {
                spawnItemInWorld(world, itemStack, x, y, z);
            }
        }
    }

    public static void spawnItemInWorld(World world, ItemStack itemStack, double x, double y, double z){

        if(world.isRemote) return;
        float dX = world.rand.nextFloat() * 0.8F + 0.1F;
        float dY = world.rand.nextFloat() * 0.8F + 0.1F;
        float dZ = world.rand.nextFloat() * 0.8F + 0.1F;

        EntityItem entityItem = new EntityItem(world, x + dX, y + dY, z + dZ, new ItemStack(itemStack.getItem(), itemStack.stackSize, itemStack.getItemDamage()));

        if(itemStack.hasTagCompound()) {
            entityItem.getEntityItem().setTagCompound((NBTTagCompound)itemStack.getTagCompound().copy());
        }

        float factor = 0.05F;
        entityItem.motionX = world.rand.nextGaussian() * factor;
        entityItem.motionY = world.rand.nextGaussian() * factor + 0.2F;
        entityItem.motionZ = world.rand.nextGaussian() * factor;
        world.spawnEntityInWorld(entityItem);
        itemStack.stackSize = 0;
    }

    public static boolean canInterfaceWith(TileEntity tile, ForgeDirection direction){
        if(tile instanceof IInventory) {
            return !(tile instanceof ISidedInventory) || ((ISidedInventory)tile).getAccessibleSlotsFromSide(direction.ordinal()).length > 0;
        }
        return false;
    }
}