// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.objects;

import engine.Enum;
import engine.Enum.*;
import engine.exception.SerializationException;
import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.gameManager.PowersManager;
import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.DeleteItemMsg;
import engine.powers.EffectsBase;
import engine.powers.effectmodifiers.AbstractEffectModifier;
import engine.powers.poweractions.AbstractPowerAction;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;


public class Item extends AbstractWorldObject {

    private static ConcurrentHashMap<String, Integer> enchantValues = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
    private final ConcurrentHashMap<AbstractEffectModifier, Float> bonuses = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
    private final ArrayList<String> effectNames = new ArrayList<>();
    public Enum.ItemContainerType containerType;
    public ReentrantLock lootLock = new ReentrantLock();
    public int ownerID;  //may be character, account, npc, mob
    public float drop_chance;
    public EnumSet<Enum.ItemFlags> flags; //1 = isIDed
    public int numberOfItems;
    public float durabilityCurrent;
    public int chargesRemaining;
    public Enum.EquipSlotType equipSlot;
    private boolean canDestroy;
    private boolean isRandom = false;
    private int value;
    public OwnerType ownerType;
    public int templateID;
    private long dateToUpgrade;
    private int magicValue;
    public ItemTemplate template;
    public String name;

    /**
     * In Memory constructor
     * Set values and call PERSIST();
     */

    public Item(int templateID) {
        super();
        this.templateID = templateID;
        this.template = ItemTemplate.templates.get(templateID);
        this.chargesRemaining = this.template.item_initial_charges;
        this.durabilityCurrent = this.template.combat_health_full;
        this.equipSlot = EquipSlotType.NONE;
        this.containerType = ItemContainerType.NONE;
        this.numberOfItems = 1;
        loadEnchantments();
        applyBakedInStats();
    }

    /**
     * ResultSet Constructor
     */
    public Item(ResultSet rs) throws SQLException {
        super(rs);

        this.templateID = rs.getInt("item_itemBaseID");
        this.template = ItemTemplate.templates.get(this.templateID);

        if (this.template == null)
            Logger.error("Null template of " + this.templateID);

        // Name override in db.

        this.name = rs.getString("item_name");

        if (this.name == null)
            this.name = "";

        // Set container enumeration

        String container = rs.getString("item_container");

        switch (container) {
            case "inventory":
                this.containerType = Enum.ItemContainerType.INVENTORY;
                break;
            case "bank":
                this.containerType = Enum.ItemContainerType.BANK;
                break;
            case "vault":
                this.containerType = Enum.ItemContainerType.VAULT;
                break;
            case "equip":
                this.containerType = Enum.ItemContainerType.EQUIPPED;
                break;
            case "forge":
                this.containerType = Enum.ItemContainerType.FORGE;
                break;
            case "warehouse":
                this.containerType = Enum.ItemContainerType.FORGE;
                break;
        }

        this.ownerID = rs.getInt("parent");

        this.chargesRemaining = rs.getByte("item_chargesRemaining");

        this.durabilityCurrent = rs.getShort("item_durabilityCurrent");

        DbObjectType ownerType;
        ownerType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerID);

        switch (ownerType) {
            case CHARACTER:
                this.ownerType = OwnerType.PlayerCharacter;
                break;
            case NPC:
                this.ownerType = OwnerType.Npc;
                break;
            case ACCOUNT:
                this.ownerType = OwnerType.Account;
                break;
        }

        this.canDestroy = true;

        this.equipSlot = EquipSlotType.values()[rs.getByte("item_equipSlot")];

        this.numberOfItems = rs.getInt("item_numberOfItems");

        String flagString = rs.getString("item_flags");

        for (String itemFlag : flagString.split(";"))
            this.flags.add(Enum.ItemFlags.valueOf(itemFlag));

        // Empty flags should default to template

        if (this.flags.isEmpty())
            this.flags.addAll(this.template.item_flags);

