package net.viddeno.technical.machine.tileentity;

import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.Level;

import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.viddeno.technical.Technical;
import net.viddeno.technical.auto.TechnicalAutoTileEntity;
import net.viddeno.technical.item.TechnicalItem;
import net.viddeno.technical.machine.Type;
import net.viddeno.technical.machine.recipes.MachineRecipes;

public class TileEntityMachine extends TileEntity implements IInventory, TechnicalAutoTileEntity {

	protected final Type type;
	protected final int totalTime;

	protected ItemStack[] machineItemStacks;

	public int machineBurnTime;
	public int machineCookTime;
	public int currentBurnTime;

	protected String machineName;

	protected TileEntityMachine(Type machineType, int slotAmount) {
		type = machineType;
		totalTime = type.speed;
		machineItemStacks = new ItemStack[slotAmount];
	}

	public void setMachineName(String name) {
		machineName = name;
	}

	@Override
	public int getSizeInventory() {
		return machineItemStacks.length;
	}

	@Override
	public ItemStack getStackInSlot(int slot) {
		return machineItemStacks[slot];
	}

	@Override
	public ItemStack getStackInSlotOnClosing(int slot) {
		if(machineItemStacks[slot] != null) {
			ItemStack itemStack = machineItemStacks[slot];
			machineItemStacks[slot] = null;
			return itemStack;
		} else {
			return null;
		}
	}

