package common.item;

import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import common.attributes.Attribute;
import common.attributes.UsageSlot;
import common.block.Block;
import common.collect.Lists;
import common.collect.Maps;
import common.color.TextColor;
import common.enchantment.Enchantment;
import common.enchantment.EnchantmentHelper;
import common.entity.npc.EntityNPC;
import common.entity.types.EntityLiving;
import common.init.ItemRegistry;
import common.item.tool.ItemBow;
import common.rng.Random;
import common.tags.TagObject;
import common.util.BlockPos;
import common.util.Facing;
import common.world.World;

public final class ItemStack
{
    public static final int MAX_SIZE = 67108864;
    private static final DecimalFormat DECIMALFORMAT = new DecimalFormat("#.###");

    public int size;
    private Item item;
    private TagObject tag;

    public ItemStack(Item item)
    {
        this(item, 1);
    }

    public ItemStack(Item item, int amount)
    {
        this.item = item;
        this.size = amount;
    }
    
    public static String formatAmount(int amount) {
    	if(amount < 0)
    		return amount <= -1000000000 ? (amount / 1000000000) + "B" : ("-" + formatAmount(-amount));
    	if(amount < 1000)
    		return "" + amount;
    	else if(amount < 100000)
    		return (amount / 1000) + "K";
    	else if(amount < 1000000)
    		return "." + (amount / 100000) + "M";
    	else if(amount < 100000000)
    		return (amount / 1000000) + "M";
    	else if(amount < 1000000000)
    		return "." + (amount / 100000000) + "B";
    	else
    		return (amount / 1000000000) + "B+";
    }

    public static ItemStack readFromTag(TagObject tag)
    {
        ItemStack itemstack = new ItemStack();
        itemstack.readTags(tag);
        return itemstack.getItem() != null ? itemstack : null;
    }

	public static ItemStack getStack(String name, ItemStack def) {
		if(name == null)
			return def;
		Item item = ItemRegistry.REGISTRY.byNameExact(name);
		return item == null ? def : new ItemStack(item);
	}

    private ItemStack()
    {
    }

    /**
     * Splits off a stack of the given amount of this stack and reduces this stack by the amount.
     */
    public ItemStack splitStack(int amount)
    {
        ItemStack itemstack = new ItemStack(this.item, amount);

        if (this.tag != null)
        {
            itemstack.tag = this.tag.copy();
        }

        this.size -= amount;
        return itemstack;
    }

    /**
     * Returns the object corresponding to the stack.
     */
    public Item getItem()
    {
        return this.item;
    }

    /**
     * Called when the player uses this ItemStack on a Block (right-click). Places blocks, etc. (Legacy name:
     * tryPlaceItemIntoWorld)
     */
    public boolean onItemUse(EntityNPC playerIn, World worldIn, BlockPos pos, Facing side, float hitX, float hitY, float hitZ)
    {
        boolean flag = this.getItem().onItemUse(this, playerIn, worldIn, pos, side, hitX, hitY, hitZ);

//        if (flag)
//        {
//            playerIn.triggerAchievement(StatRegistry.objectUseStats[ItemRegistry.getIdFromItem(this.item)]);
//        }

        return flag;
    }

    public float getStrVsBlock(Block blockIn)
    {
        return this.getItem().getStrVsBlock(this, blockIn);
    }

    /**
     * Called whenever this item stack is equipped and right clicked. Returns the new item stack to put in the position
     * where this item is. Args: world, player
     */
    public ItemStack useItemRightClick(World worldIn, EntityNPC playerIn)
    {
        return this.getItem().onItemRightClick(this, worldIn, playerIn);
    }

    /**
     * Called when the item in use count reach 0, e.g. item food eaten. Return the new ItemStack. Args : world, entity
     */
    public ItemStack onItemUseFinish(World worldIn, EntityNPC playerIn)
    {
        return this.getItem().onItemUseFinish(this, worldIn, playerIn);
    }

    public TagObject writeTags(TagObject tag)
    {
    	if(this.item != null)
    		tag.setString("id", ItemRegistry.getNameFromItem(this.item));
        if(this.size != 1)
        	tag.setInt("size", this.size);
        if(this.tag != null)
            tag.setObject("tag", this.tag);
        return tag;
    }

