package com.minemaarten.advancedmod.tileentity;

import io.netty.buffer.ByteBuf;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;

import com.minemaarten.advancedmod.init.ModBlocks;
import com.minemaarten.advancedmod.utility.Log;

public class TileEntityModularStorage extends TileEntityAdvancedMod implements IInventory{

    private static final int SLOTS_PER_BLOCK = 1;
    private ItemStack[] inventory = new ItemStack[SLOTS_PER_BLOCK];

    private TileEntityModularStorage master;
    private boolean isMaster;
    private boolean firstRun = true;

    public boolean isMaster(){
        return isMaster;
    }

    public TileEntityModularStorage getMaster(){
        initializeMultiblockIfNecessary();
        return master;
    }

    private void setMaster(TileEntityModularStorage master, int storages){
        this.master = master;
        boolean wasMaster = isMaster;
        isMaster = master == this;
        if(isMaster) {
            Log.info("Master set to " + storages + " blocks");
            ItemStack[] newInventory = new ItemStack[SLOTS_PER_BLOCK * storages];
            for(int i = 0; i < inventory.length; i++) {
                if(i < newInventory.length) {
                    newInventory[i] = inventory[i];
                } else if(inventory[i] != null) {
                    worldObj.spawnEntityInWorld(new EntityItem(worldObj, xCoord, yCoord, zCoord, inventory[i]));
                }
            }
            inventory = newInventory;
        } else if(!isMaster && wasMaster) {
            for(ItemStack stack : inventory) {
                if(stack != null) worldObj.spawnEntityInWorld(new EntityItem(worldObj, xCoord, yCoord, zCoord, stack));
            }
        }
    }

    @Override
    public void updateEntity(){
        super.updateEntity();
        if(firstRun) {
            initializeMultiblockIfNecessary();
            firstRun = false;
        }
    }

