package net.minecraft.src;


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * Tools for inventory manipulation.
 * 
 * @author MightyPork
 */
public class PC_InvUtils {

	/**
	 * Get inventory or double chest inventory.
	 * 
	 * @param world world to work with
	 * @param pos the position
	 * @return the IInventory of the chest
	 */
	public static IInventory getCompositeInventoryAt(IBlockAccess world, PC_CoordI pos) {
		TileEntity te = pos.getTileEntity(world);
		if (te == null) {
			return null;
		}
		if (te instanceof PC_IInventoryWrapper) {
			return ((PC_IInventoryWrapper) te).getInventory();
		}
		if (!(te instanceof IInventory)) {
			return null;
		}

		IInventory inv = (IInventory) te;

		int id = pos.getId(world);

		if (id == Block.chest.blockID) {

			if (pos.offset(-1, 0, 0).getId(world) == Block.chest.blockID) {
				inv = new InventoryLargeChest("Large chest", (IInventory) pos.offset(-1, 0, 0).getTileEntity(world), inv);
			}
			if (pos.offset(1, 0, 0).getId(world) == Block.chest.blockID) {
				inv = new InventoryLargeChest("Large chest", inv, (IInventory) pos.offset(1, 0, 0).getTileEntity(world));
			}
			if (pos.offset(0, 0, -1).getId(world) == Block.chest.blockID) {
				inv = new InventoryLargeChest("Large chest", (IInventory) pos.offset(0, 0, -1).getTileEntity(world), inv);
			}
			if (pos.offset(0, 0, 1).getId(world) == Block.chest.blockID) {
				inv = new InventoryLargeChest("Large chest", inv, (IInventory) pos.offset(0, 0, 1).getTileEntity(world));
			}

		}

		return inv;
	}

	/**
	 * Store itemstack into specified slot in an inventory. Useful for furnaces,
	 * brewing stands and that kind of containers.
	 * 
	 * @param inventory target inventory
	 * @param stackToStore stack to store
	 * @param slot id of the slot
	 * @return true if something was stored
	 */
	public static boolean storeItemInSlot(IInventory inventory, ItemStack stackToStore, int slot) {
		if (stackToStore == null || stackToStore.stackSize == 0) {
			return false;
		}
		if (inventory instanceof PC_ISpecialAccessInventory && !((PC_ISpecialAccessInventory) inventory).canInsertStackTo(slot, stackToStore)) {
			return false;
		}

		ItemStack destination = inventory.getStackInSlot(slot);

		if (destination == null) {
			int numStored = stackToStore.stackSize;
			numStored = Math.min(numStored, stackToStore.getMaxStackSize());
			numStored = Math.min(numStored, inventory.getInventoryStackLimit());
			destination = stackToStore.splitStack(numStored);
			inventory.setInventorySlotContents(slot, destination);
			return true;
		}

		if (destination.itemID == stackToStore.itemID && destination.isStackable() && (!destination.getHasSubtypes() || destination.getItemDamage() == stackToStore.getItemDamage()) && destination.stackSize < inventory.getInventoryStackLimit()) {
			int numStored = stackToStore.stackSize;
			numStored = Math.min(numStored, destination.getMaxStackSize() - destination.stackSize);
			numStored = Math.min(numStored, inventory.getInventoryStackLimit() - destination.stackSize);
			destination.stackSize += numStored;
			stackToStore.stackSize -= numStored;
			return (numStored > 0);
		}

		return false;
	}

	private static boolean isSpecialContainer(IInventory inventory) {
		boolean flag = false;
		flag |= inventory instanceof TileEntityFurnace;
		flag |= inventory instanceof TileEntityBrewingStand;
		return flag;
	}

	private static ItemStack dispenseFromSpecialContainer(IInventory inventory) {
		if (inventory instanceof TileEntityFurnace) {
			ItemStack stack = inventory.getStackInSlot(2);
			if (stack != null && stack.stackSize > 0) {
				inventory.setInventorySlotContents(2, null);
				return stack;
			}
			return null;
		}

		if (inventory instanceof TileEntityBrewingStand) {

			// check if brewing finished
			if (((TileEntityBrewingStand) inventory).getBrewTime() != 0) {
				return null;
			}

			for (int i = 0; i < 4; i++) {

				ItemStack stack = inventory.getStackInSlot(i);

				// if 0-2, its potion slot. If 3, its ingredient.
				if ((i < 3 && (stack != null && stack.stackSize > 0 && stack.itemID == Item.potion.shiftedIndex && stack.getItemDamage() != 0)) || (i == 3 && (stack != null))) {
					inventory.setInventorySlotContents(i, null);
					return stack;
				}
			}
			return null;
		}

		return null;
	}

