package ihl.metallurgy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.commons.lang3.mutable.MutableObject;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.RegistryNamespaced;
import net.minecraft.util.RegistryNamespacedDefaultedByKey;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidContainerItem;
import net.minecraftforge.fluids.IFluidHandler;
import ic2.api.energy.EnergyNet;
import ic2.api.energy.event.EnergyTileLoadEvent;
import ic2.api.energy.event.EnergyTileUnloadEvent;
import ic2.api.energy.tile.IEnergySink;
import ic2.api.item.IC2Items;
import ic2.api.network.INetworkClientTileEntityEventListener;
import ic2.core.ContainerBase;
import ic2.core.IC2;
import ic2.core.IHasGui;
import ic2.core.Ic2Items;
import ic2.core.audio.AudioSource;
import ic2.core.block.TileEntityBlock;
import ic2.core.block.TileEntityInventory;
import ic2.core.block.TileEntityLiquidTankInventory;
import ic2.core.block.invslot.InvSlot;
import ic2.core.block.invslot.InvSlot.Access;
import ic2.core.block.invslot.InvSlotConsumableLiquid;
import ic2.core.block.invslot.InvSlotConsumableLiquidByTank;
import ic2.core.block.invslot.InvSlotDischarge;
import ic2.core.block.invslot.InvSlotOutput;
import ic2.core.network.NetworkManager;
import ic2.core.util.StackUtil;
import ihl.IHLMod;
import ihl.chemistry.ApparatusProcessableInvSlot;
import ihl.recipes.UniversalRecipeInput;
import ihl.recipes.UniversalRecipeManager;
import ihl.recipes.UniversalRecipeOutput;
import ihl.utils.IHLFluidTank;
import ihl.utils.IHLUtils;

public class ImpregnatingMachineTileEntity extends TileEntityInventory implements IHasGui, INetworkClientTileEntityEventListener, IFluidHandler
{
	private final static UniversalRecipeManager recipeManager = new UniversalRecipeManager();
    public final ApparatusProcessableInvSlot input;
    public final InvSlotOutput outputSlot;
    public final InvSlotConsumableLiquid drainInputSlot;
    public final InvSlotConsumableLiquid fillInputSlot;
    public final InvSlotOutput emptyFluidItemsSlot;
	public short progress;
	protected short operationLength=200;
	private final IHLFluidTank fluidTank = new IHLFluidTank(1000);
	public short temperature=20;
	private ItemStack fractionalOutput;
	private int fractionalOutputAmount=0;
    
	public ImpregnatingMachineTileEntity() {
		super();
	 	this.outputSlot = new InvSlotOutput(this, "output", 0, 2);
        this.drainInputSlot = new InvSlotConsumableLiquid(this, "drainInput", -1, InvSlot.Access.I, 1, InvSlot.InvSide.TOP, InvSlotConsumableLiquid.OpType.Drain);
        this.fillInputSlot = new InvSlotConsumableLiquid(this, "fillInput", -1, InvSlot.Access.I, 1, InvSlot.InvSide.BOTTOM, InvSlotConsumableLiquid.OpType.Fill);
		this.emptyFluidItemsSlot = new InvSlotOutput(this, "fluidCellsOutput", 2, 1);
		this.input = new ApparatusProcessableInvSlot(this, "input", 3, Access.IO, 1, 64);
	}
	
    public void readFromNBT(NBTTagCompound nbttagcompound)
    {
        super.readFromNBT(nbttagcompound);
        this.fluidTank.readFromNBT(nbttagcompound.getCompoundTag("fluidTank"));
        if(nbttagcompound.hasKey("fractionalOutput"))
        {
        	this.fractionalOutput.readFromNBT(nbttagcompound.getCompoundTag("fractionalOutput"));
        	this.fractionalOutputAmount = nbttagcompound.getInteger("fractionalOutputAmount");
        }
    }