    private void readTags(TagObject tag)
    {
        this.item = tag.hasString("id") ? ItemRegistry.getRegisteredItem(tag.getString("id")) : null;
        this.size = tag.hasInt("size") ? tag.getInt("size") : 1;
        this.tag = tag.hasObject("tag") ? tag.getObject("tag") : null;
    }

    public int getMaxStackSize()
    {
        return this.getItem().getItemStackLimit();
    }

    public boolean isStackable()
    {
        return this.getMaxStackSize() > 1 && (!this.isItemStackDamageable() || !this.isItemDamaged());
    }

    public boolean isItemStackDamageable()
    {
        return this.item != null && this.item.getMaxDamage() > 0;
    }

    public boolean isItemDamaged()
    {
        return this.isItemStackDamageable() && this.getItemDamage() > 0;
    }

    public int getItemDamage()
    {
        return this.isItemStackDamageable() && this.tag != null && this.tag.hasInt("dmg") ? this.tag.getInt("dmg") : 0;
    }

    public void setItemDamage(int damage)
    {
    	if(!this.isItemStackDamageable())
    		return;
    	if(damage <= 0) {
    		if(this.tag != null) {
    			this.tag.remove("dmg");
    			if(this.tag.isEmpty())
    				this.tag = null;
    		}
    	}
    	else {
	        if(this.tag == null)
	        	this.tag = new TagObject();
	        this.tag.setInt("dmg", damage);
    	}
    }

    public int getMaxDamage()
    {
        return this.item.getMaxDamage();
    }

    /**
     * Attempts to damage the ItemStack with par1 amount of damage, If the ItemStack has the Unbreaking enchantment
     * there is a chance for each point of damage to be negated. Returns true if it takes more damage than
     * getMaxDamage(). Returns false otherwise or if the ItemStack can't be damaged or if all points of damage are
     * negated.
     */
    public boolean attemptDamageItem(int amount, Random rand)
    {
        if (!this.isItemStackDamageable())
        {
            return false;
        }
        else
        {
            if (amount > 0)
            {
                int i = EnchantmentHelper.getEnchantmentLevel(Enchantment.UNBREAKING, this);
                int j = 0;

                for (int k = 0; i > 0 && k < amount; ++k)
                {
                    if (Enchantment.negateDamage(this, i, rand))
                    {
                        ++j;
                    }
                }

                amount -= j;

                if (amount <= 0)
                {
                    return false;
                }
            }

            int damage = this.getItemDamage() + amount;
            this.setItemDamage(damage);
            return damage > this.getMaxDamage();
        }
    }

    public void damageItem(int amount, EntityLiving entityIn)
    {
        if (this.isItemStackDamageable())
        {
            if (this.attemptDamageItem(amount, entityIn.getRNG()))
            {
                entityIn.renderBrokenItemStack(this);
                --this.size;

                if (entityIn.isPlayer())
                {
                    EntityNPC entityplayer = (EntityNPC)entityIn;

                    if (this.size == 0 && this.getItem() instanceof ItemBow)
                    {
                        entityplayer.destroyCurrentEquippedItem();
                    }
                }

                if (this.size < 0)
                {
                    this.size = 0;
                }

                this.setItemDamage(0);
            }
        }
    }

    /**
     * Calls the corresponding fct in di
     */
    public void hitEntity(EntityLiving entityIn, EntityNPC playerIn)
    {
        boolean flag = this.item.hitEntity(this, entityIn, playerIn);

//        if (flag)
//        {
//            playerIn.triggerAchievement(StatRegistry.objectUseStats[ItemRegistry.getIdFromItem(this.item)]);
//        }
    }

    /**
     * Called when a Block is destroyed using this ItemStack
     */
    public void onBlockDestroyed(World worldIn, Block blockIn, BlockPos pos, EntityNPC playerIn)
    {
        boolean flag = this.item.onBlockDestroyed(this, worldIn, blockIn, pos, playerIn);

//        if (flag)
//        {
//            playerIn.triggerAchievement(StatRegistry.objectUseStats[ItemRegistry.getIdFromItem(this.item)]);
//        }
    }

    /**
     * Check whether the given Block can be harvested using this ItemStack.
     */
    public boolean canHarvestBlock(Block blockIn)
    {
        return this.item.canHarvestBlock(blockIn);
    }

    public boolean interactWithEntity(EntityNPC playerIn, EntityLiving entityIn)
    {
        return this.item.itemInteractionForEntity(this, playerIn, entityIn);
    }