	public void setInventorySlotContents(int slot, ItemStack itemStack) {
		machineItemStacks[slot] = itemStack;

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

	public String getInventoryName() {
		return hasCustomInventoryName() ? machineName : type.name;
	}

	public boolean hasCustomInventoryName() {
		return machineName != null && machineName.length() > 0;
	}

	public int getInventoryStackLimit() {
		return 64;
	}

	public void readFromNBT(NBTTagCompound tagCompound) {
		super.readFromNBT(tagCompound);
		NBTTagList tagList = tagCompound.getTagList("Items", 10);
		machineItemStacks = new ItemStack[getSizeInventory()];

		for(int i = 0; i < tagList.tagCount(); ++i) {
			NBTTagCompound tagCompund1 = tagList.getCompoundTagAt(i);
			byte b = tagCompund1.getByte("Slot");
			if(b >= 0 && b < machineItemStacks.length)
				machineItemStacks[b] = ItemStack.loadItemStackFromNBT(tagCompund1);
		}
		machineBurnTime = tagCompound.getShort("BurnTime");
		machineCookTime = tagCompound.getShort("CookTime");
		if(getItemBurnTimeElectrical(machineItemStacks[3]) != 0)
			currentBurnTime = getItemBurnTimeElectrical(machineItemStacks[3]);
		else
			currentBurnTime = getItemBurnTimeBurning(machineItemStacks[4]);

		if(tagCompound.hasKey("CustomName", 8)) {
			machineName = tagCompound.getString("CustomName");
		}
	}

	public void tileEntityReadFromNBT(NBTTagCompound tagCompound) {
		super.readFromNBT(tagCompound);
	}

	public ItemStack decrStackSize(int slot, int amount) {
		if(machineItemStacks[slot] != null) {
			ItemStack itemStack;
			if(machineItemStacks[slot].stackSize <= amount) {
				itemStack = machineItemStacks[slot];
				machineItemStacks[slot] = null;
				return itemStack;
			} else {
				itemStack = machineItemStacks[slot].splitStack(amount);

				if(machineItemStacks[slot].stackSize == 0) {
					machineItemStacks[slot] = null;
				}
				return itemStack;
			}
		} else {
			return null;
		}
	}

	public void writeToNBT(NBTTagCompound tagCompound) {
		super.writeToNBT(tagCompound);

		tagCompound.setShort("BurnTime", (short) machineBurnTime);
		tagCompound.setShort("CookTime", (short) machineCookTime);
		NBTTagList tagList = new NBTTagList();

		for(int i = 0; i < machineItemStacks.length; ++i) {
			if(machineItemStacks[i] != null) {
				NBTTagCompound tagCompound1 = new NBTTagCompound();
				tagCompound1.setByte("Slot", (byte) i);
				machineItemStacks[i].writeToNBT(tagCompound1);
				tagList.appendTag(tagCompound1);
			}
		}
		tagCompound.setTag("Items", tagList);

		if(hasCustomInventoryName())
			tagCompound.setString("CustomName", machineName);
	}

	public void tileEntityWriteToNBT(NBTTagCompound tagCompound) {
		super.writeToNBT(tagCompound);
	}

	@SideOnly(Side.CLIENT)
	public int getCookProgressScaled(int par1) {
		return machineCookTime * par1 / totalTime;
	}

	@SideOnly(Side.CLIENT)
	public int getBurnTimeRemainingScaled(int par1) {
		if(currentBurnTime == 0)
			currentBurnTime = totalTime;

		return machineBurnTime * par1 / currentBurnTime;
	}

	public boolean isBurning() {
		return machineBurnTime > 0;
	}

	public void updateEntity() {
		boolean flag = machineBurnTime > 0;
		boolean flag1 = false;

		if(machineBurnTime > 0)
			machineBurnTime--;

		if(!this.worldObj.isRemote) {
			if(machineBurnTime != 0 || (machineItemStacks[3] != null || machineItemStacks[4] != null) && (machineItemStacks[0] != null || machineItemStacks[1] != null || machineItemStacks[2] != null)) {
				if(machineBurnTime == 0 && (Boolean) canSmelt()[0]) {
					if(getItemBurnTimeElectrical(machineItemStacks[3]) != 0)
						currentBurnTime = machineBurnTime = getItemBurnTimeElectrical(machineItemStacks[3]);
					else
						currentBurnTime = machineBurnTime = getItemBurnTimeBurning(machineItemStacks[4]);

					if(machineBurnTime > 0) {
						flag1 = true;

						if(machineItemStacks[3] != null) {
							--machineItemStacks[3].stackSize;

							if(machineItemStacks[3].stackSize == 0) {
								machineItemStacks[3] = machineItemStacks[3].getItem().getContainerItem(machineItemStacks[3]);
							}
						} else if(machineItemStacks[4] != null) {
							--machineItemStacks[4].stackSize;

							if(machineItemStacks[4].stackSize == 0) {
								machineItemStacks[4] = machineItemStacks[4].getItem().getContainerItem(machineItemStacks[4]);
							}
						}
					}
				}
			}

			if(isBurning() && (Boolean) canSmelt()[0]) {
				++machineCookTime;
				if(machineCookTime == totalTime) {
					machineCookTime = 0;
					smeltItem();
					flag1 = true;
				}
			} else {
				machineBurnTime = 0;
			}

			if(flag != machineBurnTime > 0) {
				flag1 = true;
				updateBlockState(machineBurnTime > 0, worldObj, xCoord, yCoord, zCoord);
			}

			if(flag1) {
				this.markDirty();
			}
		}
	}

	protected void updateBlockState(boolean active, World world, int x, int y, int z) {
		FMLLog.log(Level.WARN, Technical.modName + ": Something is trying to do updateBlockState() in " + getClass().getName() + ". This is an error and may cause damage, please report this to the mod author (" + Technical.authors + ")");
		return;
	}

	@SuppressWarnings("unchecked")
	public void smeltItem() {
		Object[] canSmeltResult = canSmelt();
		if((Boolean) canSmeltResult[0]) {
			ItemStack[] itemStacks = getSmeltingResult(new ItemStack[] { machineItemStacks[0], machineItemStacks[1], machineItemStacks[2] }).clone();

			for(int item = 3; item < itemStacks.length; item++) {
				for(int slot = 5; slot < machineItemStacks.length; slot++) {
					if(itemStacks[item] == null)
						continue;

					if(machineItemStacks[slot] == null) {
						machineItemStacks[slot] = itemStacks[item].copy();
						itemStacks[item] = null;
						continue;
					}

					int added = machineItemStacks[slot].stackSize + itemStacks[item].stackSize;
					if(machineItemStacks[slot].isItemEqual(itemStacks[item])) {
						if(added > machineItemStacks[slot].getMaxStackSize()) {
							machineItemStacks[slot].stackSize += machineItemStacks[slot].getMaxStackSize();
							itemStacks[item].stackSize = added - machineItemStacks[slot].getMaxStackSize();
							machineItemStacks[slot].stackSize = machineItemStacks[slot].getMaxStackSize();
							continue;
						} else {
							machineItemStacks[slot].stackSize = added;
							itemStacks[item] = null;
							continue;
						}
					}
				}
			}

			if(itemStacks[0] != null)
				machineItemStacks[((HashMap<Integer, Integer>) canSmeltResult[1]).get(0)].stackSize -= itemStacks[0].stackSize;

			if(itemStacks[1] != null)
				machineItemStacks[((HashMap<Integer, Integer>) canSmeltResult[1]).get(1)].stackSize -= itemStacks[1].stackSize;

			if(itemStacks[2] != null)
				machineItemStacks[((HashMap<Integer, Integer>) canSmeltResult[1]).get(2)].stackSize -= itemStacks[2].stackSize;

			if(machineItemStacks[0] != null && machineItemStacks[0].stackSize <= 0)
				machineItemStacks[0] = null;

			if(machineItemStacks[1] != null && machineItemStacks[1].stackSize <= 0)
				machineItemStacks[1] = null;

			if(machineItemStacks[2] != null && machineItemStacks[2].stackSize <= 0)
				machineItemStacks[2] = null;

		}
	}

	public static int getItemBurnTimeElectrical(ItemStack itemStack) {
		if(itemStack == null) {
			return 0;
		} else {
			Item item = itemStack.getItem();

			if(item instanceof ItemBlock && Block.getBlockFromItem(item) != Blocks.air) {

				@SuppressWarnings("unused")
				Block block = Block.getBlockFromItem(item);

			}
			if(item == TechnicalItem.Battery1)
				return 2560;

			return 0;
		}
	}

	public static int getItemBurnTimeBurning(ItemStack itemStack) {
		if(itemStack == null)
			return 0;
		else {
			Item item = itemStack.getItem();

			if(item instanceof ItemBlock && Block.getBlockFromItem(item) != Blocks.air) {

				Block block = Block.getBlockFromItem(item);

				if(block == Blocks.coal_block)
					return 3200;
			}

			if(item == Items.coal)
				return 320;

			return 0;
		}
	}

	public static boolean isItemFuelElectrical(ItemStack itemStack) {
		if(getItemBurnTimeElectrical(itemStack) != 0)
			return true;
		return false;

	}

	public static boolean isItemFuelBurning(ItemStack itemStack) {
		if(getItemBurnTimeBurning(itemStack) != 0)
			return true;
		return false;

	}

	protected Object[] canSmelt() {
		Map<Integer, Integer> itemSlots = new HashMap<Integer, Integer>();
		if(machineItemStacks[0] == null && machineItemStacks[1] == null && machineItemStacks[2] == null) {
			// FMLLog.log(Level.INFO, "Y");
			return new Object[] { false, itemSlots };
		} else {
			ItemStack[] resultItemStacks = getSmeltingResult(new ItemStack[] { machineItemStacks[0], machineItemStacks[1], machineItemStacks[2] }).clone();
			ItemStack[] itemStacks = new ItemStack[9];

			for(int i = 0; i < resultItemStacks.length; i++)
				itemStacks[i] = resultItemStacks[i];

			/*
			 * FMLLog.log(Level.INFO, "machineItemStacks[0]: " +
			 * (machineItemStacks[0] != null ? machineItemStacks[0].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[1]: " +
			 * (machineItemStacks[1] != null ? machineItemStacks[1].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[2]: " +
			 * (machineItemStacks[2] != null ? machineItemStacks[2].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[3]: " +
			 * (machineItemStacks[3] != null ? machineItemStacks[3].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[4]: " +
			 * (machineItemStacks[4] != null ? machineItemStacks[4].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[5]: " +
			 * (machineItemStacks[5] != null ? machineItemStacks[5].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[6]: " +
			 * (machineItemStacks[6] != null ? machineItemStacks[6].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[7]: " +
			 * (machineItemStacks[7] != null ? machineItemStacks[7].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[8]: " +
			 * (machineItemStacks[8] != null ? machineItemStacks[8].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[9]: " +
			 * (machineItemStacks[9] != null ? machineItemStacks[9].toString() :
			 * "null")); FMLLog.log(Level.INFO, "machineItemStacks[10]: " +
			 * (machineItemStacks[10] != null ? machineItemStacks[10].toString()
			 * : "null")); FMLLog.log(Level.INFO, "itemStacks[0]: " +
			 * (itemStacks[0] != null ? itemStacks[0].toString() : "null"));
			 * FMLLog.log(Level.INFO, "itemStacks[1]: " + (itemStacks[1] != null
			 * ? itemStacks[1].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[2]: " + (itemStacks[2] != null ?
			 * itemStacks[2].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[3]: " + (itemStacks[3] != null ?
			 * itemStacks[3].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[4]: " + (itemStacks[4] != null ?
			 * itemStacks[4].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[5]: " + (itemStacks[5] != null ?
			 * itemStacks[5].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[6]: " + (itemStacks[6] != null ?
			 * itemStacks[6].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[7]: " + (itemStacks[7] != null ?
			 * itemStacks[7].toString() : "null")); FMLLog.log(Level.INFO,
			 * "itemStacks[8]: " + (itemStacks[8] != null ?
			 * itemStacks[8].toString() : "null"));
			 * 
			 * FMLLog.log(Level.INFO, (itemStacks[0] != null) + "");
			 */
			if(itemStacks[0] != null)
				// FMLLog.log(Level.INFO, (machineItemStacks[0].stackSize >=
				// itemStacks[0].stackSize) + "");

				if(itemStacks[0] == null && itemStacks[1] == null && itemStacks[2] == null || !((itemStacks[0] == null || itemStacks[0] != null && machineItemStacks[0].stackSize >= itemStacks[0].stackSize) && (itemStacks[1] == null || itemStacks[1] != null && machineItemStacks[1].stackSize >= itemStacks[1].stackSize)
						&& (itemStacks[2] == null || itemStacks[2] != null && machineItemStacks[2].stackSize >= itemStacks[2].stackSize)))
					return new Object[] { false, itemSlots };

			// FMLLog.log(Level.INFO, "check1");

			if(itemStacks[0] == null || (machineItemStacks[3] == null && machineItemStacks[4] == null && machineBurnTime <= 0))
				return new Object[] { false, itemSlots };

			// FMLLog.log(Level.INFO, "check2");

			boolean cancel[] = { true, true, true };
			for(int item = 0; item < 3; item++) {
				for(int mItem = 0; mItem < 3; mItem++) {
					if(itemStacks[item] != null) {
						// FMLLog.log(Level.INFO, String.valueOf(item) + " " +
						// String.valueOf(mItem));
						if(machineItemStacks[mItem] == null) {
							cancel[item] = false;
							itemSlots.put(mItem, item);
						} else {
							if(itemStacks[item].getItem() == machineItemStacks[mItem].getItem()) {
								if(itemStacks[item].stackSize <= machineItemStacks[mItem].stackSize) {
									cancel[item] = false;
									itemSlots.put(mItem, item);
								}
							}
						}
					} else {
						// FMLLog.log(Level.INFO, "cancel[item] = false; " +
						// item);
						cancel[item] = false;
					}
				}
			}
			boolean Return = false;
			// FMLLog.log(Level.INFO, "Returns:");
			for(boolean b : cancel) {
				Return = Return || b;
				// FMLLog.log(Level.INFO, String.valueOf(b));
			}
			if(Return)
				return new Object[] { false, itemSlots };

			boolean[] checked = new boolean[itemStacks.length - 3];
			for(int item = 3; item < itemStacks.length; item++) {
				for(int slot = 5; slot < machineItemStacks.length; slot++) {
					if(itemStacks[item] == null) {
						checked[item - 3] = true;
						continue;
					}

					if(machineItemStacks[slot] == null) {
						checked[item - 3] = true;
						itemStacks[item] = null;
						continue;
					}

					int added = machineItemStacks[slot].stackSize + itemStacks[item].stackSize;
					if(machineItemStacks[slot].isItemEqual(itemStacks[item]) && added <= getInventoryStackLimit() && added <= itemStacks[item].getMaxStackSize()) {
						checked[item - 3] = true;
						itemStacks[item] = null;
						continue;
					}
				}
			}
			boolean result = true;
			// FMLLog.log(Level.INFO, "Results:");
			for(boolean b : checked) {
				result = result && b;
				// FMLLog.log(Level.INFO, String.valueOf(b));
			}
			/*
			 * for(int i = 0; i < 6; i++) FMLLog.log(Level.INFO, "itemSlots" + i
			 * + itemSlots.get(i));
			 */
			return new Object[] { result, itemSlots };
		}
	}

	public ItemStack[] getSmeltingResult(ItemStack[] itemStack) {
		FMLLog.log(Level.WARN, Technical.modName + ": Something is trying to get a recipe result from class " + this.getClass().getName() + ". This is not allowed and will probably cause errors");
		return new ItemStack[] { new ItemStack(TechnicalItem.Error) };
	}

	public boolean isItemUsedInRecipe(ItemStack itemStack) {
		return MachineRecipes.isItemUsedInRecipe(itemStack);
	}

	@Override
	public boolean isUseableByPlayer(EntityPlayer player) {
		return worldObj.getTileEntity(xCoord, yCoord, zCoord) != this ? false : player.getDistanceSq((double) xCoord + 0.5D, (double) yCoord + 0.5D, (double) zCoord + 0.5D) <= 64.0D;
	}

	@Override
	public void openInventory() {

	}

	@Override
	public void closeInventory() {

	}

	@Override
	public boolean isItemValidForSlot(int slot, ItemStack itemStack) {
		if(slot == 5 || slot == 6 || slot == 7 || slot == 8 || slot == 9 || slot == 10)
			return false;

		if(slot == 3)
			return isItemFuelBurning(itemStack);
		if(slot == 4)
			return isItemFuelElectrical(itemStack);

		return true;
	}

	@Override
	public int[] getSlotsForItem(Item item) {
		if(isItemFuelElectrical(new ItemStack(item)))
			return new int[] { 3 };
		if(isItemFuelBurning(new ItemStack(item)))
			return new int[] { 4 };
		return new int[] { 2, 1, 0 };
	}

	@Override
	public int[] pullItemsFromWhichSlots() {
		if(machineItemStacks.length == 11)
			return new int[] { 5, 6, 7, 8, 9, 10 };
		return new int[] {};
	}

	@Override
	public ItemStack pullItem(int slot, int maxStackSize) {
		if(slot == -1) {
			for(int s : pullItemsFromWhichSlots()) {
				if(machineItemStacks[s] != null && machineItemStacks[s].stackSize > 0) {
					slot = s;
					break;
				}
			}
			if(slot == -1)
				return null;
		}
		if(machineItemStacks[slot] == null || machineItemStacks[slot].stackSize == 0)
			return null;
		if(maxStackSize >= machineItemStacks[slot].stackSize) {
			ItemStack returnStack = machineItemStacks[slot].copy();
			machineItemStacks[slot] = null;
			return returnStack;
		} else {
			ItemStack returnStack = machineItemStacks[slot].splitStack(maxStackSize);
			return returnStack;
		}
	}

	@Override
	public ItemStack pullItem(int slot, int maxStackSize, ItemStack compareStack) {
		if(slot == -1) {
			for(int s : pullItemsFromWhichSlots()) {
				if(machineItemStacks[s] != null && machineItemStacks[s].stackSize > 0) {
					slot = s;
					break;
				}
			}
			if(slot == -1)
				return null;
		}
		if(machineItemStacks[slot] == null || machineItemStacks[slot].stackSize == 0)
			return null;
		if(compareStack != null && compareStack.isItemEqual(machineItemStacks[slot])) {
			if(maxStackSize <= machineItemStacks[slot].stackSize) {
				ItemStack returnStack = machineItemStacks[slot].copy();
				machineItemStacks[slot] = null;
				return returnStack;
			} else {
				ItemStack returnStack = machineItemStacks[slot].splitStack(maxStackSize);
				return returnStack;
			}
		}
		return null;
	}

	@Override
	public ItemStack pushItem(ItemStack itemStack, int slot) {
		if(itemStack != null) {
			if(slot == -1) {
				for(int s : getSlotsForItem(itemStack.getItem())) {
					if(machineItemStacks[s] != null && machineItemStacks[s].isItemEqual(itemStack)) {
						slot = s;
					}
				}
				if(slot == -1) {
					for(int s : getSlotsForItem(itemStack.getItem())) {
						if(machineItemStacks[s] == null) {
							slot = s;
						}
					}
				}
				if(slot == -1)
					return itemStack;
			}
			if(machineItemStacks[slot] == null) {
				machineItemStacks[slot] = itemStack.copy();
				itemStack = null;
			} else if(machineItemStacks[slot].isItemEqual(itemStack)) {
				if(machineItemStacks[slot].stackSize + itemStack.stackSize <= machineItemStacks[slot].getMaxStackSize() && machineItemStacks[slot].stackSize + itemStack.stackSize <= getInventoryStackLimit()) {
					machineItemStacks[slot].stackSize += itemStack.stackSize;
					itemStack = null;
				} else {
					int maxStackSize = machineItemStacks[slot].getMaxStackSize();
					if(machineItemStacks[slot].getMaxStackSize() > getInventoryStackLimit())
						maxStackSize = getInventoryStackLimit();
					int totalStackSize = machineItemStacks[slot].stackSize + itemStack.stackSize;
					machineItemStacks[slot].stackSize = maxStackSize;
					totalStackSize -= maxStackSize;
					itemStack.stackSize = totalStackSize;
				}
			}
		}
		if(itemStack != null && itemStack.stackSize == 0)
			itemStack = null;
		return itemStack;
	}

	@Override
	public TileEntity pushTileEntity() {
		return null;
	}

	@Override
	public TileEntity pullTileEntity() {
		return null;
	}

	@Override
	public boolean isStackNull(int slot) {
		return machineItemStacks[slot] == null;
	}

	@Override
	public boolean areItemStacksSame(int slot, ItemStack itemStack) {
		if(machineItemStacks[slot] != null && itemStack != null)
			return machineItemStacks[slot].isItemEqual(itemStack);
		return false;
	}

	@Override
	public ItemStack getItemStack(int slot) {
		return getStackInSlot(slot);
	}

	@Override
	public int maxStackSize() {
		return getInventoryStackLimit();
	}
}