/**
 * This software is provided under the terms of the Minecraft Forge Public
 * License v1.0.
 */

package net.minecraftforge.common;

import java.util.ArrayList;
import java.util.Arrays;

import net.minecraft.util.DamageSource;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemStack;

/**
 * This interface is to be implemented by ItemArmor classes. It will allow to
 * modify computation of damage and health loss. Computation will be called
 * before the actual armor computation, which can then be cancelled.
 *
 * @see ItemArmor
 */
public interface ISpecialArmor
{
	/**
	 * Retrieves the modifiers to be used when calculating armor damage.
	 *
	 * Armor will higher priority will have damage applied to them before
	 * lower priority ones. If there are multiple pieces of armor with the
	 * same priority, damage will be distributed between them based on there
	 * absorption ratio.
	 *
	 * @param player The entity wearing the armor.
	 * @param armor The ItemStack of the armor item itself.
	 * @param source The source of the damage, which can be used to alter armor
	 *     properties based on the type or source of damage.
	 * @param damage The total damage being applied to the entity
	 * @param slot The armor slot the item is in.
	 * @return A ArmorProperties instance holding information about how the armor effects damage.
	 */
	public ArmorProperties getProperties(EntityLivingBase player, ItemStack armor, DamageSource source, double damage, int slot);

	/**
	 * Get the displayed effective armor.
	 *
	 * @param player The player wearing the armor.
	 * @param armor The ItemStack of the armor item itself.
	 * @param slot The armor slot the item is in.
	 * @return The number of armor points for display, 2 per shield.
	 */
	public abstract int getArmorDisplay(EntityPlayer player, ItemStack armor, int slot);

	/**
	 * Applies damage to the ItemStack. The mod is responsible for reducing the
	 * item durability and stack size. If the stack is depleted it will be cleaned
	 * up automatically.
	 *
	 * @param entity The entity wearing the armor
	 * @param stack The ItemStack of the armor item itself.
	 * @param source The source of the damage, which can be used to alter armor
	 *     properties based on the type or source of damage.
	 * @param damage The amount of damage being applied to the armor
	 * @param slot The armor slot the item is in.
	 */
	public abstract void damageArmor(EntityLivingBase entity, ItemStack stack, DamageSource source, int damage, int slot);
	
	public static class ArmorProperties implements Comparable<ArmorProperties>
	{
		public int    Priority    = 0;
		public int    AbsorbMax   = Integer.MAX_VALUE;
		public double AbsorbRatio = 0;
		public int    Slot        = 0;
		private static final boolean DEBUG = false; //Only enable this if you wish to be spamed with debugging information.
													//Left it in because I figured it'd be useful for modders developing custom armor.

		public ArmorProperties(int priority, double ratio, int max)
		{
			Priority    = priority;
			AbsorbRatio = ratio;
			AbsorbMax   = max;
		}

		/**
		 * Gathers and applies armor reduction to damage being dealt to a entity.
		 *
		 * @param entity The Entity being damage
		 * @param inventory An array of armor items
		 * @param source The damage source type
		 * @param damage The total damage being done
		 * @return The left over damage that has not been absorbed by the armor
		 */
		public static float ApplyArmor(EntityLivingBase entity, ItemStack[] inventory, DamageSource source, double damage)
		{
			if (DEBUG)
			{
				System.out.println("Start: " + damage + " " + (damage * 25));
			}
			damage *= 25;
			ArrayList<ArmorProperties> dmgVals = new ArrayList<ArmorProperties>();
			for (int x = 0; x < inventory.length; x++)
			{
				ItemStack stack = inventory[x];
				if (stack == null)
				{
					continue;
				}
				ArmorProperties prop = null;
				if (stack.getItem() instanceof ISpecialArmor)
				{
					ISpecialArmor armor = (ISpecialArmor)stack.getItem();
					prop = armor.getProperties(entity, stack, source, damage / 25D, x).copy();
				}
				else if (stack.getItem() instanceof ItemArmor && !source.isUnblockable())
				{
					ItemArmor armor = (ItemArmor)stack.getItem();
					prop = new ArmorProperties(0, armor.damageReduceAmount / 25D, armor.getMaxDamage() + 1 - stack.getItemDamage());
				}
				if (prop != null)
				{
					prop.Slot = x;
					dmgVals.add(prop);
				}
			}
			if (dmgVals.size() > 0)
			{
				ArmorProperties[] props = dmgVals.toArray(new ArmorProperties[dmgVals.size()]);
				StandardizeList(props, damage);
				int level = props[0].Priority;
				double ratio = 0;
				for (ArmorProperties prop : props)
				{
					if (level != prop.Priority)
					{
						damage -= (damage * ratio);
						ratio = 0;
						level = prop.Priority;
					}
					ratio += prop.AbsorbRatio;

					double absorb = damage * prop.AbsorbRatio;
					if (absorb > 0)
					{
						ItemStack stack = inventory[prop.Slot];
						int itemDamage = (int)(absorb / 25D < 1 ? 1 : absorb / 25D);
						if (stack.getItem() instanceof ISpecialArmor)
						{
							((ISpecialArmor)stack.getItem()).damageArmor(entity, stack, source, itemDamage, prop.Slot);
						}
						else
						{
							if (DEBUG)
							{
								System.out.println("Item: " + stack.toString() + " Absorbed: " + (absorb / 25D) + " Damaged: " + itemDamage);
							}
							stack.damageItem(itemDamage, entity);
						}
						if (stack.stackSize <= 0)
						{
							/*if (entity instanceof EntityPlayer)
							{
								stack.onItemDestroyedByUse((EntityPlayer)entity);
							}*/
							inventory[prop.Slot] = null;
						}
					}
				}
				damage -= (damage * ratio);
			}
			if (DEBUG)
			{
				System.out.println("Return: " + (int)(damage / 25.0F) + " " + damage);
			}
			return (float)(damage / 25.0F);
		}