	/**
	 * Remove first stack from ivnentory
	 * @param inventory inv
	 * @return the stack removed
	 */
	public static ItemStack dispenseFirstStack(IInventory inventory) {

		if (isSpecialContainer(inventory)) {
			return dispenseFromSpecialContainer(inventory);
		}

		for (int i = 0, n = inventory.getSizeInventory(); i < n; i++) {

			if (inventory instanceof PC_ISpecialAccessInventory) {
				if (!((PC_ISpecialAccessInventory) inventory).canDispenseStackFrom(i)) {
					continue;
				}
			}

			ItemStack stack = inventory.getStackInSlot(i);
			if (stack != null && stack.stackSize > 0) {

				inventory.setInventorySlotContents(i, null);
				return stack;

			}
		}

		return null;
	}

	/**
	 * Add given stack to an inventory. First fills used slots, then starts
	 * using new slots.
	 * 
	 * @param inv target IInventory
	 * @param itemstack stack to store
	 * @return true if something was stored
	 */
	public static boolean addItemStackToInventory(IInventory inv, ItemStack itemstack) {
		if (!itemstack.isItemDamaged()) {
			int i;
			do {
				i = itemstack.stackSize;
				itemstack.stackSize = storePartialItemStack(inv, itemstack);
			} while (itemstack.stackSize > 0 && itemstack.stackSize < i);
			return itemstack.stackSize < i;
		}
		int j = getFirstEmptySlot(inv, itemstack);
		if (j >= 0) {
			inv.setInventorySlotContents(j, ItemStack.copyItemStack(itemstack));
			itemstack.stackSize = 0;
			return true;
		}
		return false;
	}

	/**
	 * Add given stack to an inventory. First fills used slots, then starts
	 * using new slots. Returns TRUE only if EVERYTHING was stored, and the
	 * entity item can be discarded.
	 * 
	 * @param inv target IInventory
	 * @param itemstack stack to store
	 * @return true if all was stored.
	 */
	public static boolean addWholeItemStackToInventory(IInventory inv, ItemStack itemstack) {
		if (!itemstack.isItemDamaged()) {
			int oldSize;
			do {
				oldSize = itemstack.stackSize;
				itemstack.stackSize = storePartialItemStack(inv, itemstack);
			} while (itemstack.stackSize > 0 && itemstack.stackSize < oldSize);
			return itemstack.stackSize == 0;
		}
		int emptySlot = getFirstEmptySlot(inv, itemstack);
		if (emptySlot >= 0) {
			inv.setInventorySlotContents(emptySlot, ItemStack.copyItemStack(itemstack));
			itemstack.stackSize = 0;
			return true;
		}
		return false;
	}

	private static int getStackWithFreeSpace(IInventory inv, ItemStack itemstack) {

		for (int slot = 0; slot < inv.getSizeInventory(); slot++) {

			if (inv instanceof PC_ISpecialAccessInventory) {
				if (!((PC_ISpecialAccessInventory) inv).canInsertStackTo(slot, itemstack)) {
					continue;
				}
			}

			ItemStack stackAt = inv.getStackInSlot(slot);
			if (stackAt != null && stackAt.itemID == itemstack.itemID && stackAt.isStackable() && stackAt.stackSize < stackAt.getMaxStackSize() && stackAt.stackSize < inv.getInventoryStackLimit()
					&& (!stackAt.getHasSubtypes() || stackAt.getItemDamage() == itemstack.getItemDamage())) {
				return slot;
			}
		}

		return -1;
	}

	private static int storePartialItemStack(IInventory inv, ItemStack itemstack) {

		int id = itemstack.itemID;
		int size = itemstack.stackSize;

		// not stackable
		if (itemstack.getMaxStackSize() == 1) {
			int firstEmpty = getFirstEmptySlot(inv, itemstack);
			if (firstEmpty < 0) {
				return size;
			}
			if (inv.getStackInSlot(firstEmpty) == null) {
				inv.setInventorySlotContents(firstEmpty, ItemStack.copyItemStack(itemstack));
			}
			return 0;
		}

		int targetSlot = getStackWithFreeSpace(inv, itemstack);
		if (targetSlot < 0) {
			targetSlot = getFirstEmptySlot(inv, itemstack);
		}

		if (targetSlot < 0) {
			return size;
		}

		if (inv.getStackInSlot(targetSlot) == null) {
			inv.setInventorySlotContents(targetSlot, new ItemStack(id, 0, itemstack.getItemDamage()));
		}

		int canStore = size;
		if (canStore > inv.getStackInSlot(targetSlot).getMaxStackSize() - inv.getStackInSlot(targetSlot).stackSize) {
			canStore = inv.getStackInSlot(targetSlot).getMaxStackSize() - inv.getStackInSlot(targetSlot).stackSize;
		}
		if (canStore > inv.getInventoryStackLimit() - inv.getStackInSlot(targetSlot).stackSize) {
			canStore = inv.getInventoryStackLimit() - inv.getStackInSlot(targetSlot).stackSize;
		}
		if (canStore == 0) {
			return size;
		} else {
			size -= canStore;
			inv.getStackInSlot(targetSlot).stackSize += canStore;
			return size;
		}
	}