    public ItemStack copy()
    {
        ItemStack itemstack = new ItemStack(this.item, this.size);

        if (this.tag != null)
        {
            itemstack.tag = this.tag.copy();
        }

        return itemstack;
    }

    public static boolean areItemStackTagsEqual(ItemStack stackA, ItemStack stackB)
    {
        return stackA == null && stackB == null ? true : (stackA != null && stackB != null ? (stackA.tag == null && stackB.tag != null ? false : stackA.tag == null || stackA.tag.equals(stackB.tag)) : false);
    }

    /**
     * compares ItemStack argument1 with ItemStack argument2; returns true if both ItemStacks are equal
     */
    public static boolean areItemStacksEqual(ItemStack stackA, ItemStack stackB)
    {
        return stackA == null && stackB == null ? true : (stackA != null && stackB != null ? stackA.isItemStackEqual(stackB) : false);
    }

    /**
     * compares ItemStack argument to the instance ItemStack; returns true if both ItemStacks are equal
     */
    private boolean isItemStackEqual(ItemStack other)
    {
        return this.size != other.size ? false : (this.item != other.item ? false : (this.tag == null && other.tag != null ? false : this.tag == null || this.tag.equals(other.tag)));
    }

    /**
     * Compares Item and damage value of the two stacks
     */
    public static boolean areItemsEqual(ItemStack stackA, ItemStack stackB)
    {
        return stackA == null && stackB == null ? true : (stackA != null && stackB != null ? stackA.isItemEqual(stackB) : false);
    }

    /**
     * compares ItemStack argument to the instance ItemStack; returns true if the Items contained in both ItemStacks are
     * equal
     */
    public boolean isItemEqual(ItemStack other)
    {
        return other != null && this.item == other.item;
    }

//    public String getUnlocalizedName()
//    {
//        return this.item.getUnlocalizedName(this);
//    }

    /**
     * Creates a copy of a ItemStack, a null parameters will return a null.
     */
    public static ItemStack copyItemStack(ItemStack stack)
    {
        return stack == null ? null : stack.copy();
    }

//    public String toString()
//    {
//        return this.stackSize + "x" + this.item.getUnlocalizedName() + "@" + this.itemDamage;
//    }

//    /**
//     * Called each tick as long the ItemStack in on player inventory. Used to progress the pickup animation and update
//     * maps.
//     */
//    public void updateAnimation(World worldIn, Entity entityIn, int inventorySlot, boolean isCurrentItem)
//    {
//        if (this.animationsToGo > 0)
//        {
//            --this.animationsToGo;
//        }
//
//        this.item.onUpdate(this, worldIn, entityIn, inventorySlot, isCurrentItem);
//    }
//
//    public void onCrafting(World worldIn, EntityNPC playerIn, int amount)
//    {
////        playerIn.addStat(StatRegistry.objectCraftStats[ItemRegistry.getIdFromItem(this.item)], amount);
//        this.item.onCreated(this, worldIn, playerIn);
//    }

    public boolean getIsItemStackEqual(ItemStack p_179549_1_)
    {
        return this.isItemStackEqual(p_179549_1_);
    }

    public int getMaxItemUseDuration()
    {
        return this.getItem().getMaxItemUseDuration(this);
    }

    public ItemAction getItemUseAction()
    {
        return this.getItem().getItemUseAction(this);
    }

    public ItemAction getItemPosition()
    {
        return this.getItem().getItemPosition(this);
    }

    /**
     * Called when the player releases the use item button. Args: world, entityplayer, itemInUseCount
     */
    public void onPlayerStoppedUsing(World worldIn, EntityNPC playerIn, int timeLeft)
    {
        this.getItem().onPlayerStoppedUsing(this, worldIn, playerIn, timeLeft);
    }

    /**
     * Returns true if the ItemStack has an NBTTagCompound. Currently used to store enchantments.
     */
    public boolean hasTagCompound()
    {
        return this.tag != null;
    }

    /**
     * Returns the NBTTagCompound of the ItemStack.
     */
    public TagObject getTagCompound()
    {
        return this.tag;
    }