    public void writeToNBT(NBTTagCompound nbttagcompound)
    {
        super.writeToNBT(nbttagcompound);
        NBTTagCompound fluidTankTag = new NBTTagCompound();
        this.fluidTank.writeToNBT(fluidTankTag);
        nbttagcompound.setTag("fluidTank", fluidTankTag);
        NBTTagCompound fractionalOutputNBT = new NBTTagCompound();
        if(this.fractionalOutput!=null)
        {
        	this.fractionalOutput.writeToNBT(fractionalOutputNBT);
        	nbttagcompound.setTag("fractionalOutput", fractionalOutputNBT);
        	nbttagcompound.setInteger("fractionalOutputAmount",this.fractionalOutputAmount);
        }
    }
    
	@Override
	public boolean wrenchCanSetFacing(EntityPlayer entityPlayer, int side) {
		return false;
	}
	
	@Override
	public ItemStack getWrenchDrop(EntityPlayer entityPlayer) {
		return new ItemStack(IHLMod.sackBlock,1);
	}
	
    public boolean enableUpdateEntity()
    {
        return IC2.platform.isSimulating();
    }
    

    public void updateEntity()
    {
        super.updateEntity();
        
        if(this.fluidTank.getFluid()!=null)
        {
        	if(this.fluidTank.getFluid().tag!=null && this.fluidTank.getFluid().tag.hasKey("temperature"))
        	{
        		temperature=(short) (this.fluidTank.getFluid().tag.getInteger("temperature")-273);
        	}
        	else
        	{
        		temperature=(short) (this.fluidTank.getFluid().getFluid().getTemperature()-273);
        	}
        }
        MutableObject output;
        if (!this.drainInputSlot.isEmpty())
        {
            output = new MutableObject();
            if (this.fluidTank.fill(this.drainInputSlot.drain(null, this.fluidTank.getCapacity()-this.fluidTank.getFluidAmount(), output, true),false)>0 && (output.getValue() == null || this.emptyFluidItemsSlot.canAdd((ItemStack)output.getValue())))
            {
            	this.fluidTank.fill(this.drainInputSlot.drain(null, this.fluidTank.getCapacity()-this.fluidTank.getFluidAmount(), output, false),true);
            	if(output.getValue()!=null)
            	{
            		this.emptyFluidItemsSlot.add((ItemStack)output.getValue());
            	}
            }
        }
        if (!this.fillInputSlot.isEmpty())
        {
            output = new MutableObject();
            if (this.fillInputSlot.transferFromTank(this.fluidTank, output, false) && (output.getValue() == null || this.emptyFluidItemsSlot.canAdd((ItemStack)output.getValue())))
            {
            	if(output.getValue()!=null)
            	{
            		this.emptyFluidItemsSlot.add((ItemStack)output.getValue());
            	}
            }
        }
        boolean needsInvUpdate = false;
        if (this.canOperate())
        {
            this.setActive(true);
            if (this.progress == 0)
            {
                ((NetworkManager)IC2.network.get()).initiateTileEntityEvent(this, 0, true);
            }

            ++this.progress;
            if (this.progress >= this.operationLength)
            {
                this.operate();
                needsInvUpdate = true;
                this.progress = 0;
                ((NetworkManager)IC2.network.get()).initiateTileEntityEvent(this, 2, true);
            }
        }
        else
        {
            if (this.progress != 0 && this.getActive())
            {
                ((NetworkManager)IC2.network.get()).initiateTileEntityEvent(this, 1, true);
            }
            if (!this.canOperate())
            {
                this.progress = 0;
            }
            this.setActive(false);
        }
    }
    
    @Override
    public FluidStack drain(ForgeDirection from, int amount, boolean doDrain)
    {
    	FluidStack fstack = this.fluidTank.drain(amount, doDrain);
		return fstack;
    }

	
    //1.7.10 API
	@Override
	public boolean canDrain(ForgeDirection arg0, Fluid arg1) {
		return true;
	}