	private static int getFirstEmptySlot(IInventory inv, ItemStack stackInserted) {
		for (int i = 0; i < inv.getSizeInventory(); i++) {
			if (inv.getStackInSlot(i) == null) {

				if (inv instanceof PC_ISpecialAccessInventory) {
					if (!((PC_ISpecialAccessInventory) inv).canInsertStackTo(i, stackInserted)) {
						continue;
					}
				}

				return i;
			}
		}

		return -1;
	}

	/**
	 * Store whole stack in inventory; works correctly with special inventory
	 * types
	 * 
	 * @param inventory the inventory we're storing into
	 * @param stack the stored stack
	 * @return stored completely
	 */
	public static boolean storeItemInInventory(IInventory inventory, ItemStack stack) {
		if (inventory instanceof TileEntityFurnace) {
			if (PC_Utils.isSmeltable(stack)) {
				return PC_InvUtils.storeItemInSlot(inventory, stack, 0);
			} else if (PC_Utils.isFuel(stack)) {
				return PC_InvUtils.storeItemInSlot(inventory, stack, 1);
			} else {
				return false;
			}
		}

		if (inventory instanceof TileEntityBrewingStand) {
			if (stack.itemID == Item.potion.shiftedIndex) {
				if (PC_InvUtils.storeItemInSlot(inventory, stack, 0)) {
					return true;
				}
				if (PC_InvUtils.storeItemInSlot(inventory, stack, 1)) {
					return true;
				}
				if (PC_InvUtils.storeItemInSlot(inventory, stack, 2)) {
					return true;
				}
				return false;
			} else {
				if (stack.getItem().isPotionIngredient()) {
					return PC_InvUtils.storeItemInSlot(inventory, stack, 3);
				}
				return false;
			}
		}

		if (inventory instanceof PC_ISpecialAccessInventory) {

			boolean result = ((PC_ISpecialAccessInventory) inventory).insertStackIntoInventory(stack);

			return result;
		}

		return PC_InvUtils.addItemStackToInventory(inventory, stack);
	}