    /**
     * Get an NBTTagCompound from this stack's NBT data.
     */
    public TagObject getSubCompound(String key, boolean create)
    {
        if (this.tag != null && this.tag.hasObject(key))
        {
            return this.tag.getObject(key);
        }
        else if (create)
        {
            TagObject nbttagcompound = new TagObject();
            this.setTagInfo(key, nbttagcompound);
            return nbttagcompound;
        }
        else
        {
            return null;
        }
    }

    public List<TagObject> getEnchantmentTagList()
    {
        return this.tag == null ? null : this.tag.getList("ench");
    }

    public void setTagCompound(TagObject nbt)
    {
        this.tag = nbt;
    }

    public String getDisplayName()
    {
        String s = this.getItem().getDisplay(this);

        if (this.tag != null && this.tag.hasString("Name"))
        {
            s = this.tag.getString("Name");
        }

        return s;
    }

    public String getColoredName() {
    	return this.getColor() + this.getDisplayName();
    }

    public String getColoredName(TextColor reset) {
    	return TextColor.DGRAY + "[" + this.getColor() + this.getDisplayName() + TextColor.DGRAY + "]" + reset;
    }

    public ItemStack setStackDisplayName(String displayName)
    {
        if (this.tag == null)
        {
            this.tag = new TagObject();
        }

        this.tag.setString("Name", displayName);
        return this;
    }

    public void clearCustomName()
    {
        if (this.tag != null)
        {
            if (this.tag.hasString("Name"))
            {
                this.tag.remove("Name");

                if (this.tag.isEmpty())
                {
                    this.setTagCompound(null);
                }
            }
        }
    }
    
    public boolean hasDisplayName()
    {
        return this.tag != null && this.tag.hasString("Name");
    }

    public List<String> getTooltip(EntityNPC playerIn)
    {
        List<String> list = Lists.<String>newArrayList();
        String s = this.getColoredName();
        if(this.size != 1)
        	s = TextColor.YELLOW + "" + this.size + " " + TextColor.RESET + s;
        s = s + TextColor.RESET;
        list.add(s);
        this.item.addInformation(this, playerIn, list);

        if (this.hasTagCompound())
        {
        	List<TagObject> nbttaglist = this.getEnchantmentTagList();

            if (nbttaglist != null)
            {
                for (int j = 0; j < nbttaglist.size(); ++j)
                {
                	Enchantment k = Enchantment.getEnchantment(nbttaglist.get(j).getString("id"));

                    if (k != null)
                    {
                        int l = nbttaglist.get(j).getShort("lvl");
                        list.add(k.getFormattedName(l));
                    }
                }
            }
        }
        
        int damage = this.item.getAttackDamageBonus();
        damage += EnchantmentHelper.getDamageModifier(this);
        if(damage != 0) {
        	if(damage > 0)
                list.add(TextColor.BLUE + String.format("+%d Angriffsschaden", damage));
            else
                list.add(TextColor.RED + String.format("-%d Angriffsschaden", damage));
        }

        Map<Attribute, Float> mods = this.getAttributeModifiers(null);

        if (!mods.isEmpty())
        {
            list.add("");
            for (Entry<Attribute, Float> entry : mods.entrySet())
            {
                float amt = entry.getValue();
                if(this.size != 1) {
                	double total = amt * (double)this.size;
                	if (amt > 0.0D)
	                    list.add(TextColor.BLUE + String.format("+%s %s [%dx +%s]", DECIMALFORMAT.format(total), entry.getKey(), this.size, DECIMALFORMAT.format(amt)));
	                else if (amt < 0.0D)
	                    list.add(TextColor.RED + String.format("-%s %s [%dx -%s]", DECIMALFORMAT.format(total * -1.0), entry.getKey(), this.size, DECIMALFORMAT.format(amt * -1.0)));
                }
                else {
	                if (amt > 0.0D)
	                    list.add(TextColor.BLUE + String.format("+%s %s", DECIMALFORMAT.format(amt), entry.getKey()));
	                else if (amt < 0.0D)
	                    list.add(TextColor.RED + String.format("-%s %s", DECIMALFORMAT.format(amt * -1.0), entry.getKey()));
                }
            }
        }

        if (this.isItemStackDamageable())
        {
            list.add(String.format("Haltbarkeit: %d" + (this.isItemDamaged() ? " / %d" : ""), 
            		this.isItemDamaged() ? (this.getMaxDamage() - this.getItemDamage()) : this.getMaxDamage(), this.getMaxDamage()));
        }
        
        if(this.getRepairCost() > 0)
        	list.add("Reparaturkosten: " + this.getRepairCost() + " Mana");

        list.add(TextColor.GRAY + ItemRegistry.getNameFromItem(this.item));

        if (this.hasTagCompound())
        {
            list.add(TextColor.GRAY + String.format("NBT-Tags: %d", this.getTagCompound().keySet().size()));
        }

        return list;
    }