	@Override
	public boolean canFill(ForgeDirection direction, Fluid arg1) {
		return true;
	}

	@Override
	public String getInventoryName() {
		return "impregnatingMachine";
	}
	
    private int mX()
	{
		switch(this.getFacing())
		{
		case 4:
		return -1;
		case 5:
		return 1;
		default:
		return 0;
		}
	}
	
	private int mZ()
	{
		switch(this.getFacing())
		{
		case 3:
		return 1;
		case 2:
		return -1;
		case 4:
		return 0;
		case 5:
		return 0;
		default:
		return -1;
		}
	}
	
	private short getFacingFromXZ(int x, int z)
	{
		switch(x)
		{
			case -1:
				return (short)4;
			case 1:
				return (short)5;
			default:
				switch(z)
				{
				case 1:
					return (short)3;
				case -1:
					return (short)2;
				default:
					return (short)2;
				}
		}
	}
	
	public float getRenderLiquidLevel()
	{
		return (float)this.fluidTank.getFluidAmount()/(float)this.fluidTank.getCapacity();
	}

	@Override
	public void onNetworkEvent(EntityPlayer player, int event) 
	{
		// TODO Auto-generated method stub
		
	}

    public int gaugeProgressScaled(int i)
    {
        return this.progress * i / operationLength;
    }

	@Override
	@SideOnly(Side.CLIENT)
	public GuiScreen getGui(EntityPlayer player, boolean arg1) {
		return new ImpregnatingMachineGui(new ImpregnatingMachineContainer(player, this));
	}

	@Override
	public ContainerBase<?> getGuiContainer(EntityPlayer player) 
	{
		this.fluidTank.sortFluidsByDensity();
		return new ImpregnatingMachineContainer(player, this);
	}

	@Override
	public void onGuiClosed(EntityPlayer player) {}

    public boolean canOperate()
    {
    	return getOutput()!=null && this.outputSlot.canAdd(getOutput().getItemOutputs());
    }
    
    public UniversalRecipeOutput getOutput()
    {
    	return this.recipeManager.getOutputFor(this.getInput(), false, false);
    }

	public UniversalRecipeInput getInput()
	{
		if(this.drainInputSlot.isEmpty() && fluidTank.getNumberOfFluids()>=2)
		{
			return new UniversalRecipeInput(Arrays.asList(new FluidStack[]{fluidTank.getFluid(0),fluidTank.getFluid(1)}), this.input.getItemStackList());
		}
		else if(fluidTank.getNumberOfFluids()>=2)
		{
			UniversalRecipeInput var1 = new UniversalRecipeInput(Arrays.asList(new FluidStack[]{fluidTank.getFluid(1),this.drainInputSlot.drain(null, Integer.MAX_VALUE, new MutableObject(), true)}), this.input.getItemStackList());
			if(this.recipeManager.getOutputFor(var1, false, false)!=null)
			{
				return var1;
			}
		}
		return new UniversalRecipeInput(Arrays.asList(new FluidStack[]{fluidTank.getFluid(),this.drainInputSlot.drain(null, Integer.MAX_VALUE, new MutableObject(), true)}), this.input.getItemStackList());
	}
	
