package net.minecraft.src;

import java.lang.reflect.Array;
import java.util.*;
import java.util.Hashtable;
import java.util.List;

/**
 * Direct Crafting slot, used in Crafting Tool.<br>
 * "It works, and that's important. No matter how and why."
 * 
 * @author MightyPork
 * @copy (c) 2012
 * 
 */
public class PCco_SlotDirectCrafting extends Slot {
	private EntityPlayer thePlayer;
	/** Stack craftable from this slot. Read only, please. */
	public ItemStack product;
	private static final int RECURSION_LIMIT = 50;

	/**
	 * @param entityplayer the Player
	 * @param product product stack (Must be in the same size as crafted from the recipe!)
	 * @param index slot index
	 * @param x slot position X
	 * @param y slot position Y
	 */
	public PCco_SlotDirectCrafting(EntityPlayer entityplayer, ItemStack product, int index, int x, int y) {
		super(null, index, x, y);
		thePlayer = entityplayer;
		this.product = product;
	}

	@Override
	public boolean isItemValid(ItemStack itemstack) {
		return false;
	}

	@Override
	public void onPickupFromSlot(ItemStack itemstack) {
		// itemstack.onCrafting(thePlayer.worldObj, thePlayer);
		super.onPickupFromSlot(itemstack);

		doCrafting(product);
	}

	@Override
	public ItemStack decrStackSize(int i) {
		if (isAvailable()) {
			return product.copy();
		}
		return null;
	}

	@Override
	public ItemStack getStack() {
		if (isAvailable()) { return product.copy(); }
		return null;
	}

	@Override
	public void putStack(ItemStack itemstack) {
		product = itemstack;
	}

	@Override
	public void onSlotChanged() {}

	@Override
	public int getSlotStackLimit() {
		if (product == null) { return 1; }
		return product.getMaxStackSize();
	}

	/**
	 * @return true if the item can be crafted
	 */
	private boolean isAvailable() {
		Profiler.endSection();
		Profiler.startSection("GUI");
		if (product == null) { return false; }

		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return true; }