    @Override
    public void invalidate(){
        super.invalidate();
        for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
            TileEntity te = worldObj.getTileEntity(xCoord + d.offsetX, yCoord + d.offsetY, zCoord + d.offsetZ);
            if(te instanceof TileEntityModularStorage) {
                ((TileEntityModularStorage)te).master = null;
                ((TileEntityModularStorage)te).initializeMultiblockIfNecessary();
            }
        }
        for(ItemStack stack : inventory) {
            if(stack != null) worldObj.spawnEntityInWorld(new EntityItem(worldObj, xCoord, yCoord, zCoord, stack));
        }
    }

    private void initializeMultiblockIfNecessary(){
        if(master == null || master.isInvalid()) {
            List<TileEntityModularStorage> connectedStorages = new ArrayList<TileEntityModularStorage>();
            Stack<TileEntityModularStorage> traversingStorages = new Stack<TileEntityModularStorage>();
            TileEntityModularStorage master = this;
            traversingStorages.add(this);
            while(!traversingStorages.isEmpty()) {
                TileEntityModularStorage storage = traversingStorages.pop();
                if(storage.isMaster()) {
                    master = storage;
                }
                connectedStorages.add(storage);
                for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
                    TileEntity te = worldObj.getTileEntity(storage.xCoord + d.offsetX, storage.yCoord + d.offsetY, storage.zCoord + d.offsetZ);
                    if(te instanceof TileEntityModularStorage && !connectedStorages.contains(te)) {
                        traversingStorages.add((TileEntityModularStorage)te);
                    }
                }
            }
            Log.info("Setting master to " + master.xCoord + ", " + master.yCoord + ", " + master.zCoord + " for " + connectedStorages.size() + " blocks");
            for(TileEntityModularStorage storage : connectedStorages) {
                storage.setMaster(master, connectedStorages.size());
            }
        }
    }

    @Override
    public void onGuiButtonPress(int id){

    }

    @Override
    public void writeToPacket(ByteBuf buf){

    }

    @Override
    public void readFromPacket(ByteBuf buf){

        //   worldObj.markBlockRangeForRenderUpdate(xCoord, yCoord, zCoord, xCoord, yCoord, zCoord);
    }

    @Override
    public void readFromNBT(NBTTagCompound tag){
        super.readFromNBT(tag);

        isMaster = tag.getBoolean("isMaster");

        inventory = new ItemStack[tag.getInteger("slots")];
        NBTTagList camoStackTag = tag.getTagList("inventory", 10);

        for(int i = 0; i < camoStackTag.tagCount(); i++) {
            NBTTagCompound t = camoStackTag.getCompoundTagAt(i);
            int index = t.getByte("index");
            if(index >= 0 && index < inventory.length) {
                inventory[index] = ItemStack.loadItemStackFromNBT(t);
            }
        }
    }

    @Override
    public void writeToNBT(NBTTagCompound tag){
        super.writeToNBT(tag);

        tag.setBoolean("isMaster", isMaster);

        tag.setInteger("slots", inventory.length);
        NBTTagList camoStackTag = new NBTTagList();
        for(int i = 0; i < inventory.length; i++) {
            ItemStack stack = inventory[i];
            if(stack != null) {
                NBTTagCompound t = new NBTTagCompound();
                stack.writeToNBT(t);
                t.setByte("index", (byte)i);
                camoStackTag.appendTag(t);
            }
        }
        tag.setTag("inventory", camoStackTag);
    }

    /**
     * Returns the number of slots in the inventory.
     */
    @Override
    public int getSizeInventory(){
        return isMaster() ? inventory.length : getMaster().getSizeInventory();
    }

    /**
     * Returns the stack in slot i
     */
    @Override
    public ItemStack getStackInSlot(int slot){
        return isMaster ? inventory[slot] : getMaster().getStackInSlot(slot);
    }

    /**
     * Removes from an inventory slot (first arg) up to a specified number (second arg) of items and returns them in a
     * new stack.
     */
    @Override
    public ItemStack decrStackSize(int slot, int decreaseAmount){
        if(isMaster()) {
            if(inventory[slot] != null) {
                ItemStack itemstack;

                if(inventory[slot].stackSize <= decreaseAmount) {
                    itemstack = inventory[slot];
                    setInventorySlotContents(slot, null);
                    markDirty();
                    return itemstack;
                } else {
                    itemstack = inventory[slot].splitStack(decreaseAmount);

                    if(inventory[slot].stackSize == 0) {
                        setInventorySlotContents(slot, null);
                    }

                    markDirty();
                    return itemstack;
                }
            } else {
                return null;
            }
        } else {
            return getMaster().decrStackSize(slot, decreaseAmount);
        }
    }

    /**
     * When some containers are closed they call this on each slot, then drop whatever it returns as an EntityItem -
     * like when you close a workbench GUI.
     */
    @Override
    public ItemStack getStackInSlotOnClosing(int slot){
        if(isMaster()) {
            if(inventory[slot] != null) {
                ItemStack itemstack = inventory[slot];
                inventory[slot] = null;
                return itemstack;
            } else {
                return null;
            }
        } else {
            return getMaster().getStackInSlotOnClosing(slot);
        }
    }

    /**
     * Sets the given item stack to the specified slot in the inventory (can be crafting or armor sections).
     */
    @Override
    public void setInventorySlotContents(int slot, ItemStack stack){
        if(isMaster()) {
            inventory[slot] = stack;

            if(stack != null && stack.stackSize > getInventoryStackLimit()) {
                stack.stackSize = getInventoryStackLimit();
            }

            markDirty();
        } else {
            getMaster().setInventorySlotContents(slot, stack);
        }
    }

    /**
     * Returns the name of the inventory
     */
    @Override
    public String getInventoryName(){
        return ModBlocks.modularStorage.getUnlocalizedName() + ".name";
    }

    /**
     * Returns if the inventory is named
     */
    @Override
    public boolean hasCustomInventoryName(){
        return false;
    }

    /**
     * Returns the maximum stack size for a inventory slot.
     */
    @Override
    public int getInventoryStackLimit(){
        return 64;
    }

    /**
     * Do not make give this method the name canInteractWith because it clashes with Container
     */
    @Override
    public boolean isUseableByPlayer(EntityPlayer player){
        return worldObj.getTileEntity(xCoord, yCoord, zCoord) != this ? false : player.getDistanceSq(xCoord + 0.5D, yCoord + 0.5D, zCoord + 0.5D) <= 64.0D;
    }

    @Override
    public void openInventory(){}

    @Override
    public void closeInventory(){}

    /**
     * Returns true if automation is allowed to insert the given stack (ignoring stack size) into the given slot.
     */
    @Override
    public boolean isItemValidForSlot(int slot, ItemStack stack){
        return true;
    }

}