	public void operate() 
	{
		UniversalRecipeInput recipeInput = this.recipeManager.getRecipeInput(getInput());
		UniversalRecipeOutput output1 = getOutput();
		for(int i=0; i<this.input.size();i++)
		{
			this.input.consume(i,recipeInput.getItemInputs().get(i).stackSize);
		}
		MutableObject<ItemStack> output2 = new MutableObject();
		if(this.drainInputSlot.drain(null, recipeInput.getFluidInputs().get(1).amount, output2, false)==null)
		{
			this.drainInputSlot.drain(null, Integer.MAX_VALUE, output2, false);
		}
		this.fluidTank.drain(recipeInput.getFluidInputs().get(1), true);
		this.fluidTank.drain(recipeInput.getFluidInputs().get(0), true);
		if(output1.getFluidOutputs().size()>0)this.fluidTank.fill(output1.getFluidOutputs().get(0), true);
		if(output1.getItemOutputs().get(0)!=null)this.outputSlot.put(0,output1.getItemOutputs().get(0).copy());
		if(output2.getValue()!=null)this.emptyFluidItemsSlot.add((ItemStack)output2.getValue());
		if(this.drainInputSlot.get()!=null && !(this.drainInputSlot.get().getItem() instanceof IFluidContainerItem))
		{
			this.drainInputSlot.consume(1);
		}
		if(output1.getItemOutputs().size()>1)
		{
			if(this.fractionalOutput!=null && this.fractionalOutput.isItemEqual(output1.getItemOutputs().get(1)))
			{
				this.fractionalOutputAmount+=26;
				if(this.fractionalOutputAmount>=100)
				{
					this.outputSlot.put(1, this.fractionalOutput);
					this.fractionalOutputAmount-=100;
				}
			}
			else
			{
				this.fractionalOutput=output1.getItemOutputs().get(1).copy();
				this.fractionalOutputAmount=0;
			}
			
		}
	}

	@Override
	public FluidStack drain(ForgeDirection arg0, FluidStack fluidStack, boolean doDrain) {
		if(fluidTank.getFluid().containsFluid(fluidStack))
		{
			return this.fluidTank.drain(fluidStack.amount, doDrain);
		}
		return null;
	}

	@Override
	public int fill(ForgeDirection arg0, FluidStack arg1, boolean arg2) {
		return this.fluidTank.fill(arg1, arg2);
	}

	@Override
	public FluidTankInfo[] getTankInfo(ForgeDirection arg0) {
		return new FluidTankInfo[] {this.fluidTank.getInfo()};
	}
	
    public boolean needsFluid()
    {
        return this.fluidTank.getFluidAmount() <= this.fluidTank.getCapacity();
    }
    
    public FluidStack getFluidStackfromTank()
    {
    	return this.fluidTank.getFluid();
    }

	public int getTankAmount() 
	{
		return this.fluidTank.getFluidAmount();
	}
	
    public int gaugeLiquidScaled(int i, int index)
    {
        return this.fluidTank.getFluidAmount() <= 0 ? 0 : this.fluidTank.getFluidAmount(index) * i / this.fluidTank.getCapacity();
    }
	public static void addRecipe(UniversalRecipeInput input, UniversalRecipeOutput output) 
	{
		recipeManager.addRecipe(input, output);
	}
	

	public void setFluid(FluidStack fluidStack, int index) 
	{
		this.fluidTank.setFluid(fluidStack, index);
	}
	

	public void setFluidAmount(int amount, int index) 
	{
		this.fluidTank.setFluidAmount(amount, index);
	}

	public int getNumberOfFluidsInTank() 
	{
		return this.fluidTank.getNumberOfFluids();
	}

	public int getFluidAmount(int i) 
	{
		return this.fluidTank.getFluidAmount(i);
	}
	
	public int getFluidID(int i) 
	{
		return this.fluidTank.getFluidID(i);
	}
	
	public FluidStack getFluid(int i) 
	{
		return this.fluidTank.getFluid(i);
	}

	public void removeFluid(int i) 
	{
		this.fluidTank.removeFluid(i);
	}

	public static Map<UniversalRecipeInput, UniversalRecipeOutput> getRecipes() {
		return recipeManager.getRecipes();
	}

	public static void addQuenchingRecipe(String nameHot, String nameQuenched) 
	{
		addRecipe(new UniversalRecipeInput(Arrays.asList(new FluidStack[] {IHLUtils.getFluidStackWithSize("oleicacid", 1)}), Arrays.asList(new ItemStack[] {IHLUtils.getThisModItemStack(nameHot)})), new UniversalRecipeOutput(null, Arrays.asList(new ItemStack[] {IHLUtils.getThisModItemStack(nameQuenched)}),2));
	}
}