        this.dateToUpgrade = rs.getLong("item_dateToUpgrade");
        this.value = rs.getInt("item_value");

    }

    public static void _serializeForClientMsg(Item item, ByteBufferWriter writer)
            throws SerializationException {
        Item._serializeForClientMsg(item, writer, true);
    }

    public static void serializeForClientMsgWithoutSlot(Item item, ByteBufferWriter writer) {
        Item._serializeForClientMsg(item, writer, false);
    }

    public static void serializeForClientMsgForVendor(Item item, ByteBufferWriter writer, float percent) {
        Item._serializeForClientMsg(item, writer, true);
        int baseValue = item.magicValue;
        writer.putInt(baseValue);
        writer.putInt((int) (baseValue * percent));
    }

    public static void serializeForClientMsgForVendorWithoutSlot(Item item, ByteBufferWriter writer, float percent) {
        Item._serializeForClientMsg(item, writer, false);
        writer.putInt(item.getValue());
        writer.putInt(item.getValue());
    }

    public static void _serializeForClientMsg(Item item, ByteBufferWriter writer,
                                              boolean includeSlot) {
        if (includeSlot)
            writer.putInt(item.equipSlot.ordinal());
        writer.putInt(0); // Pad
        writer.putInt(item.getItemBase().getUUID());

        writer.putInt(item.getObjectType().ordinal());
        writer.putInt(item.getObjectUUID());

        // Unknown statics
        for (int i = 0; i < 3; i++) {
            writer.putInt(0); // Pad
        }
        for (int i = 0; i < 4; i++) {
            writer.putInt(0x3F800000); // Static
        }
        for (int i = 0; i < 5; i++) {
            writer.putInt(0); // Pad
        }
        for (int i = 0; i < 2; i++) {
            writer.putInt(0xFFFFFFFF); // Static
        }

        // Handle Hair / Beard / horns Color.
        boolean isHair = (item.equipSlot.equals(EquipSlotType.HAIR));
        boolean isBeard = (item.equipSlot.equals(EquipSlotType.BEARD));

        int itemColor = 0;

        if (isHair || isBeard) {
            PlayerCharacter pc = PlayerCharacter.getFromCache(item.ownerID);
            if (pc != null)
                if (isHair)
                    itemColor = pc.getHairColor();
                else if (isBeard)
                    itemColor = pc.getBeardColor();
        }
        writer.putInt(itemColor);

        writer.put((byte) 1); // End Datablock byte
        writer.putString(item.name); // Unknown. pad?
        writer.put((byte) 1); // End Datablock byte

        writer.putFloat(item.template.item_health_full);
        writer.putFloat((float) item.durabilityCurrent);

        writer.put((byte) 1); // End Datablock byte

        writer.putInt(0); // Pad
        writer.putInt(0); // Pad

        if (item.templateID == ResourceType.GOLD.templateID) {

            if (item.getOwner() != null && item.getOwner().getObjectType() == GameObjectType.PlayerCharacter) {
                PlayerCharacter player = (PlayerCharacter) item.getOwner();
                int tradingAmount = player.charItemManager.getGoldTrading();
                writer.putInt(item.numberOfItems - tradingAmount);
            } else
                writer.putInt(item.numberOfItems); // Amount of gold
        } else
            writer.putInt(item.template.item_value);

        writer.putInt(item.getValue());

        int effectsSize = item.effects.size();
        ArrayList<Effect> effs = null;
        Effect nextE = null;
        if (effectsSize > 0 && item.flags.contains(ItemFlags.Identified)) {
            effs = new ArrayList<>(item.effects.values());

            //Don't send effects that have a token of 1
            Iterator<Effect> efi = effs.iterator();
            while (efi.hasNext()) {
                nextE = efi.next();
                if (nextE.getEffectToken() == 1 || nextE.bakedInStat())
                    efi.remove();
            }
        } else
            effs = new ArrayList<>();

        int effectsToSendSize = effs.size();
        writer.putInt(effectsToSendSize);
        for (int i = 0; i < effectsToSendSize; i++) {
            effs.get(i).serializeForItem(writer, item);
        }
        writer.putInt(0x00000000);


        if (effectsSize > 0)
            if (item.flags.contains(ItemFlags.Identified))
                writer.putInt(36); //Magical, blue name
            else
                writer.putInt(40); //Magical, unidentified
        else if (item.template.item_user_power_action.size() > 0)
            writer.putInt(36); //Magical, blue name
        else
            writer.putInt(4); //Non-Magical, grey name
        writer.putInt(item.chargesRemaining);
        writer.putInt(0); // Pad
        writer.putInt(item.numberOfItems);
        writer.put((byte) 0);


        if (item.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT) == false) {
            writer.putShort((short) 0);
            return;
        }
        writer.put((byte) 1); //
        writer.putInt(0);
        writer.putInt(0);
        if (item.chargesRemaining == 0)
            writer.putInt(1);
        else
            writer.putInt(item.chargesRemaining);
        writer.put((byte) 0);
    }

    public static void SerializeTradingGold(PlayerCharacter player, ByteBufferWriter writer) {

        writer.putInt(0); // Pad
        writer.putInt(7);

        writer.putInt(GameObjectType.Item.ordinal());
        writer.putInt(player.getObjectUUID());

        // Unknown statics
        for (int i = 0; i < 3; i++) {
            writer.putInt(0); // Pad
        }
        for (int i = 0; i < 4; i++) {
            writer.putInt(0x3F800000); // Static
        }
        for (int i = 0; i < 5; i++) {
            writer.putInt(0); // Pad
        }
        for (int i = 0; i < 2; i++) {
            writer.putInt(0xFFFFFFFF); // Static
        }

        // Handle Hair / Beard / horns Color.

        int itemColor = 0;
        writer.putInt(itemColor);

        writer.put((byte) 1); // End Datablock byte
        writer.putInt(0);
        writer.put((byte) 1); // End Datablock byte

        writer.putFloat((float) 1);
        writer.putFloat((float) 1);

        writer.put((byte) 1); // End Datablock byte

        writer.putInt(0); // Pad
        writer.putInt(0); // Pad


        writer.putInt(player.charItemManager.getGoldTrading()); // Amount of gold


        writer.putInt(0);


        writer.putInt(0);

        writer.putInt(0x00000000);

        writer.putInt(4); //Non-Magical, grey name
        writer.putInt(1);
        writer.putInt(0); // Pad
        writer.putInt(player.charItemManager.getGoldTrading());
        writer.put((byte) 0);

        writer.putShort((short) 0);

    }

    public static boolean MakeItemForPlayer(int templateID, PlayerCharacter reciever, int amount) {

        boolean itemWorked = false;

        Item item = new Item(templateID);

        item.ownerID = reciever.getObjectUUID();
        item.ownerType = OwnerType.PlayerCharacter;
        item.containerType = Enum.ItemContainerType.INVENTORY;
        item.numberOfItems = amount;

        try {
            item = DbManager.ItemQueries.PERSIST(item);
            itemWorked = true;
        } catch (Exception e) {
            Logger.error(e);
        }

        if (!itemWorked)
            return false;

        reciever.charItemManager.addItemToInventory(item);
        reciever.charItemManager.updateInventory();

        return true;
    }

    public static Item deserializeFromClientMsg(ByteBufferReader reader,
                                                boolean includeSlot) {
        if (includeSlot)
            reader.getInt();
        reader.getInt();
        int itemBase = reader.getInt(); //itemBase
        int objectType = reader.getInt(); //object type;
        int UUID = reader.getInt();
        for (int i = 0; i < 14; i++) {
            reader.getInt(); // Pads and statics
        }
        int unknown = reader.getInt();

        byte readString = reader.get();
        if (readString == 1)
            reader.getString();
        byte readDurability = reader.get();
        if (readDurability == 1) {
            reader.getInt();
            reader.getInt();
        }

        byte readEnchants = reader.get();
        if (readEnchants == 1) {
            reader.getInt();
            reader.getInt();
            reader.getInt();
            reader.getInt();
            int enchantSize = reader.getInt();
            for (int i = 0; i < enchantSize; i++) {
                reader.getInt(); //effect token
                reader.getInt(); //trains
                int type = reader.getInt();
                reader.get();
                if (type == 1)
                    reader.getLong(); //item comp
                else
                    reader.getInt(); //power token
                reader.getString(); //name
                reader.getFloat(); //duration
            }
            for (int i = 0; i < 5; i++) {
                reader.getInt();
            }
        }

        reader.get();
        byte isContract = reader.get();
        if (isContract == 1) {
            reader.getInt();
            reader.getInt();
            reader.getInt();
        }
        reader.get();

        if (UUID == 0 || objectType == 0)
            return null;
        if (objectType == GameObjectType.MobLoot.ordinal())
            return MobLoot.getFromCache(UUID);
        return Item.getFromCache(UUID);
    }

    public static void putListForVendor(ByteBufferWriter writer, ArrayList<Item> list, NPC vendor) {
        putList(writer, list, false, vendor.getObjectUUID(), true, vendor);
    }

    public static void putList(ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID) {
        putList(writer, list, includeSlot, ownerID, false, null);
    }

    private static void putList(ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) {
        int indexPosition = writer.position();
        //reserve 4 bytes for index.
        writer.putInt(0);

        int serialized = 0;
        for (Item item : list) {

            if (item.template.item_type.equals(ItemType.GOLD))
                if (item.numberOfItems == 0)
                    continue;
            try {
                if (includeSlot && !forVendor)
                    Item._serializeForClientMsg(item, writer);
                else if (!includeSlot && !forVendor)
                    Item.serializeForClientMsgWithoutSlot(item, writer);

                if (!includeSlot && forVendor) //TODO separate for sell/buy percent

                    Item.serializeForClientMsgForVendorWithoutSlot(item, writer, vendor.getSellPercent());

                if (includeSlot && forVendor) //TODO separate for sell/buy percent

                    Item.serializeForClientMsgForVendor(item, writer, vendor.getSellPercent());

            } catch (SerializationException se) {
                continue;
            }
            ++serialized;
        }

        writer.putIntAt(serialized, indexPosition);
    }

    public static void putTradingList(PlayerCharacter player, ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) {
        int indexPosition = writer.position();
        //reserve 4 bytes for index.
        writer.putInt(0);

        int serialized = 0;
        for (Item item : list) {

            if (item.template.item_type.equals(ItemType.GOLD))
                if (item.numberOfItems == 0)
                    continue;
            try {
                if (includeSlot && !forVendor)
                    Item._serializeForClientMsg(item, writer);
                else if (!includeSlot && !forVendor)
                    Item.serializeForClientMsgWithoutSlot(item, writer);

                if (!includeSlot && forVendor) //TODO separate for sell/buy percent

                    Item.serializeForClientMsgForVendorWithoutSlot(item, writer, vendor.getSellPercent());

                if (includeSlot && forVendor) //TODO separate for sell/buy percent

                    Item.serializeForClientMsgForVendor(item, writer, vendor.getSellPercent());

            } catch (SerializationException se) {
                continue;
            }
            ++serialized;
        }
        if (player.charItemManager.getGoldTrading() > 0) {
            Item.SerializeTradingGold(player, writer);
            ++serialized;
        }


        writer.putIntAt(serialized, indexPosition);
    }

    public static Item createItemForPlayer(PlayerCharacter pc, int templateID) {

        Item item = new Item(templateID);
        item.ownerID = pc.getObjectUUID();
        item.ownerType = OwnerType.PlayerCharacter;

        try {
            item = DbManager.ItemQueries.PERSIST(item);
        } catch (Exception e) {
            Logger.error(e);
        }
        return item;
    }

    public static Item getFromCache(int id) {
        return (Item) DbManager.getFromCache(GameObjectType.Item, id);
    }

    public static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType) {
        return newGoldItem(awo, ib, containerType, true);
    }

    //used for vault!
    public static Item newGoldItem(int accountID, ItemBase ib, Enum.ItemContainerType containerType) {
        return newGoldItem(accountID, ib, containerType, true);
    }

    private static Item newGoldItem(int accountID, ItemBase ib, Enum.ItemContainerType containerType, boolean persist) {

        int ownerID;
        OwnerType ownerType;

        ownerID = accountID;
        ownerType = OwnerType.Account;

        Item newGold = new Item(ib.getUUID());
        newGold.ownerID = ownerID;
        newGold.ownerType = ownerType;
        newGold.containerType = containerType;
        newGold.numberOfItems = 0;

        if (persist) {
            try {
                newGold = DbManager.ItemQueries.PERSIST(newGold);
                DbManager.ItemQueries.ZERO_ITEM_STACK(newGold);
            } catch (Exception e) {
                Logger.error(e);
            }
        }

        return newGold;
    }

    private static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType, boolean persist) {

        int ownerID;
        OwnerType ownerType;

        if (awo.getObjectType().equals(GameObjectType.Mob))
            return null;

        if (containerType == Enum.ItemContainerType.VAULT) {
            if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) {
                Logger.error("AWO is not a PlayerCharacter");
                return null;
            }
            ownerID = ((PlayerCharacter) awo).getAccount().getObjectUUID();
            ownerType = OwnerType.Account;
        } else {

            ownerID = awo.getObjectUUID();

            switch (awo.getObjectType()) {

                case NPC:
                    ownerType = OwnerType.Npc;
                    break;
                case PlayerCharacter:
                    ownerType = OwnerType.PlayerCharacter;
                    break;
                case Mob:
                    ownerType = OwnerType.Mob;
                    break;
                default:
                    Logger.error("Unsupported AWO object type.");
                    return null;
            }
        }

        Item newGold = new Item(ib.getUUID());
        newGold.ownerID = ownerID;
        newGold.ownerType = ownerType;
        newGold.containerType = containerType;
        newGold.numberOfItems = 0;

        if (persist) {
            try {
                newGold = DbManager.ItemQueries.PERSIST(newGold);
            } catch (Exception e) {
                Logger.error(e);
            }
            DbManager.ItemQueries.ZERO_ITEM_STACK(newGold);
        }

        return newGold;
    }

    // This is to be used for trades - the new item is not stored in the database
    public static Item newGoldItemTemp(AbstractWorldObject awo, ItemBase ib) {
        return Item.newGoldItem(awo, ib, Enum.ItemContainerType.NONE, false);
    }

    public static Item getItem(int UUID) {
        if (UUID == 0)
            return null;

        Item item = (Item) DbManager.getFromCache(GameObjectType.Item, UUID);
        if (item != null)
            return item;
        return DbManager.ItemQueries.GET_ITEM(UUID);
    }

    public static void addEnchantValue(String enchant, int value) {
        Item.enchantValues.put(enchant, value);
    }

    public static int getEnchantValue(String enchant) {
        if (Item.enchantValues.containsKey(enchant))
            return Item.enchantValues.get(enchant);
        return 0;
    }

    public ItemBase getItemBase() {
        return ItemBase.getItemBase(templateID);
    }

    public int getTemplateID() {
        return this.templateID;
    }

    public int getOwnerID() {
        return ownerID;
    }

    //Only to be used for trading
    public void setOwnerID(int ownerID) {
        this.ownerID = ownerID;
    }

    public AbstractGameObject getOwner() {
        if (this.ownerType == OwnerType.Npc)
            return NPC.getFromCache(this.ownerID);
        else if (this.ownerType == OwnerType.PlayerCharacter)
            return PlayerCharacter.getFromCache(this.ownerID);
        else if (this.ownerType == OwnerType.Mob)
            return Mob.getFromCache(this.ownerID);
        else if (this.ownerType == OwnerType.Account)
            return DbManager.AccountQueries.GET_ACCOUNT(this.ownerID);
        else
            return null;
    }

    public boolean setOwner(AbstractGameObject owner) {
        if (owner == null)
            return false;
        if (owner.getObjectType().equals(GameObjectType.NPC))
            this.ownerType = OwnerType.Npc;
        else if (owner.getObjectType().equals(GameObjectType.PlayerCharacter))
            this.ownerType = OwnerType.PlayerCharacter;
        else if (owner.getObjectType().equals(GameObjectType.Mob))
            this.ownerType = OwnerType.Mob;
        else if (owner.getObjectType().equals(GameObjectType.Account))
            this.ownerType = OwnerType.Account;
        else
            return false;
        this.ownerID = owner.getObjectUUID();
        return true;
    }

    public void setDurabilityCurrent(float value) {
        this.durabilityCurrent = value;
    }

    public boolean isCanDestroy() {
        return canDestroy;
    }

    public int getNumOfItems() {
        return this.numberOfItems;
    }

    public synchronized void setNumOfItems(int numberOfItems) {
        this.numberOfItems = numberOfItems;
    }

    public ConcurrentHashMap<AbstractEffectModifier, Float> getBonuses() {
        return this.bonuses;
    }

    public void clearBonuses() {
        this.bonuses.clear();
    }

    public float getBonus(ModType modType, SourceType sourceType) {

        int amount = 0;
        for (AbstractEffectModifier modifier : this.getBonuses().keySet()) {
            if (modifier.getPercentMod() != 0)
                continue;
            if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType) == false)
                continue;
            amount += this.bonuses.get(modifier);
        }
        return amount;
    }

    public float getBonusPercent(ModType modType, SourceType sourceType) {

        int amount = 0;
        for (AbstractEffectModifier modifier : this.getBonuses().keySet()) {

            if (modifier.getPercentMod() == 0)
                continue;
            if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType) == false)
                continue;
            amount += this.bonuses.get(modifier);
        }
        return amount;
    }

    public boolean isComplete() {
        return this.dateToUpgrade < System.currentTimeMillis() + 1000;
    }

    public String getContainerInfo() {
        String ret = "OwnerID: " + this.ownerID + ", container: ";
        ret += this.containerType.toString();
        ret += "Equip Slot: " + this.equipSlot;
        return ret;
    }

    public void addBonus(AbstractEffectModifier key, float amount) {
        if (this.bonuses.containsKey(key))
            this.bonuses.put(key, (this.bonuses.get(key) + amount));
        else
            this.bonuses.put(key, amount);
    }

    public void multBonus(AbstractEffectModifier key, float amount) {
        if (this.bonuses.containsKey(key))
            this.bonuses.put(key, (this.bonuses.get(key) * amount));
        else
            this.bonuses.put(key, amount);
    }

    public synchronized void decrementChargesRemaining() {
        this.chargesRemaining -= 1;
        if (this.chargesRemaining < 0)
            this.chargesRemaining = 0;
        DbManager.ItemQueries.UPDATE_REMAINING_CHARGES(this);
    }

    public void zeroItem() {
        this.ownerID = 0;

        this.ownerType = null;
        this.containerType = Enum.ItemContainerType.NONE;
        this.equipSlot = EquipSlotType.NONE;
    }

    protected void validateItemContainer() {

        if (this.containerType == Enum.ItemContainerType.NONE)

            if (this.ownerID != 0)
                // Item has an owner, just somehow the flags got messed up.
                // Default to bank.
                // TODO NEED LOG EVENT HERE.
                this.containerType = Enum.ItemContainerType.BANK;
            else
                // This item is on the ground. Nothing to worry about.
                this.zeroItem();
    }

    // Removes all ownership of item and 'orphans' it.
    protected synchronized void junk() {

        DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false, ItemContainerType.NONE, 0);
        this.zeroItem();

        //cleanup item from server.

        this.removeFromCache();
    }

    protected synchronized boolean moveItemToInventory(PlayerCharacter pc) {

        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                pc.getObjectUUID(), //tableID
                false, //isNPC
                true, //isPlayer
                false, //isAccount
                ItemContainerType.INVENTORY,
                0)) //Slot
            return false;

        this.ownerID = pc.getObjectUUID();
        this.ownerType = OwnerType.PlayerCharacter;
        this.containerType = ItemContainerType.INVENTORY;
        this.equipSlot = EquipSlotType.NONE;
        return true;
    }

    protected synchronized boolean moveItemToInventory(NPC npc) {
        if (npc.isStatic()) {
            if (!DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false, ItemContainerType.INVENTORY, 0))
                return false;
        } else if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                npc.getObjectUUID(), //UUID
                true, //isNPC
                false, //isPlayer
                false, //isAccount
                ItemContainerType.INVENTORY,
                0)) //Slot

            return false;
        this.zeroItem();
        this.ownerID = npc.getObjectUUID();
        this.ownerType = OwnerType.Npc;
        this.containerType = Enum.ItemContainerType.INVENTORY;
        return true;
    }

    protected synchronized boolean moveItemToInventory(Corpse corpse) {
        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                0, //no ID for corpse
                false, //isNPC
                true, //isPlayer
                false, //isAccount
                ItemContainerType.INVENTORY,
                0)) //Slot

            return false;
        this.zeroItem();
        this.ownerID = 0;
        this.ownerType = null;
        this.containerType = Enum.ItemContainerType.INVENTORY;
        return true;
    }

    protected synchronized boolean moveItemToBank(PlayerCharacter pc) {
        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                pc.getObjectUUID(), //UUID
                false, //isNPC
                true, //isPlayer
                false, //isAccount
                ItemContainerType.BANK,
                0)) //Slot

            return false;
        this.zeroItem();
        this.ownerID = pc.getObjectUUID();
        this.ownerType = OwnerType.PlayerCharacter;
        this.containerType = Enum.ItemContainerType.BANK;
        return true;
    }

    protected synchronized boolean moveItemToBank(NPC npc) {
        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                npc.getObjectUUID(), //UUID
                true, //isNPC
                false, //isPlayer
                false, //isAccount
                ItemContainerType.BANK,
                0)) //Slot

            return false;
        this.zeroItem();
        this.ownerID = npc.getObjectUUID();
        this.ownerType = OwnerType.Npc;
        this.containerType = Enum.ItemContainerType.BANK;
        return true;
    }

    protected synchronized boolean moveItemToVault(Account a) {
        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                a.getObjectUUID(), //UUID
                false, //isNPC
                false, //isPlayer
                true, //isAccount
                ItemContainerType.VAULT,
                0)) //Slot

            return false;
        this.zeroItem();
        this.ownerID = a.getObjectUUID();
        this.ownerType = OwnerType.Account;
        this.containerType = Enum.ItemContainerType.VAULT;
        return true;
    }

    protected synchronized boolean equipItem(PlayerCharacter pc, Enum.EquipSlotType slot) {

        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                pc.getObjectUUID(), //tableID
                false, //isNPC
                true, //isPlayer
                false, //isAccount
                ItemContainerType.EQUIPPED,
                slot.ordinal())) //Slot

            return false;
        this.zeroItem();
        this.ownerID = pc.getObjectUUID();
        this.ownerType = OwnerType.PlayerCharacter;
        this.containerType = Enum.ItemContainerType.EQUIPPED;
        this.equipSlot = slot;
        return true;
    }

    protected synchronized boolean equipItem(NPC npc, Enum.EquipSlotType slot) {
        if (!DbManager.ItemQueries.UPDATE_OWNER(this,
                npc.getObjectUUID(), //UUID
                true, //isNPC
                false, //isPlayer
                false, //isAccount
                ItemContainerType.EQUIPPED,
                slot.ordinal())) //Slot

            return false;
        this.zeroItem();
        this.ownerID = npc.getObjectUUID();
        this.ownerType = OwnerType.Npc;
        this.containerType = Enum.ItemContainerType.EQUIPPED;
        this.equipSlot = slot;
        return true;
    }

    protected synchronized boolean equipItem(Mob npc, Enum.EquipSlotType slot) {

        this.zeroItem();
        this.ownerID = npc.getObjectUUID();
        this.ownerType = OwnerType.Mob;
        this.containerType = Enum.ItemContainerType.EQUIPPED;
        this.equipSlot = slot;
        return true;
    }

    public final int getMagicValue() {
        return this.magicValue;
    }

    private void applyBakedInStats() {

        EffectsBase effect;

        if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER))
            return;


        for (String effectID : this.template.item_user_power_action.keySet()) {

            effect = PowersManager.getEffectByIDString(effectID);

            if (effect == null) {
                Logger.error("missing effect of type: " + effectID);
                continue;
            }

            AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString());
            apa.applyBakedInStatsForItem(this, this.template.item_user_power_action.get(effectID)[0]);
            }
    }

    public final void loadEnchantments() {
        //dont load mobloot enchantments, they arent in db.
        if (this.getObjectType().equals(GameObjectType.MobLoot)) {
            this.magicValue = this.template.item_value + calcMagicValue();
            return;
        }

        ConcurrentHashMap<String, Integer> enchantList = DbManager.EnchantmentQueries.GET_ENCHANTMENTS_FOR_ITEM(this.getObjectUUID());

        for (String enchant : enchantList.keySet()) {
            AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchant);
            if (apa != null) {
                apa.applyEffectForItem(this, enchantList.get(enchant));
                this.effectNames.add(enchant);
            }
        }

        this.magicValue = this.template.item_value + calcMagicValue();
    }

    public void clearEnchantments() {

        //Clear permanent enchantment out of database
        DbManager.EnchantmentQueries.CLEAR_ENCHANTMENTS((long) this.getObjectUUID());

        for (String name : this.getEffects().keySet()) {
            Effect eff = this.getEffects().get(name);
            if (!eff.bakedInStat())
                this.endEffect(name);
        }
        this.effectNames.clear();
    }

    public void addPermanentEnchantment(String enchantID, int rank) {
        AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID);
        if (apa == null)
            return;

        DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank);
        apa.applyEffectForItem(this, rank);
        this.effectNames.add(enchantID);
    }

    public void addPermanentEnchantmentForDev(String enchantID, int rank) {
        AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID);
        if (apa == null)
            return;

        DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank);
        apa.applyEffectForItem(this, rank);
        this.effectNames.add(enchantID);
    }

    protected int calcMagicValue() {
        int ret = 0;
        for (String enchant : this.effectNames) {
            ret += Item.getEnchantValue(enchant + 'A');
        }
        return ret;
    }

    public void addToCache() {
        DbManager.addToCache(this);
    }

    @Override
    public void updateDatabase() {
        //DbManager.ItemQueries.updateDatabase(this);
    }

    @Override
    public void runAfterLoad() {
        loadEnchantments();
        applyBakedInStats();
    }


    public ArrayList<String> getEffectNames() {
        return effectNames;
    }

    public boolean validForInventory(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {

        if (origin == null || pc == null || charItemMan == null)
            return false;

        if (ownerID != pc.getObjectUUID()) {
            Logger.warn("Inventory Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID());
            charItemMan.updateInventory();
            return false;
        }

        if (!charItemMan.inventoryContains(this)) {
            charItemMan.updateInventory();
            return false;
        }
        return true;
    }

    public boolean validForBank(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
        if (origin == null || pc == null || charItemMan == null)
            return false;

        if (!charItemMan.bankContains(this))
            return false;
        else if (ownerID != pc.getObjectUUID()) {
            Logger.warn("Bank Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID());
            return false;
        }
        return true;
    }

    public boolean validForEquip(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
        if (origin == null || pc == null || charItemMan == null)
            return false;

        if (!charItemMan.equippedContains(this))
            return false;
        else if (ownerID != pc.getObjectUUID()) {
            //duped item, cleanup
            Logger.warn("Duped item id "
                    + this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.');
            DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID());
            charItemMan.cleanupDupe(this);
            Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg);
            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);

            return false;
        }
        return true;
    }

    public boolean validForVault(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
        if (origin == null || pc == null || charItemMan == null)
            return false;

        if (pc.getAccount() == null)
            return false;

        if (!pc.getAccount().getVault().contains(this))
            return false;
        else if (ownerID != pc.getAccount().getObjectUUID()) {
            //duped item, cleanup
            Logger.warn("Duped item id "
                    + this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.');
            DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID());
            charItemMan.cleanupDupe(this);
            Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg);
            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
            return false;
        }
        return true;
    }

    public long getDateToUpgrade() {
        return dateToUpgrade;
    }

    public void setDateToUpgrade(long dateToUpgrade) {
        this.dateToUpgrade = dateToUpgrade;
    }

    /**
     * @return the value
     */
    public int getValue() {

        if (this.value == 0)
            if (this.flags.contains(ItemFlags.Identified)) {
                return this.getMagicValue();
            } else
                return this.template.item_value;

        return this.value;
    }

    /**
     * @param value the value to set
     */
    public void setValue(int value) {
        this.value = value;
    }

    public boolean isRandom() {
        return isRandom;
    }

    public void setRandom(boolean isRandom) {
        this.isRandom = isRandom;
    }

    public boolean isCustomValue() {
        if (this.value == 0)
            return false;
        return true;
    }
}