		lastRecipe = -1;
		recipeEndReached = false;
		while (true) {
			IRecipe irecipe = getNextRecipeForProduct(product);
			if (irecipe == null || recipeEndReached) {
				return false;
			}
			recursionCount = 0;
			allocatedStacks.clear();
			toConsume.clear();
			toGiveBack.clear();
			stacksSeekedInThisRecursion.clear();
			if (tryToFindCraftingSequenceForProduct(irecipe, product.isItemEqual(new ItemStack(Block.blockSteel)), ">>>")) {
				if(toConsume.size() == 1 && toConsume.get(0).isItemEqual(product) || (toConsume.get(0).itemID == product.itemID && toConsume.get(0).getItemDamage() == -1)) return false;
				return true;
			}
		}
	}

	private boolean doCrafting(ItemStack prod) {
		if (prod == null) { return false; }

		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return true; }

		lastRecipe = -1;
		recipeEndReached = false;
		while (true) {
			IRecipe irecipe = getNextRecipeForProduct(prod);
			if (irecipe == null || recipeEndReached) { return false; }
			recursionCount = 0;
			allocatedStacks.clear();
			toConsume.clear();
			toGiveBack.clear();
			stacksSeekedInThisRecursion.clear();
			if (tryToFindCraftingSequenceForProduct(irecipe, true, ">>>")) {
				for (ItemStack stack : toConsume) {
					System.out.println("Consuming "+stack);
					consumePlayerItems(stack, stack.stackSize);
				}
				
				if(irecipe.getRecipeOutput().stackSize > product.stackSize){
					toGiveBack.add(new ItemStack(irecipe.getRecipeOutput().itemID, irecipe.getRecipeOutput().stackSize - product.stackSize, product.getItemDamage()));
				}

				for (ItemStack stack : toGiveBack) {
					System.out.println("Giving back "+stack);
					thePlayer.inventory.addItemStackToInventory(stack);
					if (stack.stackSize > 0) {
						thePlayer.dropPlayerItem(stack);
					}
				}
				
				return true;
			}
		}
	}

	private String getStackDescriptor(ItemStack stack) {
		return Item.itemsList[stack.itemID].getItemName() + "@" + stack.getItemDamage();
	}

	// stackName@meta, count
	private Hashtable<String, Integer> allocatedStacks = new Hashtable<String, Integer>();

	private boolean allocatePlayerItems(ItemStack stack1, int needed) {
		if (stack1 == null) { return true; }

		Integer alloc = allocatedStacks.get(getStackDescriptor(stack1));
		if (alloc == null) alloc = 0;

		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return true; }

		int counter = 0;
		for (int i = 0; i < thePlayer.inventory.getSizeInventory(); i++) {
			ItemStack curStack = thePlayer.inventory.getStackInSlot(i);
			if (curStack != null && (curStack.isItemEqual(stack1) || (curStack.itemID == stack1.itemID && stack1.getItemDamage() == -1))) {
				counter += curStack.stackSize;


				if (counter - alloc >= needed) {
					allocatedStacks.put(getStackDescriptor(stack1), alloc + needed);
					return true;
				}
			}
		}

		if (counter - alloc >= needed) {
			allocatedStacks.put(getStackDescriptor(stack1), alloc + needed);
			return true;
		}
		return false;
	}
	
	private int countPlayerItems(ItemStack wanted) {
		if (wanted == null) { return 0; }

		Integer alloc = allocatedStacks.get(getStackDescriptor(wanted));
		if (alloc == null) alloc = 0;

		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return -1; }

		int counter = 0;
		for (int i = 0; i < thePlayer.inventory.getSizeInventory(); i++) {
			ItemStack curStack = thePlayer.inventory.getStackInSlot(i);
			if (curStack != null && (curStack.isItemEqual(wanted) || (curStack.itemID == wanted.itemID && wanted.getItemDamage() == -1))) {
				counter += curStack.stackSize;
			}
		}

		return counter - alloc;
	}
	
	

	private boolean consumePlayerItems(ItemStack stack1, int count) {

		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return true; }

		if (stack1 == null) { return true; }

		for (int i = 0; i < thePlayer.inventory.getSizeInventory(); i++) {
			ItemStack curStack = thePlayer.inventory.getStackInSlot(i);
			if (curStack != null && (curStack.isItemEqual(stack1) || (curStack.itemID == stack1.itemID && stack1.getItemDamage() == -1))) {
				if (curStack.stackSize > count) {
					curStack.stackSize -= count;
					return true;
				} else if (curStack.stackSize <= count) {
					count -= curStack.stackSize;
					thePlayer.inventory.setInventorySlotContents(i, null);
				}
			}
		}
		if (count > 0) { return false; }
		return true;
	}

	private int lastRecipe = -1;
	private boolean recipeEndReached = false;

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private IRecipe getNextRecipeForProduct(final ItemStack prod) {
		List<IRecipe> recipes = new ArrayList(CraftingManager.getInstance().getRecipeList());
		
		Collections.sort(recipes, new Comparator<IRecipe>(){
			@Override
			public int compare(IRecipe a, IRecipe b) {
				if(a.getRecipeOutput().stackSize == prod.stackSize && b.getRecipeOutput().stackSize != prod.stackSize) return -1;
				if(a.getRecipeOutput().stackSize != prod.stackSize && b.getRecipeOutput().stackSize == prod.stackSize) return 1;
				
				if((a instanceof ShapelessRecipes) && !(b instanceof ShapelessRecipes)) return -1;
				if(!(a instanceof ShapelessRecipes) && (b instanceof ShapelessRecipes)) return 1;
				
				if(a.getRecipeSize() > b.getRecipeSize()) return 1;
				if(a.getRecipeSize() < b.getRecipeSize()) return -1;
				return 0;
			}			
		});

		if (lastRecipe == recipes.size() - 1) { return null; }

		int k;
		for (k = lastRecipe + 1; k < recipes.size(); k++) {
			IRecipe irecipe = recipes.get(k);
			try {
				if (irecipe.getRecipeOutput().isItemEqual(prod)
						|| (irecipe.getRecipeOutput().itemID == prod.itemID && prod.getItemDamage() == -1)) {
					lastRecipe = k;
					return irecipe;
				}
			} catch (NullPointerException npe) {
				continue;
			}
		}

		recipeEndReached = true;
		lastRecipe = k;
		return null;
	}

	private int recursionCount = 0;

	private ArrayList<String> stacksSeekedInThisRecursion = new ArrayList<String>();

	private ArrayList<ItemStack> toConsume = new ArrayList<ItemStack>();
	private ArrayList<ItemStack> toGiveBack = new ArrayList<ItemStack>();

	@SuppressWarnings("unchecked")
	private boolean tryToFindCraftingSequenceForProduct(IRecipe irecipe, boolean verbose, String rec) {
		
		if(verbose && recursionCount == 0) System.out.println(rec+" \n========================================");
		
		long time=System.nanoTime();
		if(verbose) System.out.println("\n"+rec+" ****** Starting tryToFindCraftingSequenceForProduct "+irecipe.getRecipeOutput()+" time "+time);
		
		if (ModLoader.getMinecraftInstance().playerController.isInCreativeMode() || mod_PCcore.survival_cheating) { return true; }
		
		recursionCount++;
		if (recursionCount > RECURSION_LIMIT) {
			if (verbose) System.out.println(rec+" RECURSION LIMIT");
			return false;
		}
		try {

			ItemStack[] tmps;
			if (irecipe instanceof ShapedRecipes) {
				tmps = (ItemStack[]) ModLoader.getPrivateValue(net.minecraft.src.ShapedRecipes.class, irecipe, 2);
			} else if (irecipe instanceof ShapelessRecipes) {
				List<ItemStack> foo = ((List<ItemStack>) ModLoader.getPrivateValue(net.minecraft.src.ShapelessRecipes.class, irecipe, 1));
				tmps = foo.toArray(new ItemStack[foo.size()]);
			} else {
				return false;
			}

			ItemStack[] recipeStacks = new ItemStack[tmps.length];

			for (int i = 0; i < tmps.length; i++) {
				if (tmps[i] != null) {
					recipeStacks[i] = tmps[i].copy();
				}
			}

			for (int i = 0; i < recipeStacks.length; i++) {
				if (recipeStacks[i] != null) {
					recipeStacks[i].stackSize = 1;
					for (int j = i + 1; j < recipeStacks.length; j++) {
						if (recipeStacks[j] != null && recipeStacks[j].isItemEqual(recipeStacks[i])) {
							recipeStacks[i].stackSize++;
							recipeStacks[j] = null;
						}
					}
				}
			}

			if(verbose) System.out.println(rec+" recloop.");
			
			recloop:
			for (int i = 0; i < recipeStacks.length; i++) {
				if(verbose) System.out.println(rec+" position in recipe "+i+" -> "+recipeStacks[i]+" .. time "+time);
				if (recipeStacks[i] == null) {
					continue recloop;
				}
				
				if(verbose) System.out.println(rec +" -- trying to consume stuff for "+i+" "+recipeStacks[i]);
				if (!allocatePlayerItems(recipeStacks[i], recipeStacks[i].stackSize)) {

					if (!mod_PCcore.recursive_crafting) { return false; }

					if(verbose){
						System.out.println(rec+" can't consume all directly "+i);
					}
					
					if (stacksSeekedInThisRecursion.contains(getStackDescriptor(recipeStacks[i]))) {
						if(verbose) System.out.println(rec+" already tried to find "+recipeStacks[i]);
						return false;
					}

					if (recursionCount > RECURSION_LIMIT) {
						System.out.println(rec+" RECURSION LIMIT");
						return false;
					}

					int stacked = lastRecipe;


					lastRecipe = -1;
					
					ItemStack needed = recipeStacks[i].copy();
					
					int availableDirectly = countPlayerItems(needed);
					
					if(availableDirectly>0){
						needed.stackSize -= availableDirectly;
						Integer alloc = allocatedStacks.get(getStackDescriptor(needed));
						if (alloc == null) alloc = 0;
						allocatedStacks.put(getStackDescriptor(needed), alloc + availableDirectly);
						ItemStack consumed = new ItemStack(needed.itemID, availableDirectly, needed.getItemDamage());
						if(verbose) System.out.println(rec+" ADDING TO CONSUME LIST consumed = "+consumed);
						toConsume.add(consumed);
						
						if(verbose) if(availableDirectly > 0) System.out.println(rec+" ** "+needed+"available directly "+availableDirectly);
					
						if(needed.stackSize <= 0){
							lastRecipe = stacked;
							System.out.println(rec+" finished, continuing..");
							continue recloop;
						}
					}
					
					
					boolean anymeta = false;
					int tmpmeta = 0;
					if (needed.getItemDamage() < 0) {
						needed.setItemDamage(tmpmeta);
						anymeta = true;
					}

					while (true) {
						if(verbose) System.out.println(rec+" We want "+needed);
						IRecipe irecipe2 = getNextRecipeForProduct(needed);
						if (irecipe2 == null) {
							if (anymeta && tmpmeta <= 15) {
								needed.setItemDamage(++tmpmeta);
								lastRecipe = -1;
								continue;
							} else {
								if(verbose) System.out.println(rec+" out of recipes");
								lastRecipe = stacked;
								return false;
							}
						}

						innerloop:
						while (true) {
							stacksSeekedInThisRecursion.add(getStackDescriptor(recipeStacks[i]));
							// ###### RECURSION ######
							if (tryToFindCraftingSequenceForProduct(irecipe2, verbose, rec+"-###")) {

								if(verbose) System.out.println(rec+" found recipe with output "+irecipe2.getRecipeOutput());
								
								stacksSeekedInThisRecursion.remove(getStackDescriptor(recipeStacks[i]));

								needed.stackSize -= irecipe2.getRecipeOutput().stackSize;

								if (needed.stackSize > 0) {
									
									//lastRecipe--;
									if(verbose) System.out.println(rec+" not all what needed, continuing inerloop (trying to use it again). Remains "+needed.stackSize);
									continue innerloop;
									
								} else if (needed.stackSize < 0) {
									ItemStack got = needed.copy();
									got.stackSize = Math.abs(got.stackSize);
									if (got.getItemDamage() < 0) got.setItemDamage(0);
									toGiveBack.add(got);
									if(verbose) System.out.println(rec+" something is not used, give back -> "+got);
								}

								if(verbose) System.out.println(rec+" continuing to next main recipe entry.");
								
								lastRecipe = stacked;
								continue recloop;
							} else {
								if(verbose) System.out.println(rec+" no more can be crafted using this recipe.");
								break;
							}
						}
					}
				} else {					
					if(verbose) System.out.println(rec+" ADDING TO CONSUME LIST recipeStacks["+i+"] = "+recipeStacks[i]);
					toConsume.add(recipeStacks[i]);
				}
			}
			if(verbose) System.out.println(rec+"recipe finsiehd. true.");
			
			
			
			return true;

		} catch (IllegalArgumentException et) {
			et.printStackTrace();
		} catch (SecurityException es) {
			es.printStackTrace();
		} catch (NoSuchFieldException en) {
			en.printStackTrace();
		}
		if(verbose) System.out.println(rec+"recipe finsiehd. false.");
		return false;
	}

}