    public boolean hasEffect()
    {
        return this.getItem().hasEffect(this);
    }

    public TextColor getColor()
    {
        return this.getItem().getColor(this);
    }

    /**
     * True if it is a tool and has no enchantments to begin with
     */
    public boolean isItemEnchantable()
    {
        return !this.getItem().isItemTool(this) ? false : !this.isItemEnchanted();
    }

    /**
     * Adds an enchantment with a desired level on the ItemStack.
     */
    public void addEnchantment(Enchantment ench, int level)
    {
        if (this.tag == null)
        {
            this.setTagCompound(new TagObject());
        }

        if (!this.tag.hasList("ench"))
        {
            this.tag.setList("ench", Lists.newArrayList());
        }

        List<TagObject> nbttaglist = this.tag.getList("ench");
        TagObject nbttagcompound = new TagObject();
        nbttagcompound.setString("id", ench.getName());
        nbttagcompound.setShort("lvl", (short)(/* (byte) */ level));
        nbttaglist.add(nbttagcompound);
    }

    /**
     * Removes an enchantment from the ItemStack.
     */
    public boolean removeEnchantment(Enchantment ench) {
        if(this.tag == null) {
            return false;
        }
        if(!this.tag.hasList("ench")) {
            return false;
        }
        List<TagObject> oldEnch = this.tag.getList("ench");
        List<TagObject> newEnch = Lists.newArrayList();
        boolean changed = false;
        TagObject tag;
        for(int z = 0; z < oldEnch.size(); z++) {
        	tag = oldEnch.get(z);
        	if(Enchantment.getEnchantment(tag.getString("id")) != ench) {
        		newEnch.add(tag);
        	}
        	else {
        		changed = true;
        	}
        }
        if(!changed) {
        	return false;
        }
        if(newEnch.size() == 0) {
        	this.tag.remove("ench");
        	if(this.tag.isEmpty()) {
        		this.tag = null;
        	}
        }
        else {
        	this.tag.setList("ench", newEnch);
        }
        return true;
    }

    /**
     * Removes all enchantments from the ItemStack.
     */
    public boolean clearEnchantments() {
        if(this.tag == null) {
            return false;
        }
        if(!this.tag.hasList("ench")) {
            return false;
        }
    	this.tag.remove("ench");
    	if(this.tag.isEmpty()) {
    		this.tag = null;
    	}
    	return true;
    }

    /**
     * True if the item has enchantment data
     */
    public boolean isItemEnchanted()
    {
        return this.tag != null && this.tag.hasList("ench");
    }

    public void setTagInfo(String key, TagObject value)
    {
        if (this.tag == null)
        {
            this.setTagCompound(new TagObject());
        }

        this.tag.setObject(key, value);
    }

    public void setTagInfo(String key, List<TagObject> value)
    {
        if (this.tag == null)
        {
            this.setTagCompound(new TagObject());
        }

        this.tag.setList(key, value);
    }

    public int getRepairCost()
    {
        return this.hasTagCompound() && this.tag.hasInt("RepairCost") ? this.tag.getInt("RepairCost") : 0;
    }

    public void setRepairCost(int cost)
    {
    	if(cost == 0) {
    		if(this.tag == null) {
                return;
            }
            if(!this.tag.hasInt("RepairCost")) {
                return;
            }
        	this.tag.remove("RepairCost");
        	if(this.tag.isEmpty()) {
        		this.tag = null;
        	}
            return;
    	}
    	
        if (!this.hasTagCompound())
        {
            this.tag = new TagObject();
        }

        this.tag.setInt("RepairCost", cost);
    }

	public Map<Attribute, Float> getAttributeModifiers(UsageSlot slot) {
		Map<Attribute, Float> map = Maps.newEnumMap(Attribute.class);
		this.getItem().getModifiers(map, slot);
		return map;
	}

    public void setItem(Item newItem)
    {
        this.item = newItem;
    }
}