	/**
	 * Check if given inventory is full
	 * 
	 * @param inv the ivnentory
	 * @return is full
	 */
	public static boolean isInventoryFull(IInventory inv) {
		if (inv == null) {
			return false;
		}

		if (inv instanceof PC_IStateReportingInventory) {
			return ((PC_IStateReportingInventory) inv).isContainerFull();
		}

		if (inv instanceof TileEntityFurnace) {
			return inv.getStackInSlot(1) != null && inv.getStackInSlot(1).stackSize == Math.min(inv.getInventoryStackLimit(), inv.getStackInSlot(1).getMaxStackSize());
		}

		for (int i = 0; i < inv.getSizeInventory(); i++) {
			if (inv.getStackInSlot(i) == null || inv.getStackInSlot(i).stackSize < Math.min(inv.getInventoryStackLimit(), inv.getStackInSlot(i).getMaxStackSize())) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Check if given inventory is empty
	 * 
	 * @param inv the inventory
	 * @return is empty
	 */
	public static boolean isInventoryEmpty(IInventory inv) {
		if (inv == null) {
			return true;
		}

		if (inv instanceof PC_IStateReportingInventory) {
			return ((PC_IStateReportingInventory) inv).isContainerEmpty();
		}

		if (inv instanceof TileEntityFurnace) {
			return inv.getStackInSlot(1) == null;
		}

		for (int i = 0; i < inv.getSizeInventory(); i++) {
			if (inv.getStackInSlot(i) != null) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Move stacks from one to another inventory
	 * 
	 * @param from source inventory
	 * @param to target inventory
	 * @return true if From inventory is smaller or equal to To inventory
	 */
	public static boolean moveStacks(IInventory from, IInventory to) {

		int copied = Math.min(from.getSizeInventory(), to.getSizeInventory());

		for (int i = 0; i < copied; i++) {
			to.setInventorySlotContents(i, from.getStackInSlot(i));
			from.setInventorySlotContents(i, null);
		}

		return from.getSizeInventory() <= to.getSizeInventory();
	}



	/**
	 * Get player's armour value.
	 * 
	 * @param player
	 * @return armour value
	 */
	public static int getPlayerArmourValue(EntityPlayerSP player) {
		return player.inventory.getTotalArmorValue();
	}

	/**
	 * Get fuel value of a stack's item
	 * 
	 * @param itemstack the stack
	 * @param strength value multiplier, 1.0 is the standard, furnace strength.
	 * @return fuel value
	 */
	public static int getFuelValue(ItemStack itemstack, double strength) {
		if (itemstack == null) {
			return 0;
		}
		int i = itemstack.getItem().shiftedIndex;
		if (i == Block.wood.blockID) {
			return (int) (4 * 300 * strength);
		}
		if (i < 256 && Block.blocksList[i].blockMaterial == Material.wood) {
			return (int) (300 * strength);
		}
		if (i == Item.stick.shiftedIndex) {
			return (int) (100 * strength);
		}
		if (i == Item.paper.shiftedIndex) {
			return (int) (150 * strength);
		}
		if (i == Item.coal.shiftedIndex) {
			return (int) (1600 * strength);
		}
		if (i == Item.bucketLava.shiftedIndex) {
			return (int) (20000 * strength);
		}
		if (i == Block.sapling.blockID) {
			return (int) (100 * strength);
		}
		if (i == Item.gunpowder.shiftedIndex) {
			return (int) (500 * strength);
		}
		if (i == Item.blazeRod.shiftedIndex) {
			return (int) (2400 * strength);
		}

		return (int) (ModLoader.addAllFuel(i, itemstack.getItemDamage()) * strength);
	}

	/**
	 * Count unique Power Crystals in the given inventory
	 * 
	 * @param inventory the inventory to search
	 * @return number of Power Crystals
	 */
	public static int countPowerCrystals(IInventory inventory) {
		boolean[] foundTable = { false, false, false, false, false, false, false, false };

		for (int i = 0; i < inventory.getSizeInventory(); i++) {
			if (inventory.getStackInSlot(i) != null && inventory.getStackInSlot(i).itemID == mod_PCcore.powerCrystal.blockID) {
				foundTable[MathHelper.clamp_int(inventory.getStackInSlot(i).getItemDamage(), 0, 7)] = true;
			}
		}

		int cnt = 0;
		for (int i = 0; i < 8; i++) {
			if (foundTable[i]) {
				cnt++;
			}
		}

		return cnt;
	}

	/**
	 * Go through an array of itemstacks and merge them to smallest possible
	 * count.<br>
	 * Great for inventory cleanup.
	 * 
	 * @param input original stacks array
	 * @return new stacks array
	 */
	public static ItemStack[] mergeStacks(ItemStack[] input) {

		List<ItemStack> list = stacksToList(input);

		mergeStacks(list);

		return stacksToArray(list);

	}


	/**
	 * Go through a list of itemstacks and merge them to smallest possible
	 * count.
	 * 
	 * @param input original list of stacks
	 */
	public static void mergeStacks(List<ItemStack> input) {

		if (input == null) {
			return;
		}

		for (ItemStack st1 : input) {

			if (st1 != null) {

				for (ItemStack st2 : input) {

					if (st2 != null && st2.isItemEqual(st1)) {
						int movedToFirst = Math.min(st2.stackSize, st1.getItem().maxStackSize - st1.stackSize);
						if (movedToFirst <= 0) {
							break;
						}

						st1.stackSize += movedToFirst;
						st2.stackSize -= movedToFirst;
					}

				}
			}
		}

		ArrayList<ItemStack> copy = new ArrayList<ItemStack>(input);
		for (int i = copy.size() - 1; i >= 0; i--) {
			if (copy.get(i) == null || copy.get(i).stackSize <= 0) {
				input.remove(i);
			}
		}

	}

	/**
	 * Convert ItemStack[] to List&lt;ItemStack&gt;
	 * 
	 * @param stacks input ItemStack[]
	 * @return output List&lt;ItemStack&gt;
	 */
	public static List<ItemStack> stacksToList(ItemStack[] stacks) {
		ArrayList<ItemStack> myList = new ArrayList<ItemStack>();

		Collections.addAll(myList, stacks);

		return myList;
	}

	/**
	 * Convert List&lt;ItemStack&gt; to ItemStack[]
	 * 
	 * @param stacks input List&lt;ItemStack&gt;
	 * @return output ItemStack[]
	 */
	public static ItemStack[] stacksToArray(List<ItemStack> stacks) {
		return stacks.toArray(new ItemStack[stacks.size()]);
	}
}