		/**
		 * Sorts and standardizes the distribution of damage over armor.
		 *
		 * @param armor The armor information
		 * @param damage The total damage being received
		 */
		private static void StandardizeList(ArmorProperties[] armor, double damage)
		{
			Arrays.sort(armor);

			int     start     = 0;
			double  total     = 0;
			int     priority  = armor[0].Priority;
			int     pStart    = 0;
			boolean pChange   = false;
			boolean pFinished = false;

			if (DEBUG)
			{
				for (ArmorProperties prop : armor)
				{
					System.out.println(prop);
				}
				System.out.println("========================");
			}

			for (int x = 0; x < armor.length; x++)
			{
				total += armor[x].AbsorbRatio;
				if (x == armor.length - 1 || armor[x].Priority != priority)
				{
					if (armor[x].Priority != priority)
					{
						total -= armor[x].AbsorbRatio;
						x--;
						pChange = true;
					}
					if (total > 1)
					{
						for (int y = start; y <= x; y++)
						{
							double newRatio = armor[y].AbsorbRatio / total;
							if (newRatio * damage > armor[y].AbsorbMax)
							{
								armor[y].AbsorbRatio = (double)armor[y].AbsorbMax / damage;
								total = 0;
								for (int z = pStart; z <= y; z++)
								{
									total += armor[z].AbsorbRatio;
								}
								start = y + 1;
								x = y;
								break;
							}
							else
							{
								armor[y].AbsorbRatio = newRatio;
								pFinished = true;
							}
						}
						if (pChange && pFinished)
						{
							damage -= (damage * total);
							total = 0;
							start = x + 1;
							priority = armor[start].Priority;
							pStart = start;
							pChange = false;
							pFinished = false;
							if (damage <= 0)
							{
								for (int y = x + 1; y < armor.length; y++)
								{
									armor[y].AbsorbRatio = 0;
								}
								break;
							}
						}
					}
					else
					{
						for (int y = start; y <= x; y++)
						{
							total -= armor[y].AbsorbRatio;
							if (damage * armor[y].AbsorbRatio > armor[y].AbsorbMax)
							{
								armor[y].AbsorbRatio = (double)armor[y].AbsorbMax / damage;
							}
							total += armor[y].AbsorbRatio;
						}
						damage -= (damage * total);
						total = 0;
						if (x != armor.length - 1)
						{
							start = x + 1;
							priority = armor[start].Priority;
							pStart = start;
							pChange = false;
							if (damage <= 0)
							{
								for (int y = x + 1; y < armor.length; y++)
								{
									armor[y].AbsorbRatio = 0;
								}
								break;
							}
						}
					}
				}
			}
			if (DEBUG)
			{
				for (ArmorProperties prop : armor)
				{
					System.out.println(prop);
				}
			}
		}

		@Override
		public int compareTo(ArmorProperties o)
		{
			if (o.Priority != Priority)
			{
				return o.Priority - Priority;
			}
			double left =  (  AbsorbRatio == 0 ? 0 :   AbsorbMax * 100.0D /   AbsorbRatio);
			double right = (o.AbsorbRatio == 0 ? 0 : o.AbsorbMax * 100.0D / o.AbsorbRatio);
			return (int)(left - right);
		}

		@Override
		public String toString()
		{
			return String.format("%d, %d, %f, %d", Priority, AbsorbMax, AbsorbRatio, (AbsorbRatio == 0 ? 0 : (int)(AbsorbMax * 100.0D / AbsorbRatio)));
		}

		public ArmorProperties copy()
		{
			return new ArmorProperties(Priority, AbsorbRatio, AbsorbMax);
		}
	}
}
