/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Collections;
using Framework.Constants;
using Framework.Database;
using Framework.IO;
using Game.DataStorage;
using Game.Loots;
using Game.Network;
using Game.Network.Packets;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Game.Entities
{
    public class Item : WorldObject
    {
        public Item() : base(false)
        {
            objectTypeMask |= TypeMask.Item;
            objectTypeId = TypeId.Item;

            m_updateFlag = UpdateFlag.None;
            valuesCount = (int)ItemFields.End;
            _dynamicValuesCount = (int)ItemDynamicFields.End;
            uState = ItemUpdateState.New;
            uQueuePos = -1;
            m_lastPlayedTimeUpdate = Time.UnixTime;

            loot = new Loot();
        }

        public virtual bool Create(ulong guidlow, uint itemid, Player owner)
        {
            _Create(ObjectGuid.Create(HighGuid.Item, guidlow));

            SetEntry(itemid);
            SetObjectScale(1.0f);

            if (owner)
            {
                SetOwnerGUID(owner.GetGUID());
                SetGuidValue(ItemFields.Contained, owner.GetGUID());
            }

            ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(itemid);
            if (itemProto == null)
                return false;

            _bonusData = new BonusData(itemProto);
            SetUInt32Value(ItemFields.StackCount, 1);
            SetUInt32Value(ItemFields.MaxDurability, itemProto.MaxDurability);
            SetUInt32Value(ItemFields.Durability, itemProto.MaxDurability);

            for (var i = 0; i < itemProto.Effects.Count; ++i)
            {
                if (i < 5)
                    SetSpellCharges(i, itemProto.Effects[i].Charges);

                SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(itemProto.Effects[i].SpellID);
                if (spellInfo != null)
                {
                    if (spellInfo.HasEffect(SpellEffectName.GiveArtifactPower))
                    {
                        uint artifactKnowledgeLevel = owner.GetCurrency((uint)CurrencyTypes.ArtifactKnowledge);
                        if (artifactKnowledgeLevel != 0)
                            SetModifier(ItemModifier.ArtifactKnowledgeLevel, artifactKnowledgeLevel + 1);
                    }
                }
            }

            SetUInt32Value(ItemFields.Duration, itemProto.GetDuration());
            SetUInt32Value(ItemFields.CreatePlayedTime, 0);

            if (itemProto.GetArtifactID() != 0)
            {
                InitArtifactPowers(itemProto.GetArtifactID(), 0);
                foreach (ArtifactAppearanceRecord artifactAppearance in CliDB.ArtifactAppearanceStorage.Values)
                {
                    ArtifactAppearanceSetRecord artifactAppearanceSet = CliDB.ArtifactAppearanceSetStorage.LookupByKey(artifactAppearance.ArtifactAppearanceSetID);
                    if (artifactAppearanceSet != null)
                    {
                        if (itemProto.GetArtifactID() != artifactAppearanceSet.ArtifactID)
                            continue;

                        PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactAppearance.PlayerConditionID);
                        if (playerCondition != null)
                            if (!owner || !ConditionManager.IsPlayerMeetingCondition(owner, playerCondition))
                                continue;

                        SetModifier(ItemModifier.ArtifactAppearanceId, artifactAppearance.Id);
                        SetAppearanceModId(artifactAppearance.AppearanceModID);
                        break;
                    }
                }
            }
            return true;
        }

        public bool IsNotEmptyBag()
        {
            Bag bag = ToBag();
            if (bag != null)
                return !bag.IsEmpty();

            return false;
        }

        public void UpdateDuration(Player owner, uint diff)
        {
            uint dur = GetUInt32Value(ItemFields.Duration);

            Log.outDebug(LogFilter.Player, "Item.UpdateDuration Item (Entry: {0} Duration {1} Diff {2})", GetEntry(), dur, diff);

            if (dur <= diff)
            {
                Global.ScriptMgr.OnItemExpire(owner, GetTemplate());
                owner.DestroyItem(GetBagSlot(), GetSlot(), true);
                return;
            }

            SetUInt32Value(ItemFields.Duration, dur - diff);
            SetState(ItemUpdateState.Changed, owner);                          // save new time in database
        }

        public virtual void SaveToDB(SQLTransaction trans)
        {
            PreparedStatement stmt;
            switch (uState)
            {
                case ItemUpdateState.New:
                case ItemUpdateState.Changed:
                    {
                        byte index = 0;
                        stmt = DB.Characters.GetPreparedStatement(uState == ItemUpdateState.New ? CharStatements.REP_ITEM_INSTANCE : CharStatements.UPD_ITEM_INSTANCE);
                        stmt.AddValue(index, GetEntry());
                        stmt.AddValue(++index, GetOwnerGUID().GetCounter());
                        stmt.AddValue(++index, GetGuidValue(ItemFields.Creator).GetCounter());
                        stmt.AddValue(++index, GetGuidValue(ItemFields.GiftCreator).GetCounter());
                        stmt.AddValue(++index, GetCount());
                        stmt.AddValue(++index, GetUInt32Value(ItemFields.Duration));

                        StringBuilder ss = new StringBuilder();
                        for (byte i = 0; i < ItemConst.MaxSpells; ++i)
                            ss.AppendFormat("{0} ", GetSpellCharges(i));

                        stmt.AddValue(++index, ss.ToString());
                        stmt.AddValue(++index, GetUInt32Value(ItemFields.Flags));

                        ss.Clear();
                        for (EnchantmentSlot slot = 0; slot < EnchantmentSlot.Max; ++slot)
                            ss.AppendFormat("{0} {1} {2} ", GetEnchantmentId(slot), GetEnchantmentDuration(slot), GetEnchantmentCharges(slot));

                        stmt.AddValue(++index, ss.ToString());
                        stmt.AddValue(++index, (byte)GetItemRandomEnchantmentId().Type);
                        stmt.AddValue(++index, GetItemRandomEnchantmentId().Id);
                        stmt.AddValue(++index, GetUInt32Value(ItemFields.Durability));
                        stmt.AddValue(++index, GetUInt32Value(ItemFields.CreatePlayedTime));
                        stmt.AddValue(++index, m_text);
                        stmt.AddValue(++index, GetModifier(ItemModifier.UpgradeId));
                        stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetSpeciesId));
                        stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetBreedData));
                        stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetLevel));
                        stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetDisplayId));
                        stmt.AddValue(++index, (byte)GetUInt32Value(ItemFields.Context));

                        ss.Clear();
                        foreach (uint bonusListID in GetDynamicValues(ItemDynamicFields.BonusListIds))
                            ss.Append(bonusListID + ' ');

                        stmt.AddValue(++index, ss.ToString());
                        stmt.AddValue(++index, GetGUID().GetCounter());

                        DB.Characters.Execute(stmt);

                        if ((uState == ItemUpdateState.Changed) && HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped))
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GIFT_OWNER);
                            stmt.AddValue(0, GetOwnerGUID().GetCounter());
                            stmt.AddValue(1, GetGUID().GetCounter());
                            DB.Characters.Execute(stmt);
                        }

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        if (!GetGems().Empty())
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_GEMS);
                            stmt.AddValue(0, GetGUID().GetCounter());
                            int i = 0;
                            int gemFields = 4;
                            foreach (ItemDynamicFieldGems gemData in GetGems())
                            {
                                if (gemData.ItemId != 0)
                                {
                                    stmt.AddValue(1 + i * gemFields, gemData.ItemId);
                                    StringBuilder gemBonusListIDs = new StringBuilder();
                                    foreach (ushort bonusListID in gemData.BonusListIDs)
                                    {
                                        if (bonusListID != 0)
                                            gemBonusListIDs.AppendFormat("{0} ", bonusListID);
                                    }

                                    stmt.AddValue(2 + i * gemFields, gemBonusListIDs.ToString());
                                    stmt.AddValue(3 + i * gemFields, gemData.Context);
                                    stmt.AddValue(4 + i * gemFields, m_gemScalingLevels[i]);
                                }
                                else
                                {
                                    stmt.AddValue(1 + i * gemFields, 0);
                                    stmt.AddValue(2 + i * gemFields, "");
                                    stmt.AddValue(3 + i * gemFields, 0);
                                    stmt.AddValue(4 + i * gemFields, 0);
                                }
                                ++i;
                            }
                            for (; i < ItemConst.MaxGemSockets; ++i)
                            {
                                stmt.AddValue(1 + i * gemFields, 0);
                                stmt.AddValue(2 + i * gemFields, "");
                                stmt.AddValue(3 + i * gemFields, 0);
                                stmt.AddValue(4 + i * gemFields, 0);
                            }
                            trans.Append(stmt);
                        }

                        ItemModifier[] transmogMods =
                        {
                            ItemModifier.TransmogAppearanceAllSpecs,
                            ItemModifier.TransmogAppearanceSpec1,
                            ItemModifier.TransmogAppearanceSpec2,
                            ItemModifier.TransmogAppearanceSpec3,
                            ItemModifier.TransmogAppearanceSpec4,

                            ItemModifier.EnchantIllusionAllSpecs,
                            ItemModifier.EnchantIllusionSpec1,
                            ItemModifier.EnchantIllusionSpec2,
                            ItemModifier.EnchantIllusionSpec3,
                            ItemModifier.EnchantIllusionSpec4,
                        };

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        if (transmogMods.Any(modifier => { return GetModifier(modifier) != 0; }))
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_TRANSMOG);
                            stmt.AddValue(0, GetGUID().GetCounter());
                            stmt.AddValue(1, GetModifier(ItemModifier.TransmogAppearanceAllSpecs));
                            stmt.AddValue(2, GetModifier(ItemModifier.TransmogAppearanceSpec1));
                            stmt.AddValue(3, GetModifier(ItemModifier.TransmogAppearanceSpec2));
                            stmt.AddValue(4, GetModifier(ItemModifier.TransmogAppearanceSpec3));
                            stmt.AddValue(5, GetModifier(ItemModifier.TransmogAppearanceSpec4));
                            stmt.AddValue(6, GetModifier(ItemModifier.EnchantIllusionAllSpecs));
                            stmt.AddValue(7, GetModifier(ItemModifier.EnchantIllusionSpec1));
                            stmt.AddValue(8, GetModifier(ItemModifier.EnchantIllusionSpec2));
                            stmt.AddValue(9, GetModifier(ItemModifier.EnchantIllusionSpec3));
                            stmt.AddValue(10, GetModifier(ItemModifier.EnchantIllusionSpec4));
                            trans.Append(stmt);
                        }

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        if (GetTemplate().GetArtifactID() != 0)
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT);
                            stmt.AddValue(0, GetGUID().GetCounter());
                            stmt.AddValue(1, GetUInt64Value(ItemFields.ArtifactXp));
                            stmt.AddValue(2, GetModifier(ItemModifier.ArtifactAppearanceId));
                            trans.Append(stmt);

                            foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers())
                            {
                                stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT_POWERS);
                                stmt.AddValue(0, GetGUID().GetCounter());
                                stmt.AddValue(1, artifactPower.ArtifactPowerId);
                                stmt.AddValue(2, artifactPower.PurchasedRank);
                                trans.Append(stmt);
                            }
                        }

                        ItemModifier[] modifiersTable =
                        {
                            ItemModifier.ScalingStatDistributionFixedLevel,
                            ItemModifier.ArtifactKnowledgeLevel
                        };

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        if (modifiersTable.Any(modifier => { return GetModifier(modifier) != 0; }))
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_MODIFIERS);
                            stmt.AddValue(0, GetGUID().GetCounter());
                            stmt.AddValue(1, GetModifier(ItemModifier.ScalingStatDistributionFixedLevel));
                            stmt.AddValue(2, GetModifier(ItemModifier.ArtifactKnowledgeLevel));
                            trans.Append(stmt);
                        }
                        break;
                    }
                case ItemUpdateState.Removed:
                    {
                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        trans.Append(stmt);

                        if (HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped))
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GIFT);
                            stmt.AddValue(0, GetGUID().GetCounter());
                            trans.Append(stmt);
                        }

                        // Delete the items if this is a container
                        if (!loot.isLooted())
                            ItemContainerDeleteLootMoneyAndLootItemsFromDB();

                        Dispose();
                        return;
                    }
                case ItemUpdateState.Unchanged:
                    break;
            }

            SetState(ItemUpdateState.Unchanged);
        }

        public virtual bool LoadFromDB(ulong guid, ObjectGuid ownerGuid, SQLFields fields, uint entry)
        {
            // create item before any checks for store correct guid
            // and allow use "FSetState(ITEM_REMOVED); SaveToDB();" for deleting item from DB
            _Create(ObjectGuid.Create(HighGuid.Item, guid));

            SetEntry(entry);
            SetObjectScale(1.0f);

            ItemTemplate proto = GetTemplate();
            if (proto == null)
                return false;

            _bonusData = new BonusData(proto);

            // set owner (not if item is only loaded for gbank/auction/mail
            if (!ownerGuid.IsEmpty())
                SetOwnerGUID(ownerGuid);

            uint itemFlags = fields.Read<uint>(7);
            bool need_save = false;
            ulong creator = fields.Read<ulong>(2);
            if (creator != 0)
            {
                if (!Convert.ToBoolean(itemFlags & (int)ItemFieldFlags.Child))
                    SetGuidValue(ItemFields.Creator, ObjectGuid.Create(HighGuid.Player, creator));
                else
                    SetGuidValue(ItemFields.Creator, ObjectGuid.Create(HighGuid.Item, creator));
            }

            ulong giftCreator = fields.Read<ulong>(3);
            if (giftCreator != 0)
                SetGuidValue(ItemFields.GiftCreator, ObjectGuid.Create(HighGuid.Player, giftCreator));
            SetCount(fields.Read<uint>(4));

            uint duration = fields.Read<uint>(5);
            SetUInt32Value(ItemFields.Duration, duration);
            // update duration if need, and remove if not need
            if (proto.GetDuration() != duration)
            {
                SetUInt32Value(ItemFields.Duration, proto.GetDuration());
                need_save = true;
            }

            var tokens = new StringArray(fields.Read<string>(6), ' ');
            if (tokens.Length == ItemConst.MaxProtoSpells)
                for (byte i = 0; i < ItemConst.MaxProtoSpells; ++i)
                    SetSpellCharges(i, int.Parse(tokens[i]));

            SetUInt32Value(ItemFields.Flags, itemFlags);

            _LoadIntoDataField(fields.Read<string>(8), (uint)ItemFields.Enchantment, (uint)EnchantmentSlot.Max * (uint)EnchantmentOffset.Max);
            m_randomEnchantment.Type = (ItemRandomEnchantmentType)fields.Read<byte>(9);
            m_randomEnchantment.Id = fields.Read<uint>(10);
            if (m_randomEnchantment.Type == ItemRandomEnchantmentType.Property)
                SetUInt32Value(ItemFields.RandomPropertiesId, m_randomEnchantment.Id);
            else if (m_randomEnchantment.Type == ItemRandomEnchantmentType.Suffix)
            {
                SetInt32Value(ItemFields.RandomPropertiesId, -(int)m_randomEnchantment.Id);
                // recalculate suffix factor
                UpdateItemSuffixFactor();
            }

            uint durability = fields.Read<uint>(11);
            SetUInt32Value(ItemFields.Durability, durability);
            // update max durability (and durability) if need
            SetUInt32Value(ItemFields.MaxDurability, proto.MaxDurability);
            if (durability > proto.MaxDurability)
            {
                SetUInt32Value(ItemFields.Durability, proto.MaxDurability);
                need_save = true;
            }

            SetUInt32Value(ItemFields.CreatePlayedTime, fields.Read<uint>(12));
            SetText(fields.Read<string>(13));

            uint upgradeId = fields.Read<uint>(14);
            ItemUpgradeRecord rulesetUpgrade = CliDB.ItemUpgradeStorage.LookupByKey(Global.DB2Mgr.GetRulesetItemUpgrade(entry));
            ItemUpgradeRecord upgrade = CliDB.ItemUpgradeStorage.LookupByKey(upgradeId);
            if (rulesetUpgrade == null || upgrade == null || rulesetUpgrade.ItemUpgradePathID != upgrade.ItemUpgradePathID)
            {
                upgradeId = 0;
                need_save = true;
            }

            if (rulesetUpgrade != null && upgradeId == 0)
            {
                upgradeId = rulesetUpgrade.Id;
                need_save = true;
            }

            SetModifier(ItemModifier.UpgradeId, upgradeId);
            SetModifier(ItemModifier.BattlePetSpeciesId, fields.Read<uint>(15));
            SetModifier(ItemModifier.BattlePetBreedData, fields.Read<uint>(16));
            SetModifier(ItemModifier.BattlePetLevel, fields.Read<ushort>(17));
            SetModifier(ItemModifier.BattlePetDisplayId, fields.Read<uint>(18));

            SetUInt32Value(ItemFields.Context, fields.Read<byte>(19));

            var bonusListIDs = new StringArray(fields.Read<string>(20), ' ');
            for (var i = 0; i < bonusListIDs.Length; ++i)
            {
                uint bonusListID = uint.Parse(tokens[i]);
                AddBonuses(bonusListID);
            }

            SetModifier(ItemModifier.TransmogAppearanceAllSpecs, fields.Read<uint>(21));
            SetModifier(ItemModifier.TransmogAppearanceSpec1, fields.Read<uint>(22));
            SetModifier(ItemModifier.TransmogAppearanceSpec2, fields.Read<uint>(23));
            SetModifier(ItemModifier.TransmogAppearanceSpec3, fields.Read<uint>(24));
            SetModifier(ItemModifier.TransmogAppearanceSpec4, fields.Read<uint>(25));

            SetModifier(ItemModifier.EnchantIllusionAllSpecs, fields.Read<uint>(26));
            SetModifier(ItemModifier.EnchantIllusionSpec1, fields.Read<uint>(27));
            SetModifier(ItemModifier.EnchantIllusionSpec2, fields.Read<uint>(28));
            SetModifier(ItemModifier.EnchantIllusionSpec3, fields.Read<uint>(29));
            SetModifier(ItemModifier.EnchantIllusionSpec4, fields.Read<uint>(30));

            int gemFields = 4;
            ItemDynamicFieldGems[] gemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets];
            for (int i = 0; i < ItemConst.MaxGemSockets; ++i)
            {
                gemData[i] = new ItemDynamicFieldGems();
                gemData[i].ItemId = fields.Read<uint>(31 + i * gemFields);
                var gemBonusListIDs = new StringArray(fields.Read<string>(32 + i * gemFields), ' ');
                uint b = 0;
                foreach (string token in gemBonusListIDs)
                {
                    uint bonusListID = uint.Parse(token);
                    if (bonusListID != 0)
                        gemData[i].BonusListIDs[b++] = (ushort)bonusListID;
                }

                gemData[i].Context = fields.Read<byte>(33 + i * gemFields);
                if (gemData[i].ItemId != 0)
                    SetGem((ushort)i, gemData[i], fields.Read<uint>(34 + i * gemFields));
            }

            SetModifier(ItemModifier.ScalingStatDistributionFixedLevel, fields.Read<uint>(43));
            SetModifier(ItemModifier.ArtifactKnowledgeLevel, fields.Read<uint>(44));

            // Remove bind flag for items vs NO_BIND set
            if (IsSoulBound() && GetBonding() == ItemBondingType.None)
            {
                ApplyModFlag(ItemFields.Flags, ItemFieldFlags.Soulbound, false);
                need_save = true;
            }

            if (need_save)                                           // normal item changed state set not work at loading
            {
                byte index = 0;
                PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_INSTANCE_ON_LOAD);
                stmt.AddValue(index++, GetUInt32Value(ItemFields.Duration));
                stmt.AddValue(index++, GetUInt32Value(ItemFields.Flags));
                stmt.AddValue(index++, GetUInt32Value(ItemFields.Durability));
                stmt.AddValue(index++, GetModifier(ItemModifier.UpgradeId));
                stmt.AddValue(index++, guid);
                DB.Characters.Execute(stmt);
            }
            return true;
        }

        public void LoadArtifactData(Player owner, ulong xp, uint artifactAppearanceId, List<ItemDynamicFieldArtifactPowers> powers)
        {
            InitArtifactPowers(GetTemplate().GetArtifactID(), 0);
            SetUInt64Value(ItemFields.ArtifactXp, xp);
            SetModifier(ItemModifier.ArtifactAppearanceId, artifactAppearanceId);
            ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(artifactAppearanceId);
            if (artifactAppearance != null)
                SetAppearanceModId(artifactAppearance.AppearanceModID);

            byte totalPurchasedRanks = 0;
            foreach (ItemDynamicFieldArtifactPowers power in powers)
            {
                power.CurrentRankWithBonus += power.PurchasedRank;
                totalPurchasedRanks += power.PurchasedRank;

                ArtifactPowerRecord artifactPower = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId);
                for (var e = EnchantmentSlot.Sock1; e <= EnchantmentSlot.Sock3; ++e)
                {
                    SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e));
                    if (enchant != null)
                    {
                        for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i)
                        {
                            switch (enchant.Effect[i])
                            {
                                case ItemEnchantmentType.ArtifactPowerBonusRankByType:
                                    if (artifactPower.RelicType == enchant.EffectSpellID[i])
                                        power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                    break;
                                case ItemEnchantmentType.ArtifactPowerBonusRankByID:
                                    if (artifactPower.Id == enchant.EffectSpellID[i])
                                        power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                    break;
                                case ItemEnchantmentType.ArtifactPowerBonusRankPicker:
                                    if (_bonusData.GemRelicType[e - EnchantmentSlot.Sock1] != -1)
                                    {
                                        ArtifactPowerPickerRecord artifactPowerPicker = CliDB.ArtifactPowerPickerStorage.LookupByKey(enchant.EffectSpellID[i]);
                                        if (artifactPowerPicker != null)
                                        {
                                            PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactPowerPicker.PlayerConditionID);
                                            if (playerCondition == null || ConditionManager.IsPlayerMeetingCondition(owner, playerCondition))
                                                if (artifactPower.RelicType == _bonusData.GemRelicType[e - EnchantmentSlot.Sock1])
                                                    power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                        }
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }

                SetArtifactPower(power);
            }

            foreach (ItemDynamicFieldArtifactPowers power in powers)
            {
                ArtifactPowerRecord scaledArtifactPowerEntry = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId);
                if (!scaledArtifactPowerEntry.Flags.HasAnyFlag(ArtifactPowerFlag.ScalesWithNumPowers))
                    continue;

                power.CurrentRankWithBonus = (byte)(totalPurchasedRanks + 1);
                SetArtifactPower(power);
            }
        }

        public static void DeleteFromDB(SQLTransaction trans, ulong itemGuid)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE);
            stmt.AddValue(0, itemGuid);
            trans.Append(stmt);
        }

        public virtual void DeleteFromDB(SQLTransaction trans)
        {
            DeleteFromDB(trans, GetGUID().GetCounter());

            // Delete the items if this is a container
            if (!loot.isLooted())
                ItemContainerDeleteLootMoneyAndLootItemsFromDB();
        }

        public static void DeleteFromInventoryDB(SQLTransaction trans, ulong itemGuid)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM);
            stmt.AddValue(0, itemGuid);
            trans.Append(stmt);
        }

        public void DeleteFromInventoryDB(SQLTransaction trans)
        {
            DeleteFromInventoryDB(trans, GetGUID().GetCounter());
        }

        public ItemTemplate GetTemplate()
        {
            return Global.ObjectMgr.GetItemTemplate(GetEntry());
        }

        public Player GetOwner()
        {
            return Global.ObjAccessor.FindPlayer(GetOwnerGUID());
        }

        public SkillType GetSkill()
        {
            ItemTemplate proto = GetTemplate();
            return proto.GetSkill();
        }

        public void SetItemRandomProperties(ItemRandomEnchantmentId randomPropId)
        {
            if (randomPropId.Id == 0)
                return;

            switch (randomPropId.Type)
            {
                case ItemRandomEnchantmentType.Property:
                    {
                        ItemRandomPropertiesRecord item_rand = CliDB.ItemRandomPropertiesStorage.LookupByKey(randomPropId.Id);
                        if (item_rand != null)
                        {
                            if (GetUInt32Value(ItemFields.RandomPropertiesId) != randomPropId.Id)
                            {
                                SetUInt32Value(ItemFields.RandomPropertiesId, randomPropId.Id);
                                SetState(ItemUpdateState.Changed, GetOwner());
                            }
                            for (EnchantmentSlot i = EnchantmentSlot.Prop0; i <= EnchantmentSlot.Prop4; ++i)
                                SetEnchantment(i, item_rand.Enchantment[i - EnchantmentSlot.Prop0], 0, 0);
                        }
                    }
                    break;
                case ItemRandomEnchantmentType.Suffix:
                    {
                        ItemRandomSuffixRecord item_rand = CliDB.ItemRandomSuffixStorage.LookupByKey(randomPropId.Id);
                        if (item_rand != null)
                        {
                            if (GetInt32Value(ItemFields.RandomPropertiesId) != -(int)randomPropId.Id || GetItemSuffixFactor() == 0)
                            {
                                SetInt32Value(ItemFields.RandomPropertiesId, -(int)randomPropId.Id);
                                UpdateItemSuffixFactor();
                                SetState(ItemUpdateState.Changed, GetOwner());
                            }

                            for (var i = EnchantmentSlot.Prop0; i <= EnchantmentSlot.Prop4; ++i)
                                SetEnchantment(i, item_rand.Enchantment[i - EnchantmentSlot.Prop0], 0, 0);
                        }
                    }
                    break;
                case ItemRandomEnchantmentType.BonusList:
                    AddBonuses(randomPropId.Id);
                    break;
                default:
                    break;
            }
        }

        void UpdateItemSuffixFactor()
        {
            uint suffixFactor = ItemEnchantment.GenerateEnchSuffixFactor(GetEntry());
            if (GetItemSuffixFactor() == suffixFactor)
                return;
            SetUInt32Value(ItemFields.PropertySeed, suffixFactor);
        }

        public void SetState(ItemUpdateState state, Player forplayer = null)
        {
            if (uState == ItemUpdateState.New && uState == ItemUpdateState.Removed)
            {
                // pretend the item never existed
                if (forplayer)
                {
                    RemoveItemFromUpdateQueueOf(this, forplayer);
                    forplayer.DeleteRefundReference(GetGUID());
                }
                return;
            }
            if (state != ItemUpdateState.Unchanged)
            {
                // new items must stay in new state until saved
                if (uState != ItemUpdateState.New)
                    uState = state;

                if (forplayer)
                    AddItemToUpdateQueueOf(this, forplayer);
            }
            else
            {
                // unset in queue
                // the item must be removed from the queue manually
                uQueuePos = -1;
                uState = ItemUpdateState.Unchanged;
            }
        }

        static void AddItemToUpdateQueueOf(Item item, Player player)
        {
            if (item.IsInUpdateQueue())
                return;

            Contract.Assert(player != null);

            if (player.GetGUID() != item.GetOwnerGUID())
            {
                Log.outError(LogFilter.Player, "Item.AddToUpdateQueueOf - Owner's guid ({0}) and player's guid ({1}) don't match!", item.GetOwnerGUID(), player.GetGUID().ToString());
                return;
            }

            if (player.m_itemUpdateQueueBlocked)
                return;

            player.ItemUpdateQueue.Add(item);
            item.uQueuePos = player.ItemUpdateQueue.Count - 1;
        }

        public static void RemoveItemFromUpdateQueueOf(Item item, Player player)
        {
            if (!item.IsInUpdateQueue())
                return;

            Contract.Assert(player != null);

            if (player.GetGUID() != item.GetOwnerGUID())
            {
                Log.outError(LogFilter.Player, "Item.RemoveFromUpdateQueueOf - Owner's guid ({0}) and player's guid ({1}) don't match!", item.GetOwnerGUID().ToString(), player.GetGUID().ToString());
                return;
            }

            if (player.m_itemUpdateQueueBlocked)
                return;

            player.ItemUpdateQueue[item.uQueuePos] = null;
            item.uQueuePos = -1;
        }

        public byte GetBagSlot()
        {
            return m_container != null ? m_container.GetSlot() : InventorySlots.Bag0;
        }

        public bool IsEquipped() { return !IsInBag() && m_slot < EquipmentSlot.End; }

        public bool CanBeTraded(bool mail = false, bool trade = false)
        {
            if (m_lootGenerated)
                return false;

            if ((!mail || !IsBoundAccountWide()) && (IsSoulBound() && (!HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable) || !trade)))
                return false;

            if (IsBag() && (Player.IsBagPos(GetPos()) || !ToBag().IsEmpty()))
                return false;

            Player owner = GetOwner();
            if (owner != null)
            {
                if (owner.CanUnequipItem(GetPos(), false) != InventoryResult.Ok)
                    return false;
                if (owner.GetLootGUID() == GetGUID())
                    return false;
            }

            if (IsBoundByEnchant())
                return false;

            return true;
        }

        public void SetCount(uint value)
        {
            SetUInt32Value(ItemFields.StackCount, value);

            Player player = GetOwner();
            if (player)
            {
                TradeData tradeData = player.GetTradeData();
                if (tradeData != null)
                {
                    TradeSlots slot = tradeData.GetTradeSlotForItem(GetGUID());

                    if (slot != TradeSlots.Invalid)
                        tradeData.SetItem(slot, this, true);
                }
            }
        }

        bool HasEnchantRequiredSkill(Player player)
        {
            // Check all enchants for required skill
            for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot)
            {
                uint enchant_id = GetEnchantmentId(enchant_slot);
                if (enchant_id != 0)
                {
                    SpellItemEnchantmentRecord enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id);
                    if (enchantEntry != null)
                        if (enchantEntry.RequiredSkillID != 0 && player.GetSkillValue((SkillType)enchantEntry.RequiredSkillID) < enchantEntry.RequiredSkillRank)
                            return false;
                }
            }

            return true;
        }

        uint GetEnchantRequiredLevel()
        {
            uint level = 0;

            // Check all enchants for required level
            for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot)
            {
                uint enchant_id = GetEnchantmentId(enchant_slot);
                if (enchant_id != 0)
                {
                    var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id);
                    if (enchantEntry != null)
                        if (enchantEntry.MinLevel > level)
                            level = enchantEntry.MinLevel;
                }
            }

            return level;
        }

        bool IsBoundByEnchant()
        {
            // Check all enchants for soulbound
            for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot)
            {
                uint enchant_id = GetEnchantmentId(enchant_slot);
                if (enchant_id != 0)
                {
                    var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id);
                    if (enchantEntry != null)
                        if (enchantEntry.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound))
                            return true;
                }
            }

            return false;
        }

        public InventoryResult CanBeMergedPartlyWith(ItemTemplate proto)
        {
            // not allow merge looting currently items
            if (m_lootGenerated)
                return InventoryResult.LootGone;

            // check item type
            if (GetEntry() != proto.GetId())
                return InventoryResult.CantStack;

            // check free space (full stacks can't be target of merge
            if (GetCount() >= proto.GetMaxStackSize())
                return InventoryResult.CantStack;

            return InventoryResult.Ok;
        }

        public bool IsFitToSpellRequirements(SpellInfo spellInfo)
        {
            ItemTemplate proto = GetTemplate();

            bool isEnchantSpell = spellInfo.HasEffect(SpellEffectName.EnchantItem) || spellInfo.HasEffect(SpellEffectName.EnchantItemTemporary) || spellInfo.HasEffect(SpellEffectName.EnchantItemPrismatic);
            if ((int)spellInfo.EquippedItemClass != -1)                 // -1 == any item class
            {
                if (isEnchantSpell && proto.GetFlags3().HasAnyFlag(ItemFlags3.CanStoreEnchants))
                    return true;

                if (spellInfo.EquippedItemClass != proto.GetClass())
                    return false;                                   //  wrong item class

                if (spellInfo.EquippedItemSubClassMask != 0)        // 0 == any subclass
                {
                    if ((spellInfo.EquippedItemSubClassMask & (1 << (int)proto.GetSubClass())) == 0)
                        return false;                               // subclass not present in mask
                }
            }

            if (isEnchantSpell && spellInfo.EquippedItemInventoryTypeMask != 0)       // 0 == any inventory type
            {
                // Special case - accept weapon type for main and offhand requirements
                if (proto.GetInventoryType() == InventoryType.Weapon &&
                    Convert.ToBoolean(spellInfo.EquippedItemInventoryTypeMask & (1 << (int)InventoryType.WeaponMainhand)) ||
                     Convert.ToBoolean(spellInfo.EquippedItemInventoryTypeMask & (1 << (int)InventoryType.WeaponOffhand)))
                    return true;
                else if ((spellInfo.EquippedItemInventoryTypeMask & (1 << (int)proto.GetInventoryType())) == 0)
                    return false;                                   // inventory type not present in mask
            }

            return true;
        }

        public void SetEnchantment(EnchantmentSlot slot, uint id, uint duration, uint charges, ObjectGuid caster = default(ObjectGuid))
        {
            // Better lost small time at check in comparison lost time at item save to DB.
            if ((GetEnchantmentId(slot) == id) && (GetEnchantmentDuration(slot) == duration) && (GetEnchantmentCharges(slot) == charges))
                return;

            Player owner = GetOwner();
            if (slot < EnchantmentSlot.MaxInspected)
            {
                uint oldEnchant = GetEnchantmentId(slot);
                if (oldEnchant != 0)
                    owner.GetSession().SendEnchantmentLog(GetOwnerGUID(), ObjectGuid.Empty, GetGUID(), GetEntry(), oldEnchant, (uint)slot);

                if (id != 0)
                    owner.GetSession().SendEnchantmentLog(GetOwnerGUID(), caster, GetGUID(), GetEntry(), id, (uint)slot);
            }

            ApplyArtifactPowerEnchantmentBonuses(slot, GetEnchantmentId(slot), false, owner);
            ApplyArtifactPowerEnchantmentBonuses(slot, id, true, owner);

            SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Id, id);
            SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration, duration);
            SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges, charges);
            SetState(ItemUpdateState.Changed, owner);
        }

        public void SetEnchantmentDuration(EnchantmentSlot slot, uint duration, Player owner)
        {
            if (GetEnchantmentDuration(slot) == duration)
                return;

            SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration, duration);
            SetState(ItemUpdateState.Changed, owner);
            // Cannot use GetOwner() here, has to be passed as an argument to avoid freeze due to hashtable locking
        }

        public void SetEnchantmentCharges(EnchantmentSlot slot, uint charges)
        {
            if (GetEnchantmentCharges(slot) == charges)
                return;

            SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges, charges);
            SetState(ItemUpdateState.Changed, GetOwner());
        }

        public void ClearEnchantment(EnchantmentSlot slot)
        {
            if (GetEnchantmentId(slot) == 0)
                return;

            for (byte x = 0; x < ItemConst.MaxItemEnchantmentEffects; ++x)
                SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + x, 0);
            SetState(ItemUpdateState.Changed, GetOwner());
        }

        public List<ItemDynamicFieldGems> GetGems()
        {
            return GetDynamicStructuredValues<ItemDynamicFieldGems>(ItemDynamicFields.Gems);
        }

        public ItemDynamicFieldGems GetGem(ushort slot)
        {
            //ASSERT(slot < MAX_GEM_SOCKETS);
            return GetDynamicStructuredValue<ItemDynamicFieldGems>(ItemDynamicFields.Gems, slot);
        }

        public void SetGem(ushort slot, ItemDynamicFieldGems gem, uint gemScalingLevel)
        {
            //ASSERT(slot < MAX_GEM_SOCKETS);
            m_gemScalingLevels[slot] = gemScalingLevel;
            _bonusData.GemItemLevelBonus[slot] = 0;
            ItemTemplate gemTemplate = Global.ObjectMgr.GetItemTemplate(gem.ItemId);
            if (gemTemplate != null)
            {
                GemPropertiesRecord gemProperties = CliDB.GemPropertiesStorage.LookupByKey(gemTemplate.GetGemProperties());
                if (gemProperties != null)
                {
                    SpellItemEnchantmentRecord gemEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(gemProperties.EnchantID);
                    if (gemEnchant != null)
                    {
                        BonusData gemBonus = new BonusData(gemTemplate);
                        foreach (var bonusListId in gem.BonusListIDs)
                        {

                            var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListId);
                            if (bonuses != null)
                            {

                                foreach (ItemBonusRecord itemBonus in bonuses)
                                    gemBonus.AddBonus(itemBonus.Type, itemBonus.Value);
                            }

                            uint gemBaseItemLevel = gemTemplate.GetBaseItemLevel();
                            ScalingStatDistributionRecord ssd = CliDB.ScalingStatDistributionStorage.LookupByKey(gemBonus.ScalingStatDistribution);
                            if (ssd != null)
                            {
                                uint scaledIlvl = (uint)Global.DB2Mgr.GetCurveValueAt(ssd.ItemLevelCurveID, gemScalingLevel);
                                if (scaledIlvl != 0)
                                    gemBaseItemLevel = scaledIlvl;
                            }

                            _bonusData.GemRelicType[slot] = gemBonus.RelicType;

                            for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i)
                            {
                                switch (gemEnchant.Effect[i])
                                {
                                    case ItemEnchantmentType.BonusListID:
                                        {
                                            var bonusesEffect = Global.DB2Mgr.GetItemBonusList(gemEnchant.EffectSpellID[i]);
                                            if (bonusesEffect != null)
                                            {
                                                foreach (ItemBonusRecord itemBonus in bonusesEffect)
                                                    if (itemBonus.Type == ItemBonusType.ItemLevel)

                                                        _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0];
                                            }
                                            break;
                                        }
                                    case ItemEnchantmentType.BonusListCurve:
                                        {
                                            uint artifactrBonusListId = Global.DB2Mgr.GetItemBonusListForItemLevelDelta((short)Global.DB2Mgr.GetCurveValueAt((uint)Curves.ArtifactRelicItemLevelBonus, gemBaseItemLevel + gemBonus.ItemLevelBonus));
                                            if (artifactrBonusListId != 0)
                                            {
                                                var bonusesEffect = Global.DB2Mgr.GetItemBonusList(artifactrBonusListId);
                                                if (bonusesEffect != null)
                                                    foreach (ItemBonusRecord itemBonus in bonusesEffect)
                                                        if (itemBonus.Type == ItemBonusType.ItemLevel)
                                                            _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0];
                                            }
                                            break;
                                        }
                                    default:
                                        break;
                                }
                            }
                        }
                    }
                }
            }

            SetDynamicStructuredValue(ItemDynamicFields.Gems, slot, gem);
        }

        public bool GemsFitSockets()
        {
            uint gemSlot = 0;
            foreach (ItemDynamicFieldGems gemData in GetGems())
            {
                SocketColor SocketColor = GetTemplate().GetSocketColor(gemSlot);
                if (SocketColor == 0) // no socket slot
                    continue;

                SocketColor GemColor = 0;

                ItemTemplate gemProto = Global.ObjectMgr.GetItemTemplate(gemData.ItemId);
                if (gemProto != null)
                {
                    GemPropertiesRecord gemProperty = CliDB.GemPropertiesStorage.LookupByKey(gemProto.GetGemProperties());
                    if (gemProperty != null)
                        GemColor = gemProperty.Type;
                }

                if (!GemColor.HasAnyFlag(ItemConst.SocketColorToGemTypeMask[(int)SocketColor])) // bad gem color on this socket
                    return false;
            }
            return true;
        }

        public byte GetGemCountWithID(uint GemID)
        {
            return (byte)GetGems().Count(gemData =>
            {
                return gemData.ItemId == GemID;
            });
        }

        public byte GetGemCountWithLimitCategory(uint limitCategory)
        {
            return (byte)GetGems().Count(gemData =>
            {
                ItemTemplate gemProto = Global.ObjectMgr.GetItemTemplate(gemData.ItemId);
                if (gemProto == null)
                    return false;

                return gemProto.GetItemLimitCategory() == limitCategory;
            });
        }

        public bool IsLimitedToAnotherMapOrZone(uint cur_mapId, uint cur_zoneId)
        {
            ItemTemplate proto = GetTemplate();
            return proto != null && ((proto.GetMap() != 0 && proto.GetMap() != cur_mapId) || (proto.GetArea() != 0 && proto.GetArea() != cur_zoneId));
        }

        public void SendUpdateSockets()
        {
            SocketGemsResult socketGems = new SocketGemsResult();
            socketGems.Item = GetGUID();

            GetOwner().SendPacket(socketGems);
        }

        public void SendTimeUpdate(Player owner)
        {
            uint duration = GetUInt32Value(ItemFields.Duration);
            if (duration == 0)
                return;

            ItemTimeUpdate itemTimeUpdate = new ItemTimeUpdate();
            itemTimeUpdate.ItemGuid = GetGUID();
            itemTimeUpdate.DurationLeft = duration;
            owner.SendPacket(itemTimeUpdate);
        }

        public static Item CreateItem(uint item, uint count, Player player = null)
        {
            if (count < 1)
                return null;                                        //don't create item at zero count

            var pProto = Global.ObjectMgr.GetItemTemplate(item);
            if (pProto != null)
            {
                if (count > pProto.GetMaxStackSize())
                    count = pProto.GetMaxStackSize();

                Item pItem = Bag.NewItemOrBag(pProto);
                if (pItem.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), item, player))
                {
                    pItem.SetCount(count);
                    return pItem;
                }
            }

            return null;
        }

        public Item CloneItem(uint count, Player player = null)
        {
            Item newItem = CreateItem(GetEntry(), count, player);
            if (newItem == null)
                return null;

            newItem.SetUInt32Value(ItemFields.Creator, GetUInt32Value(ItemFields.Creator));
            newItem.SetUInt32Value(ItemFields.GiftCreator, GetUInt32Value(ItemFields.GiftCreator));
            newItem.SetUInt32Value(ItemFields.Flags, GetUInt32Value(ItemFields.Flags));
            newItem.SetUInt32Value(ItemFields.Duration, GetUInt32Value(ItemFields.Duration));
            // player CAN be NULL in which case we must not update random properties because that accesses player's item update queue
            if (player != null)
                newItem.SetItemRandomProperties(GetItemRandomEnchantmentId());
            return newItem;
        }

        public bool IsBindedNotWith(Player player)
        {
            // not binded item
            if (!IsSoulBound())
                return false;

            // own item
            if (GetOwnerGUID() == player.GetGUID())
                return false;

            if (HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable))
                if (allowedGUIDs.Contains(player.GetGUID()))
                    return false;

            // BOA item case
            if (IsBoundAccountWide())
                return false;

            return true;
        }

        public override void BuildUpdate(Dictionary<Player, UpdateData> data)
        {
            Player owner = GetOwner();
            if (owner != null)
                BuildFieldsUpdate(owner, data);
            ClearUpdateMask(false);
        }

        public override void BuildDynamicValuesUpdate(UpdateType updateType, WorldPacket data, Player target)
        {
            if (!target)
                return;

            ByteBuffer fieldBuffer = new ByteBuffer();
            UpdateMask fieldMask = new UpdateMask(_dynamicValuesCount);

            uint[] flags = null;
            uint visibleFlag = GetDynamicUpdateFieldData(target, out flags);

            for (ushort index = 0; index < _dynamicValuesCount; ++index)
            {
                var values = _dynamicValues[index];
                if (_fieldNotifyFlags.HasAnyFlag(flags[index]) ||
                    ((updateType == UpdateType.Values ? _dynamicChangesMask[index] != DynamicFieldChangeType.Unchanged : !values.Empty()) && flags[index].HasAnyFlag(visibleFlag)))
                {
                    ByteBuffer arrayValuesBuffer = new ByteBuffer();
                    fieldMask.SetBit(index);

                    DynamicUpdateMask arrayValuesMask = new DynamicUpdateMask((uint)values.Count);
                    arrayValuesMask.EncodeDynamicFieldChangeType(_dynamicChangesMask[index], updateType);

                    if (updateType == UpdateType.Values && _dynamicChangesMask[index] == DynamicFieldChangeType.ValueAndSizeChanged)
                        arrayValuesMask.ValueCount = values.Count;

                    if (index != (int)ItemDynamicFields.Modifiers)
                    {
                        foreach (var pair in values)
                        {
                            if (updateType != UpdateType.Values || _dynamicChangesArrayMask[index].Get(pair.Key))
                            {
                                arrayValuesMask.SetBit(pair.Key);
                                arrayValuesBuffer.WriteUInt32(pair.Value);
                            }
                        }
                    }
                    else
                    {
                        int m = 0;

                        if (updateType == UpdateType.Values && _dynamicChangesMask[index] != DynamicFieldChangeType.ValueAndSizeChanged && _changesMask.Get((int)ItemFields.ModifiersMask))
                        {
                            arrayValuesMask.DynamicFieldChangeType |= (int)DynamicFieldChangeType.ValueAndSizeChanged;
                            arrayValuesMask.ValueCount = m;
                        }

                        // in case of ITEM_DYNAMIC_FIELD_MODIFIERS it is ITEM_FIELD_MODIFIERS_MASK that controls index of each value, not updatemask
                        // so we just have to write this starting from 0 index
                        foreach (var pair in values)
                        {
                            if (pair.Value != 0)
                            {
                                arrayValuesMask.SetBit(m++);
                                arrayValuesBuffer.WriteUInt32(pair.Value);
                            }
                        }

                        if (updateType == UpdateType.Values && _changesMask.Get((int)ItemFields.ModifiersMask))
                            arrayValuesMask.ValueCount = m;
                    }

                    arrayValuesMask.AppendToPacket(fieldBuffer);
                    fieldBuffer.WriteBytes(arrayValuesBuffer);
                }
            }

            fieldMask.AppendToPacket(data);
            data.WriteBytes(fieldBuffer);
        }

        public override void AddToObjectUpdate()
        {
            Player owner = GetOwner();
            if (owner)
                owner.GetMap().AddUpdateObject(this);
        }

        public override void RemoveFromObjectUpdate()
        {
            Player owner = GetOwner();
            if (owner)
                owner.GetMap().RemoveUpdateObject(this);
        }

        public void SaveRefundDataToDB()
        {
            DeleteRefundDataFromDB();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_REFUND_INSTANCE);
            stmt.AddValue(0, GetGUID().GetCounter());
            stmt.AddValue(1, GetRefundRecipient().GetCounter());
            stmt.AddValue(2, GetPaidMoney());
            stmt.AddValue(3, (ushort)GetPaidExtendedCost());
            DB.Characters.Execute(stmt);
        }

        public void DeleteRefundDataFromDB(SQLTransaction trans = null)
        {
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_REFUND_INSTANCE);
            stmt.AddValue(0, GetGUID().GetCounter());
            if (trans != null)
                trans.Append(stmt);
            else
                DB.Characters.Execute(stmt);
        }

        public void SetNotRefundable(Player owner, bool changestate = true, SQLTransaction trans = null, bool addToCollection = true)
        {
            if (!HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable))
                return;

            ItemExpirePurchaseRefund itemExpirePurchaseRefund = new ItemExpirePurchaseRefund();
            itemExpirePurchaseRefund.ItemGUID = GetGUID();
            owner.SendPacket(itemExpirePurchaseRefund);

            RemoveFlag(ItemFields.Flags, ItemFieldFlags.Refundable);
            // Following is not applicable in the trading procedure
            if (changestate)
                SetState(ItemUpdateState.Changed, owner);

            SetRefundRecipient(ObjectGuid.Empty);
            SetPaidMoney(0);
            SetPaidExtendedCost(0);
            DeleteRefundDataFromDB(trans);

            owner.DeleteRefundReference(GetGUID());
            if (addToCollection)
                owner.GetSession().GetCollectionMgr().AddItemAppearance(this);
        }

        public void UpdatePlayedTime(Player owner)
        {
            // Get current played time
            uint current_playtime = GetUInt32Value(ItemFields.CreatePlayedTime);
            // Calculate time elapsed since last played time update
            long curtime = Time.UnixTime;
            uint elapsed = (uint)(curtime - m_lastPlayedTimeUpdate);
            uint new_playtime = current_playtime + elapsed;
            // Check if the refund timer has expired yet
            if (new_playtime <= 2 * Time.Hour)
            {
                // No? Proceed.
                // Update the data field
                SetUInt32Value(ItemFields.CreatePlayedTime, new_playtime);
                // Flag as changed to get saved to DB
                SetState(ItemUpdateState.Changed, owner);
                // Speaks for itself
                m_lastPlayedTimeUpdate = curtime;
                return;
            }
            // Yes
            SetNotRefundable(owner);
        }

        public uint GetPlayedTime()
        {
            long curtime = Time.UnixTime;
            uint elapsed = (uint)(curtime - m_lastPlayedTimeUpdate);
            return GetUInt32Value(ItemFields.CreatePlayedTime) + elapsed;
        }

        public bool IsRefundExpired()
        {
            return (GetPlayedTime() > 2 * Time.Hour);
        }

        public void SetSoulboundTradeable(List<ObjectGuid> allowedLooters)
        {
            SetFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable);
            allowedGUIDs = allowedLooters;
        }

        public void ClearSoulboundTradeable(Player currentOwner)
        {
            RemoveFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable);
            if (allowedGUIDs.Empty())
                return;

            currentOwner.GetSession().GetCollectionMgr().AddItemAppearance(this);
            allowedGUIDs.Clear();
            SetState(ItemUpdateState.Changed, currentOwner);
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_BOP_TRADE);
            stmt.AddValue(0, GetGUID().GetCounter());
            DB.Characters.Execute(stmt);
        }

        public bool CheckSoulboundTradeExpire()
        {
            // called from owner's update - GetOwner() MUST be valid
            if (GetUInt32Value(ItemFields.CreatePlayedTime) + 2 * Time.Hour < GetOwner().GetTotalPlayedTime())
            {
                ClearSoulboundTradeable(GetOwner());
                return true; // remove from tradeable list
            }

            return false;
        }

        bool IsValidTransmogrificationTarget()
        {
            ItemTemplate proto = GetTemplate();
            if (proto == null)
                return false;

            if (proto.GetClass() != ItemClass.Armor &&
                proto.GetClass() != ItemClass.Weapon)
                return false;

            if (proto.GetClass() == ItemClass.Weapon && proto.GetSubClass() == (uint)ItemSubClassWeapon.FishingPole)
                return false;

            if (proto.GetFlags2().HasAnyFlag(ItemFlags2.NoAlterItemVisual))
                return false;

            if (!HasStats())
                return false;

            return true;
        }

        bool HasStats()
        {
            if (GetItemRandomPropertyId() != 0)
                return true;

            ItemTemplate proto = GetTemplate();
            Player owner = GetOwner();
            for (byte i = 0; i < ItemConst.MaxStats; ++i)
            {
                if ((owner ? GetItemStatValue(i, owner) : proto.GetItemStatValue(i)) != 0)
                    return true;
            }

            return false;
        }

        static bool HasStats(ItemInstance itemInstance, BonusData bonus)
        {
            if (itemInstance.RandomPropertiesID != 0)
                return true;

            for (byte i = 0; i < ItemConst.MaxStats; ++i)
            {
                if (bonus.ItemStatValue[i] != 0)
                    return true;
            }

            return false;
        }

        static ItemTransmogrificationWeaponCategory GetTransmogrificationWeaponCategory(ItemTemplate proto)
        {
            if (proto.GetClass() == ItemClass.Weapon)
            {
                switch ((ItemSubClassWeapon)proto.GetSubClass())
                {
                    case ItemSubClassWeapon.Axe2:
                    case ItemSubClassWeapon.Mace2:
                    case ItemSubClassWeapon.Sword2:
                    case ItemSubClassWeapon.Staff:
                    case ItemSubClassWeapon.Polearm:
                        return ItemTransmogrificationWeaponCategory.Melee2H;
                    case ItemSubClassWeapon.Bow:
                    case ItemSubClassWeapon.Gun:
                    case ItemSubClassWeapon.Crossbow:
                        return ItemTransmogrificationWeaponCategory.Ranged;
                    case ItemSubClassWeapon.Axe:
                    case ItemSubClassWeapon.Mace:
                    case ItemSubClassWeapon.Sword:
                    case ItemSubClassWeapon.Warglaives:
                        return ItemTransmogrificationWeaponCategory.AxeMaceSword1H;
                    case ItemSubClassWeapon.Dagger:
                        return ItemTransmogrificationWeaponCategory.Dagger;
                    case ItemSubClassWeapon.Fist:
                        return ItemTransmogrificationWeaponCategory.Fist;
                    default:
                        break;
                }
            }

            return ItemTransmogrificationWeaponCategory.Invalid;
        }

        static int[] ItemTransmogrificationSlots =
        {
            -1,                                                     // INVTYPE_NON_EQUIP
            EquipmentSlot.Head,                                    // INVTYPE_HEAD
            EquipmentSlot.Neck,                                    // INVTYPE_NECK
            EquipmentSlot.Shoulders,                               // INVTYPE_SHOULDERS
            EquipmentSlot.Shirt,                                    // INVTYPE_BODY
            EquipmentSlot.Chest,                                   // INVTYPE_CHEST
            EquipmentSlot.Waist,                                   // INVTYPE_WAIST
            EquipmentSlot.Legs,                                    // INVTYPE_LEGS
            EquipmentSlot.Feet,                                    // INVTYPE_FEET
            EquipmentSlot.Wrist,                                  // INVTYPE_WRISTS
            EquipmentSlot.Hands,                                   // INVTYPE_HANDS
            -1,                                                     // INVTYPE_FINGER
            -1,                                                     // INVTYPE_TRINKET
            -1,                                                     // INVTYPE_WEAPON
            EquipmentSlot.OffHand,                                 // INVTYPE_SHIELD
            EquipmentSlot.MainHand,                                // INVTYPE_RANGED
            EquipmentSlot.Cloak,                                    // INVTYPE_CLOAK
            -1,                                                     // INVTYPE_2HWEAPON
            -1,                                                     // INVTYPE_BAG
            EquipmentSlot.Tabard,                                  // INVTYPE_TABARD
            EquipmentSlot.Chest,                                   // INVTYPE_ROBE
            EquipmentSlot.MainHand,                                // INVTYPE_WEAPONMAINHAND
            EquipmentSlot.OffHand,                                 // INVTYPE_WEAPONOFFHAND
            EquipmentSlot.OffHand,                                 // INVTYPE_HOLDABLE
            -1,                                                     // INVTYPE_AMMO
            EquipmentSlot.MainHand,                                // INVTYPE_THROWN
            EquipmentSlot.MainHand,                                // INVTYPE_RANGEDRIGHT
            -1,                                                     // INVTYPE_QUIVER
            -1                                                      // INVTYPE_RELIC
        };

        public static bool CanTransmogrifyItemWithItem(Item item, ItemModifiedAppearanceRecord itemModifiedAppearance)
        {
            ItemTemplate source = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID); // source
            ItemTemplate target = item.GetTemplate(); // dest

            if (source == null || target == null)
                return false;

            if (itemModifiedAppearance == item.GetItemModifiedAppearance())
                return false;

            if (!item.IsValidTransmogrificationTarget())
                return false;

            if (source.GetClass() != target.GetClass())
                return false;

            if (source.GetInventoryType() == InventoryType.Bag ||
                source.GetInventoryType() == InventoryType.Relic ||
                source.GetInventoryType() == InventoryType.Finger ||
                source.GetInventoryType() == InventoryType.Trinket ||
                source.GetInventoryType() == InventoryType.Ammo ||
                source.GetInventoryType() == InventoryType.Quiver)
                return false;

            if (source.GetSubClass() != target.GetSubClass())
            {
                switch (source.GetClass())
                {
                    case ItemClass.Weapon:
                        if (GetTransmogrificationWeaponCategory(source) != GetTransmogrificationWeaponCategory(target))
                            return false;
                        break;
                    case ItemClass.Armor:
                        if ((ItemSubClassArmor)source.GetSubClass() != ItemSubClassArmor.Cosmetic)
                            return false;
                        break;
                    default:
                        return false;
                }
            }

            if (source.GetInventoryType() != target.GetInventoryType())
            {
                int sourceSlot = ItemTransmogrificationSlots[(int)source.GetInventoryType()];
                if (sourceSlot == -1 && source.GetInventoryType() == InventoryType.Weapon && (target.GetInventoryType() == InventoryType.WeaponMainhand || target.GetInventoryType() == InventoryType.WeaponOffhand))
                    sourceSlot = ItemTransmogrificationSlots[(int)target.GetInventoryType()];

                if (sourceSlot != ItemTransmogrificationSlots[(int)target.GetInventoryType()])
                    return false;
            }

            return true;
        }

        // used by mail items, transmog cost, stationeryinfo and others
        static uint GetSellPrice(ItemTemplate proto, out bool normalSellPrice)
        {
            normalSellPrice = true;

            if (proto.GetFlags2().HasAnyFlag(ItemFlags2.OverrideGoldCost))
            {
                return proto.GetBuyPrice();
            }
            else
            {
                var qualityPrice = CliDB.ImportPriceQualityStorage.LookupByKey(proto.GetQuality() + 1);
                var basePrice = CliDB.ItemPriceBaseStorage.LookupByKey(proto.GetBaseItemLevel());

                if (qualityPrice == null || basePrice == null)
                    return 0;

                float qualityFactor = qualityPrice.Factor;
                float baseFactor = 0.0f;

                var inventoryType = proto.GetInventoryType();

                if (inventoryType == InventoryType.Weapon ||
                    inventoryType == InventoryType.Weapon2Hand ||
                    inventoryType == InventoryType.WeaponMainhand ||
                    inventoryType == InventoryType.WeaponOffhand ||
                    inventoryType == InventoryType.Ranged ||
                    inventoryType == InventoryType.Thrown ||
                    inventoryType == InventoryType.RangedRight)
                    baseFactor = basePrice.WeaponFactor;
                else
                    baseFactor = basePrice.ArmorFactor;

                if (inventoryType == InventoryType.Robe)
                    inventoryType = InventoryType.Chest;

                float typeFactor = 0.0f;
                sbyte weapType = -1;

                switch (inventoryType)
                {
                    case InventoryType.Head:
                    case InventoryType.Shoulders:
                    case InventoryType.Chest:
                    case InventoryType.Waist:
                    case InventoryType.Legs:
                    case InventoryType.Feet:
                    case InventoryType.Wrists:
                    case InventoryType.Hands:
                    case InventoryType.Cloak:
                        {
                            var armorPrice = CliDB.ImportPriceArmorStorage.LookupByKey(inventoryType);
                            if (armorPrice == null)
                                return 0;

                            switch ((ItemSubClassArmor)proto.GetSubClass())
                            {
                                case ItemSubClassArmor.Miscellaneous:
                                case ItemSubClassArmor.Cloth:
                                    typeFactor = armorPrice.ClothFactor;
                                    break;
                                case ItemSubClassArmor.Leather:
                                    typeFactor = armorPrice.LeatherFactor;
                                    break;
                                case ItemSubClassArmor.Mail:
                                    typeFactor = armorPrice.MailFactor;
                                    break;
                                case ItemSubClassArmor.Plate:
                                    typeFactor = armorPrice.PlateFactor;
                                    break;
                                default:
                                    return 0;
                            }

                            break;
                        }
                    case InventoryType.Shield:
                        {
                            var shieldPrice = CliDB.ImportPriceShieldStorage.LookupByKey(1); // it only has two rows, it's unclear which is the one used
                            if (shieldPrice == null)
                                return 0;

                            typeFactor = shieldPrice.Factor;
                            break;
                        }
                    case InventoryType.WeaponMainhand:
                        weapType = 0;
                        break;
                    case InventoryType.WeaponOffhand:
                        weapType = 1;
                        break;
                    case InventoryType.Weapon:
                        weapType = 2;
                        break;
                    case InventoryType.Weapon2Hand:
                        weapType = 3;
                        break;
                    case InventoryType.Ranged:
                    case InventoryType.RangedRight:
                    case InventoryType.Relic:
                        weapType = 4;
                        break;
                    default:
                        return proto.GetBuyPrice();
                }

                if (weapType != -1)
                {
                    var weaponPrice = CliDB.ImportPriceWeaponStorage.LookupByKey(weapType + 1);
                    if (weaponPrice == null)
                        return 0;

                    typeFactor = weaponPrice.Factor;
                }

                normalSellPrice = false;
                return (uint)(qualityFactor * proto.GetUnk2() * proto.GetUnk1() * typeFactor * baseFactor);
            }
        }

        public static uint GetSpecialPrice(ItemTemplate proto, uint minimumPrice = 10000)
        {
            uint cost = 0;

            if (proto.GetFlags2().HasAnyFlag(ItemFlags2.OverrideGoldCost))
                cost = proto.GetSellPrice();
            else
            {
                bool normalPrice;
                cost = GetSellPrice(proto, out normalPrice);

                if (!normalPrice)
                {
                    if (proto.GetBuyCount() <= 1)
                    {
                        var classEntry = Global.DB2Mgr.GetItemClassByOldEnum(proto.GetClass());
                        if (classEntry != null)
                            cost *= (uint)classEntry.PriceMod;
                        else
                            cost = 0;
                    }
                    else
                        cost /= 4 * proto.GetBuyCount();
                }
                else
                    cost = proto.GetSellPrice();
            }

            if (cost < minimumPrice)
                cost = minimumPrice;

            return cost;
        }

        public int GetReforgableStat(ItemModType statType)
        {
            ItemTemplate proto = GetTemplate();
            for (uint i = 0; i < ItemConst.MaxStats; ++i)
                if ((ItemModType)proto.GetItemStatType(i) == statType)
                    return proto.GetItemStatValue(i);

            int randomPropId = GetItemRandomPropertyId();
            if (randomPropId == 0)
                return 0;

            if (randomPropId < 0)
            {
                ItemRandomSuffixRecord randomSuffix = CliDB.ItemRandomSuffixStorage.LookupByKey(-randomPropId);
                if (randomSuffix == null)
                    return 0;

                for (var e = EnchantmentSlot.Prop0; e <= EnchantmentSlot.Prop4; ++e)
                {
                    var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e));
                    if (enchant != null)
                        for (uint f = 0; f < ItemConst.MaxItemEnchantmentEffects; ++f)
                            if (enchant.Effect[f] == ItemEnchantmentType.Stat && (ItemModType)enchant.EffectSpellID[f] == statType)
                                for (int k = 0; k < 5; ++k)
                                    if (randomSuffix.Enchantment[k] == enchant.Id)
                                        return (int)((randomSuffix.AllocationPct[k] * GetItemSuffixFactor()) / 10000);
                }
            }
            else
            {
                var randomProp = CliDB.ItemRandomPropertiesStorage.LookupByKey(randomPropId);
                if (randomProp == null)
                    return 0;

                for (var e = EnchantmentSlot.Prop0; e <= EnchantmentSlot.Prop4; ++e)
                {
                    var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e));
                    if (enchant != null)
                        for (uint f = 0; f < ItemConst.MaxItemEnchantmentEffects; ++f)
                            if (enchant.Effect[f] == ItemEnchantmentType.Stat && (ItemModType)enchant.EffectSpellID[f] == statType)
                                for (int k = 0; k < ItemConst.MaxItemRandomProperties; ++k)
                                    if (randomProp.Enchantment[k] == enchant.Id)
                                        return (int)(enchant.EffectPointsMin[k]);
                }
            }

            return 0;
        }

        public void ItemContainerSaveLootToDB()
        {
            // Saves the money and item loot associated with an openable item to the DB
            if (loot.isLooted()) // no money and no loot
                return;

            SQLTransaction trans = new SQLTransaction();

            loot.containerID = GetGUID(); // Save this for when a LootItem is removed

            // Save money
            if (loot.gold > 0)
            {
                PreparedStatement stmt_money = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY);
                stmt_money.AddValue(0, loot.containerID.GetCounter());
                trans.Append(stmt_money);

                stmt_money = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_MONEY);
                stmt_money.AddValue(0, loot.containerID.GetCounter());
                stmt_money.AddValue(1, loot.gold);
                trans.Append(stmt_money);
            }

            // Save items
            if (!loot.isLooted())
            {
                PreparedStatement stmt_items = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS);
                stmt_items.AddValue(0, loot.containerID.GetCounter());
                trans.Append(stmt_items);

                // Now insert the items
                foreach (var _li in loot.items)
                {
                    // When an item is looted, it doesn't get removed from the items collection
                    //  but we don't want to resave it.
                    if (!_li.canSave)
                        continue;

                    Player guid = GetOwner();
                    if (!_li.AllowedForPlayer(guid))
                        continue;

                    stmt_items = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS);

                    // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
                    stmt_items.AddValue(0, loot.containerID.GetCounter());
                    stmt_items.AddValue(1, _li.itemid);
                    stmt_items.AddValue(2, _li.count);
                    stmt_items.AddValue(3, _li.follow_loot_rules);
                    stmt_items.AddValue(4, _li.freeforall);
                    stmt_items.AddValue(5, _li.is_blocked);
                    stmt_items.AddValue(6, _li.is_counted);
                    stmt_items.AddValue(7, _li.is_underthreshold);
                    stmt_items.AddValue(8, _li.needs_quest);
                    stmt_items.AddValue(9, (byte)_li.randomPropertyId.Type);
                    stmt_items.AddValue(10, _li.randomPropertyId.Id);
                    stmt_items.AddValue(11, _li.randomSuffix);
                    stmt_items.AddValue(12, _li.context);

                    string bonusListIDs = "";
                    foreach (int bonusListID in _li.BonusListIDs)
                        bonusListIDs += bonusListID + ' ';

                    stmt_items.AddValue(13, bonusListIDs);
                    trans.Append(stmt_items);
                }
            }
            DB.Characters.CommitTransaction(trans);
        }

        public bool ItemContainerLoadLootFromDB()
        {
            // Loads the money and item loot associated with an openable item from the DB
            // Default. If there are no records for this item then it will be rolled for in Player.SendLoot()
            m_lootGenerated = false;

            // Save this for later use
            loot.containerID = GetGUID();

            // First, see if there was any money loot. This gets added directly to the container.
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_MONEY);
            stmt.AddValue(0, loot.containerID.GetCounter());
            SQLResult money_result = DB.Characters.Query(stmt);

            if (!money_result.IsEmpty())
            {
                loot.gold = money_result.Read<uint>(0);
            }

            // Next, load any items that were saved
            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS);
            stmt.AddValue(0, loot.containerID.GetCounter());
            SQLResult item_result = DB.Characters.Query(stmt);

            if (!item_result.IsEmpty())
            {
                // Get a LootTemplate for the container item. This is where
                //  the saved loot was originally rolled from, we will copy conditions from it
                LootTemplate lt = LootStorage.Items.GetLootFor(GetEntry());
                if (lt != null)
                {
                    do
                    {
                        // Create an empty LootItem
                        LootItem loot_item = new LootItem();

                        // item_id, itm_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
                        loot_item.itemid = item_result.Read<uint>(0);
                        loot_item.count = item_result.Read<byte>(1);
                        loot_item.follow_loot_rules = item_result.Read<bool>(2);
                        loot_item.freeforall = item_result.Read<bool>(3);
                        loot_item.is_blocked = item_result.Read<bool>(4);
                        loot_item.is_counted = item_result.Read<bool>(5);
                        loot_item.canSave = true;
                        loot_item.is_underthreshold = item_result.Read<bool>(6);
                        loot_item.needs_quest = item_result.Read<bool>(7);
                        loot_item.randomPropertyId = new ItemRandomEnchantmentId((ItemRandomEnchantmentType)item_result.Read<byte>(8), item_result.Read<uint>(9));
                        loot_item.randomSuffix = item_result.Read<uint>(10);
                        loot_item.context = item_result.Read<byte>(11);

                        StringArray bonusLists = new StringArray(item_result.Read<string>(12), ' ');
                        foreach (string line in bonusLists)
                        {
                            loot_item.BonusListIDs.Add(uint.Parse(line));
                        }

                        // Copy the extra loot conditions from the item in the loot template
                        lt.CopyConditions(loot_item);

                        // If container item is in a bag, add that player as an allowed looter
                        if (GetBagSlot() != 0)
                            loot_item.allowedGUIDs.Add(GetOwner().GetGUID());

                        // Finally add the LootItem to the container
                        loot.items.Add(loot_item);

                        // Increment unlooted count
                        loot.unlootedCount++;
                    }
                    while (item_result.NextRow());
                }
            }

            // Mark the item if it has loot so it won't be generated again on open
            m_lootGenerated = !loot.isLooted();

            return m_lootGenerated;
        }

        void ItemContainerDeleteLootItemsFromDB()
        {
            // Deletes items associated with an openable item from the DB
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS);
            stmt.AddValue(0, GetGUID().GetCounter());
            DB.Characters.Execute(stmt);
        }

        void ItemContainerDeleteLootItemFromDB(uint itemID)
        {
            // Deletes a single item associated with an openable item from the DB
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM);
            stmt.AddValue(0, GetGUID().GetCounter());
            stmt.AddValue(1, itemID);
            DB.Characters.Execute(stmt);
        }

        void ItemContainerDeleteLootMoneyFromDB()
        {
            // Deletes the money loot associated with an openable item from the DB
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY);
            stmt.AddValue(0, GetGUID().GetCounter());
            DB.Characters.Execute(stmt);
        }

        public void ItemContainerDeleteLootMoneyAndLootItemsFromDB()
        {
            // Deletes money and items associated with an openable item from the DB
            ItemContainerDeleteLootMoneyFromDB();
            ItemContainerDeleteLootItemsFromDB();
        }

        public uint GetItemLevel(Player owner)
        {
            ItemTemplate stats = GetTemplate();
            if (stats == null)
                return 1;

            uint itemLevel = stats.GetBaseItemLevel();
            if (_bonusData.HasItemLevelBonus || _bonusData.ItemLevelOverride == 0)
            {
                ScalingStatDistributionRecord ssd = CliDB.ScalingStatDistributionStorage.LookupByKey(GetScalingStatDistribution());
                if (ssd != null)
                {
                    uint level = owner.getLevel();
                    uint fixedLevel = GetModifier(ItemModifier.ScalingStatDistributionFixedLevel);
                    if (fixedLevel != 0)
                        level = fixedLevel;
                    uint heirloomIlvl = (uint)Global.DB2Mgr.GetCurveValueAt(ssd.ItemLevelCurveID, level);
                    if (heirloomIlvl != 0)
                        itemLevel = heirloomIlvl;
                }

                itemLevel += (uint)_bonusData.ItemLevelBonus;
            }
            else
                itemLevel = _bonusData.ItemLevelOverride;

            ItemUpgradeRecord upgrade = CliDB.ItemUpgradeStorage.LookupByKey(GetModifier(ItemModifier.UpgradeId));
            if (upgrade != null)
                itemLevel += upgrade.ItemLevelBonus;

            for (uint i = 0; i < ItemConst.MaxGemSockets; ++i)
                itemLevel += _bonusData.GemItemLevelBonus[i];

            return Math.Min(Math.Max(itemLevel, 1), 1300);
        }

        public int GetItemStatValue(uint index, Player owner)
        {
            Contract.Assert(index < ItemConst.MaxStats);
            uint itemLevel = GetItemLevel(owner);
            uint randomPropPoints = ItemEnchantment.GetRandomPropertyPoints(itemLevel, GetQuality(), GetTemplate().GetInventoryType(), GetTemplate().GetSubClass());
            if (randomPropPoints != 0)
            {
                float statValue = (_bonusData.ItemStatAllocation[index] * randomPropPoints) * 0.0001f;
                GtItemSocketCostPerLevelRecord gtCost = CliDB.ItemSocketCostPerLevelGameTable.GetRow(itemLevel);
                if (gtCost != null)
                    statValue -= (_bonusData.ItemStatSocketCostMultiplier[index] * gtCost.SocketCost);

                return (int)(Math.Floor(statValue + 0.5f));
            }

            return _bonusData.ItemStatValue[index];
        }

        public uint GetDisplayId(Player owner)
        {
            ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs;
            if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific))
                transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()];

            ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier));
            if (transmog != null)
            {
                ItemAppearanceRecord itemAppearance = CliDB.ItemAppearanceStorage.LookupByKey(transmog.AppearanceID);
                if (itemAppearance != null)
                    return itemAppearance.DisplayID;
            }

            return Global.DB2Mgr.GetItemDisplayId(GetEntry(), GetAppearanceModId());
        }

        public ItemModifiedAppearanceRecord GetItemModifiedAppearance()
        {
            return Global.DB2Mgr.GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID);
        }

        public uint GetModifier(ItemModifier modifier)
        {
            return GetDynamicValue(ItemDynamicFields.Modifiers, (byte)modifier);
        }

        public void SetModifier(ItemModifier modifier, uint value)
        {
            ApplyModFlag(ItemFields.ModifiersMask, 1 << (int)modifier, value != 0);
            SetDynamicValue(ItemDynamicFields.Modifiers, (byte)modifier, value);
        }

        public uint GetVisibleEntry(Player owner)
        {
            ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs;
            if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific))
                transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()];

            ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier));
            if (transmog != null)
                return transmog.ItemID;

            return GetEntry();
        }

        public ushort GetVisibleAppearanceModId(Player owner)
        {
            ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs;
            if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific))
                transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()];

            ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier));
            if (transmog != null)
                return transmog.AppearanceModID;

            return (ushort)GetAppearanceModId();
        }

        public uint GetVisibleEnchantmentId(Player owner)
        {
            ItemModifier illusionModifier = ItemModifier.TransmogAppearanceAllSpecs;
            if (HasFlag(ItemFields.ModifiersMask, ItemConst.IllusionModifierMaskSpecSpecific))
                illusionModifier = ItemConst.IllusionModifierSlotBySpec[owner.GetActiveTalentGroup()];

            uint enchantIllusion = GetModifier(illusionModifier);
            if (enchantIllusion != 0)
                return enchantIllusion;

            return GetEnchantmentId(EnchantmentSlot.Perm);
        }

        public ushort GetVisibleItemVisual(Player owner)
        {
            SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetVisibleEnchantmentId(owner));
            if (enchant != null)
                return enchant.ItemVisual;

            return 0;
        }

        public void AddBonuses(uint bonusListID)
        {
            var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListID);
            if (bonuses != null)
            {
                AddDynamicValue(ItemDynamicFields.BonusListIds, bonusListID);
                foreach (ItemBonusRecord bonus in bonuses)
                    _bonusData.AddBonus((ItemBonusType)bonus.Type, bonus.Value);

                SetUInt32Value(ItemFields.AppearanceModId, _bonusData.AppearanceModID);
            }
        }

        public List<ItemDynamicFieldArtifactPowers> GetArtifactPowers()
        {
            return GetDynamicStructuredValues<ItemDynamicFieldArtifactPowers>(ItemDynamicFields.ArtifactPowers);
        }

        public ItemDynamicFieldArtifactPowers GetArtifactPower(uint artifactPowerId)
        {
            var index = m_artifactPowerIdToIndex.LookupByKey(artifactPowerId);
            if (index != 0)
                return GetDynamicStructuredValue<ItemDynamicFieldArtifactPowers>(ItemDynamicFields.ArtifactPowers, index);

            return null;
        }

        public void SetArtifactPower(ItemDynamicFieldArtifactPowers artifactPower, bool createIfMissing = false)
        {
            var foundIndex = m_artifactPowerIdToIndex.LookupByKey(artifactPower.ArtifactPowerId);
            ushort index;
            if (foundIndex != 0)
                index = foundIndex;
            else
            {
                if (!createIfMissing)
                    return;

                index = (ushort)m_artifactPowerIdToIndex.Count;
                m_artifactPowerIdToIndex[artifactPower.ArtifactPowerId] = index;
            }

            SetDynamicStructuredValue(ItemDynamicFields.ArtifactPowers, index, artifactPower);
        }

        void InitArtifactPowers(byte artifactId, byte artifactTier)
        {
            foreach (ArtifactPowerRecord artifactPower in Global.DB2Mgr.GetArtifactPowers(artifactId))
            {
                if (artifactPower.ArtifactTier != artifactTier)
                    continue;

                if (m_artifactPowerIdToIndex.ContainsKey(artifactPower.Id))
                    continue;

                ItemDynamicFieldArtifactPowers powerData = new ItemDynamicFieldArtifactPowers();
                powerData.ArtifactPowerId = artifactPower.Id;
                powerData.PurchasedRank = 0;
                powerData.CurrentRankWithBonus = (byte)(artifactPower.Flags.HasAnyFlag(ArtifactPowerFlag.First) ? 1 : 0);
                SetArtifactPower(powerData, true);
            }
        }

        public uint GetTotalPurchasedArtifactPowers()
        {
            uint purchasedRanks = 0;
            foreach (ItemDynamicFieldArtifactPowers power in GetArtifactPowers())
                purchasedRanks += power.PurchasedRank;

            return purchasedRanks;
        }

        void ApplyArtifactPowerEnchantmentBonuses(EnchantmentSlot slot, uint enchantId, bool apply, Player owner)
        {
            SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId);
            if (enchant != null)
            {
                for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i)
                {
                    switch (enchant.Effect[i])
                    {
                        case ItemEnchantmentType.ArtifactPowerBonusRankByType:
                            {
                                foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers())
                                {
                                    if (CliDB.ArtifactPowerStorage.LookupByKey(artifactPower.ArtifactPowerId).RelicType == enchant.EffectSpellID[i])
                                    {
                                        ItemDynamicFieldArtifactPowers newPower = artifactPower;
                                        if (apply)
                                            newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                        else
                                            newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i];

                                        if (IsEquipped())
                                        {
                                            ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0));
                                            if (artifactPowerRank != null)
                                                owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0);
                                        }

                                        SetArtifactPower(newPower);
                                    }
                                }
                            }
                            break;
                        case ItemEnchantmentType.ArtifactPowerBonusRankByID:
                            {
                                ItemDynamicFieldArtifactPowers artifactPower = GetArtifactPower(enchant.EffectSpellID[i]);
                                if (artifactPower != null)
                                {
                                    ItemDynamicFieldArtifactPowers newPower = artifactPower;
                                    if (apply)
                                        newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                    else
                                        newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i];

                                    if (IsEquipped())
                                    {
                                        ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0));
                                        if (artifactPowerRank != null)
                                            owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0);
                                    }

                                    SetArtifactPower(newPower);
                                }
                            }
                            break;
                        case ItemEnchantmentType.ArtifactPowerBonusRankPicker:
                            if (slot >= EnchantmentSlot.Sock1 && slot <= EnchantmentSlot.Sock3 && _bonusData.GemRelicType[slot - EnchantmentSlot.Sock1] != -1)
                            {
                                ArtifactPowerPickerRecord artifactPowerPicker = CliDB.ArtifactPowerPickerStorage.LookupByKey(enchant.EffectSpellID[i]);
                                if (artifactPowerPicker != null)
                                {
                                    PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactPowerPicker.PlayerConditionID);
                                    if (playerCondition == null || ConditionManager.IsPlayerMeetingCondition(owner, playerCondition))
                                    {
                                        foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers())
                                        {
                                            if (CliDB.ArtifactPowerStorage.LookupByKey(artifactPower.ArtifactPowerId).RelicType == _bonusData.GemRelicType[slot - EnchantmentSlot.Sock1])
                                            {
                                                ItemDynamicFieldArtifactPowers newPower = artifactPower;
                                                if (apply)
                                                    newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i];
                                                else
                                                    newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i];

                                                if (IsEquipped())
                                                {
                                                    ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0));
                                                    if (artifactPowerRank != null)
                                                        owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0);
                                                }

                                                SetArtifactPower(newPower);
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        public void CopyArtifactDataFromParent(Item parent)
        {
            Array.Copy(parent.GetBonus().GemItemLevelBonus, _bonusData.GemItemLevelBonus, _bonusData.GemItemLevelBonus.Length);
            SetModifier(ItemModifier.ArtifactAppearanceId, parent.GetModifier(ItemModifier.ArtifactAppearanceId));
            SetAppearanceModId(parent.GetAppearanceModId());
        }

        public void GiveArtifactXp(ulong amount, Item sourceItem, uint artifactCategoryId)
        {
            Player owner = GetOwner();
            if (!owner)
                return;

            if (artifactCategoryId != 0)
            {
                ArtifactCategoryRecord artifactCategory = CliDB.ArtifactCategoryStorage.LookupByKey(artifactCategoryId);
                if (artifactCategory != null)
                {
                    uint artifactKnowledgeLevel = 1;
                    if (sourceItem && sourceItem.GetModifier(ItemModifier.ArtifactKnowledgeLevel) != 0)
                        artifactKnowledgeLevel = sourceItem.GetModifier(ItemModifier.ArtifactKnowledgeLevel);
                    else
                        artifactKnowledgeLevel = owner.GetCurrency(artifactCategory.ArtifactKnowledgeCurrencyID) + 1;

                    GtArtifactKnowledgeMultiplierRecord artifactKnowledge = CliDB.ArtifactKnowledgeMultiplierGameTable.GetRow(artifactKnowledgeLevel);
                    if (artifactKnowledge != null)
                        amount = (ulong)(amount * artifactKnowledge.Multiplier);

                    if (amount >= 5000)
                        amount = 50 * (amount / 50);
                    else if (amount >= 1000)
                        amount = 25 * (amount / 25);
                    else if (amount >= 50)
                        amount = 5 * (amount / 5);
                }
            }

            SetUInt64Value(ItemFields.ArtifactXp, GetUInt64Value(ItemFields.ArtifactXp) + amount);

            ArtifactXpGain artifactXpGain = new ArtifactXpGain();
            artifactXpGain.ArtifactGUID = GetGUID();
            artifactXpGain.Amount = amount;
            owner.SendPacket(artifactXpGain);

            SetState(ItemUpdateState.Changed, owner);
        }

        public static void AddItemsSetItem(Player player, Item item)
        {
            ItemTemplate proto = item.GetTemplate();
            uint setid = proto.GetItemSet();

            ItemSetRecord set = CliDB.ItemSetStorage.LookupByKey(setid);
            if (set == null)
            {
                Log.outError(LogFilter.Sql, "Item set {0} for item (id {1}) not found, mods not applied.", setid, proto.GetId());
                return;
            }

            if (set.RequiredSkill != 0 && player.GetSkillValue((SkillType)set.RequiredSkill) < set.RequiredSkillRank)
                return;

            if (set.Flags.HasAnyFlag(ItemSetFlags.LegacyInactive))
                return;

            ItemSetEffect eff = null;
            for (int x = 0; x < player.ItemSetEff.Count; ++x)
            {
                if (player.ItemSetEff[x]?.ItemSetID == setid)
                {
                    eff = player.ItemSetEff[x];
                    break;
                }
            }

            if (eff == null)
            {
                eff = new ItemSetEffect();
                eff.ItemSetID = setid;

                int x = 0;
                for (; x < player.ItemSetEff.Count; ++x)
                    if (player.ItemSetEff[x] == null)
                        break;

                if (x < player.ItemSetEff.Count)
                    player.ItemSetEff[x] = eff;
                else
                    player.ItemSetEff.Add(eff);
            }

            ++eff.EquippedItemCount;

            List<ItemSetSpellRecord> itemSetSpells = Global.DB2Mgr.GetItemSetSpells(setid);
            foreach (var itemSetSpell in itemSetSpells)
            {
                //not enough for  spell
                if (itemSetSpell.Threshold > eff.EquippedItemCount)
                    continue;

                if (eff.SetBonuses.Contains(itemSetSpell))
                    continue;

                SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(itemSetSpell.SpellID);
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Player, "WORLD: unknown spell id {0} in items set {1} effects", itemSetSpell.SpellID, setid);
                    continue;
                }

                eff.SetBonuses.Add(itemSetSpell);
                // spell cast only if fit form requirement, in other case will cast at form change
                if (itemSetSpell.ChrSpecID == 0 || itemSetSpell.ChrSpecID == player.GetUInt32Value(PlayerFields.CurrentSpecId))
                    player.ApplyEquipSpell(spellInfo, null, true);
            }
        }

        public static void RemoveItemsSetItem(Player player, ItemTemplate proto)
        {
            uint setid = proto.GetItemSet();

            ItemSetRecord set = CliDB.ItemSetStorage.LookupByKey(setid);
            if (set == null)
            {
                Log.outError(LogFilter.Sql, "Item set {0} for item {1} not found, mods not removed.", setid, proto.GetId());
                return;
            }

            ItemSetEffect eff = null;
            int setindex = 0;
            for (; setindex < player.ItemSetEff.Count; setindex++)
            {
                if (player.ItemSetEff[setindex] != null && player.ItemSetEff[setindex].ItemSetID == setid)
                {
                    eff = player.ItemSetEff[setindex];
                    break;
                }
            }

            // can be in case now enough skill requirement for set appling but set has been appliend when skill requirement not enough
            if (eff == null)
                return;

            --eff.EquippedItemCount;

            List<ItemSetSpellRecord> itemSetSpells = Global.DB2Mgr.GetItemSetSpells(setid);
            foreach (ItemSetSpellRecord itemSetSpell in itemSetSpells)
            {
                // enough for spell
                if (itemSetSpell.Threshold <= eff.EquippedItemCount)
                    continue;

                if (!eff.SetBonuses.Contains(itemSetSpell))
                    continue;

                player.ApplyEquipSpell(Global.SpellMgr.GetSpellInfo(itemSetSpell.SpellID), null, false);
                eff.SetBonuses.Remove(itemSetSpell);
            }

            if (eff.EquippedItemCount == 0)                                    //all items of a set were removed
            {
                Contract.Assert(eff == player.ItemSetEff[setindex]);
                player.ItemSetEff[setindex] = null;
            }
        }

        public BonusData GetBonus() { return _bonusData; }

        public ObjectGuid GetOwnerGUID() { return GetGuidValue(ItemFields.Owner); }
        public void SetOwnerGUID(ObjectGuid guid) { SetGuidValue(ItemFields.Owner, guid); }

        public ItemBondingType GetBonding() { return _bonusData.Bonding; }
        public void SetBinding(bool val) { ApplyModFlag(ItemFields.Flags, (uint)ItemFieldFlags.Soulbound, val); }
        public bool IsSoulBound() { return HasFlag(ItemFields.Flags, ItemFieldFlags.Soulbound); }
        public bool IsBoundAccountWide() { return GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsBoundToAccount); }
        bool IsBattlenetAccountBound() { return GetTemplate().GetFlags2().HasAnyFlag(ItemFlags2.BnetAccountTradeOk); }

        public Bag ToBag()
        {
            if (IsBag())
                return (this as Bag);
            else
                return null;
        }

        public bool IsLocked() { return !HasFlag(ItemFields.Flags, ItemFieldFlags.Unlocked); }
        public bool IsBag() { return GetTemplate().GetInventoryType() == InventoryType.Bag; }
        public bool IsCurrencyToken() { return GetTemplate().IsCurrencyToken(); }
        public bool IsBroken() { return GetUInt32Value(ItemFields.MaxDurability) > 0 && GetUInt32Value(ItemFields.Durability) == 0; }
        public void SetInTrade(bool b = true) { mb_in_trade = b; }
        public bool IsInTrade() { return mb_in_trade; }

        public uint GetCount() { return GetUInt32Value(ItemFields.StackCount); }
        public uint GetMaxStackCount() { return GetTemplate().GetMaxStackSize(); }

        public byte GetSlot() { return m_slot; }
        public Bag GetContainer() { return m_container; }
        public void SetSlot(byte slot) { m_slot = slot; }
        public ushort GetPos() { return (ushort)(GetBagSlot() << 8 | GetSlot()); }
        public void SetContainer(Bag container) { m_container = container; }

        bool IsInBag() { return m_container != null; }

        public int GetItemRandomPropertyId() { return GetInt32Value(ItemFields.RandomPropertiesId); }
        public uint GetItemSuffixFactor() { return GetUInt32Value(ItemFields.PropertySeed); }
        public ItemRandomEnchantmentId GetItemRandomEnchantmentId() { return m_randomEnchantment; }
        public uint GetEnchantmentId(EnchantmentSlot slot)
        {
            return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Id);
        }
        public uint GetEnchantmentDuration(EnchantmentSlot slot)
        {
            return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration);
        }
        public uint GetEnchantmentCharges(EnchantmentSlot slot)
        {
            return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges);
        }

        public string GetText() { return m_text; }
        public void SetText(string text) { m_text = text; }

        public int GetSpellCharges(int index = 0) { return GetInt32Value(ItemFields.SpellCharges + index); }
        public void SetSpellCharges(int index, int value) { SetInt32Value(ItemFields.SpellCharges + index, value); }

        public ItemUpdateState GetState() { return uState; }

        public bool IsInUpdateQueue() { return uQueuePos != -1; }
        public int GetQueuePos() { return uQueuePos; }
        public void FSetState(ItemUpdateState state)// forced
        {
            uState = state;
        }

        public override bool hasQuest(uint quest_id) { return GetTemplate().GetStartQuest() == quest_id; }
        public override bool hasInvolvedQuest(uint quest_id) { return false; }
        public bool IsPotion() { return GetTemplate().IsPotion(); }
        public bool IsVellum() { return GetTemplate().IsVellum(); }
        public bool IsConjuredConsumable() { return GetTemplate().IsConjuredConsumable(); }
        public bool IsRangedWeapon() { return GetTemplate().IsRangedWeapon(); }
        public ItemQuality GetQuality() { return _bonusData.Quality; }
        public int GetRequiredLevel() { return _bonusData.RequiredLevel; }
        public int GetItemStatType(uint index)
        {
            Contract.Assert(index < ItemConst.MaxStats);
            return _bonusData.ItemStatType[index];
        }
        public SocketColor GetSocketColor(uint index)
        {
            Contract.Assert(index < ItemConst.MaxGemSockets);
            return _bonusData.socketColor[index];
        }
        public uint GetAppearanceModId() { return GetUInt32Value(ItemFields.AppearanceModId); }
        public void SetAppearanceModId(uint appearanceModId) { SetUInt32Value(ItemFields.AppearanceModId, appearanceModId); }
        public uint GetArmor(Player owner) { return GetTemplate().GetArmor(GetItemLevel(owner)); }
        public void GetDamage(Player owner, out float minDamage, out float maxDamage) { GetTemplate().GetDamage(GetItemLevel(owner), out minDamage, out maxDamage); }
        public float GetRepairCostMultiplier() { return _bonusData.RepairCostMultiplier; }
        public uint GetScalingStatDistribution() { return _bonusData.ScalingStatDistribution; }

        public void SetRefundRecipient(ObjectGuid guid) { m_refundRecipient = guid; }
        public void SetPaidMoney(uint money) { m_paidMoney = money; }
        public void SetPaidExtendedCost(uint iece) { m_paidExtendedCost = iece; }

        public ObjectGuid GetRefundRecipient() { return m_refundRecipient; }
        public uint GetPaidMoney() { return m_paidMoney; }
        public uint GetPaidExtendedCost() { return m_paidExtendedCost; }

        public uint GetScriptId() { return GetTemplate().ScriptId; }

        public uint GetSpecialPrice(uint minimumPrice = 10000) { return GetSpecialPrice(GetTemplate(), minimumPrice); }

        public ObjectGuid GetChildItem() { return m_childItem; }
        public void SetChildItem(ObjectGuid childItem) { m_childItem = childItem; }

        //Static
        public static bool ItemCanGoIntoBag(ItemTemplate pProto, ItemTemplate pBagProto)
        {
            if (pProto == null || pBagProto == null)
                return false;

            switch (pBagProto.GetClass())
            {
                case ItemClass.Container:
                    switch ((ItemSubClassContainer)pBagProto.GetSubClass())
                    {
                        case ItemSubClassContainer.Container:
                            return true;
                        case ItemSubClassContainer.SoulContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.SoulShards))
                                return false;
                            return true;
                        case ItemSubClassContainer.HerbContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Herbs))
                                return false;
                            return true;
                        case ItemSubClassContainer.EnchantingContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.EnchantingSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.MiningContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.MiningSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.EngineeringContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.EngineeringSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.GemContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Gems))
                                return false;
                            return true;
                        case ItemSubClassContainer.LeatherworkingContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.LeatherworkingSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.InscriptionContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.InscriptionSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.TackleContainer:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.FishingSupp))
                                return false;
                            return true;
                        case ItemSubClassContainer.CookingContainer:
                            if (!pProto.GetBagFamily().HasAnyFlag(BagFamilyMask.CookingSupp))
                                return false;
                            return true;
                        default:
                            return false;
                    }
                //can remove?
                case ItemClass.Quiver:
                    switch ((ItemSubClassQuiver)pBagProto.GetSubClass())
                    {
                        case ItemSubClassQuiver.Quiver:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Arrows))
                                return false;
                            return true;
                        case ItemSubClassQuiver.AmmoPouch:
                            if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Bullets))
                                return false;
                            return true;
                        default:
                            return false;
                    }
            }
            return false;
        }

        public static uint ItemSubClassToDurabilityMultiplierId(ItemClass ItemClass, uint ItemSubClass)
        {
            switch (ItemClass)
            {
                case ItemClass.Weapon: return ItemSubClass;
                case ItemClass.Armor: return ItemSubClass + 21;
            }
            return 0;
        }

        #region Fields
        public bool m_lootGenerated;
        public Loot loot;
        internal BonusData _bonusData;

        ItemUpdateState uState;
        uint m_paidExtendedCost;
        uint m_paidMoney;
        ObjectGuid m_refundRecipient;
        byte m_slot;
        Bag m_container;
        int uQueuePos;
        string m_text;
        bool mb_in_trade;
        long m_lastPlayedTimeUpdate;
        List<ObjectGuid> allowedGUIDs = new List<ObjectGuid>();
        ItemRandomEnchantmentId m_randomEnchantment;        // store separately to easily find which bonus list is the one randomly given for stat rerolling
        ObjectGuid m_childItem;
        Dictionary<uint, ushort> m_artifactPowerIdToIndex = new Dictionary<uint, ushort>();
        Array<uint> m_gemScalingLevels = new Array<uint>(ItemConst.MaxGemSockets);
        #endregion
    }

    public class ItemPosCount
    {
        public ItemPosCount(ushort _pos, uint _count)
        {
            pos = _pos;
            count = _count;
        }

        public bool isContainedIn(List<ItemPosCount> vec)
        {
            foreach (var posCount in vec)
                if (posCount.pos == pos)
                    return true;
            return false;
        }

        public ushort pos;
        public uint count;
    }

    public enum EnchantmentOffset
    {
        Id = 0,
        Duration = 1,
        Charges = 2,                         // now here not only charges, but something new in wotlk
        Max = 3
    }

    public class ItemSetEffect
    {
        public uint ItemSetID;
        public uint EquippedItemCount;
        public List<ItemSetSpellRecord> SetBonuses = new List<ItemSetSpellRecord>();
    }

    public class BonusData
    {
        public BonusData(ItemTemplate proto)
        {
            if (proto == null)
                return;

            Quality = proto.GetQuality();
            ItemLevelBonus = 0;
            RequiredLevel = proto.GetBaseRequiredLevel();
            for (uint i = 0; i < ItemConst.MaxStats; ++i)
                ItemStatType[i] = proto.GetItemStatType(i);

            for (uint i = 0; i < ItemConst.MaxStats; ++i)
                ItemStatValue[i] = proto.GetItemStatValue(i);

            for (uint i = 0; i < ItemConst.MaxStats; ++i)
                ItemStatAllocation[i] = proto.GetItemStatAllocation(i);

            for (uint i = 0; i < ItemConst.MaxStats; ++i)
                ItemStatSocketCostMultiplier[i] = proto.GetItemStatSocketCostMultiplier(i);

            for (uint i = 0; i < ItemConst.MaxGemSockets; ++i)
            {
                socketColor[i] = proto.GetSocketColor(i);
                GemItemLevelBonus[i] = 0;
                GemRelicType[i] = -1;
                GemRelicRankBonus[i] = 0;
            }

            Bonding = proto.GetBonding();

            AppearanceModID = 0;
            RepairCostMultiplier = 1.0f;
            ScalingStatDistribution = proto.GetScalingStatDistribution();
            ItemLevelOverride = 0;
            RelicType = -1;
            HasItemLevelBonus = false;

            _state.AppearanceModPriority = int.MaxValue;
            _state.ScalingStatDistributionPriority = int.MaxValue;
            _state.ItemLevelOverridePriority = int.MaxValue;
            _state.HasQualityBonus = false;
        }

        public BonusData(ItemInstance itemInstance) : this(Global.ObjectMgr.GetItemTemplate(itemInstance.ItemID))
        {
            if (itemInstance.ItemBonus.HasValue)
            {
                foreach (uint bonusListID in itemInstance.ItemBonus.Value.BonusListIDs)
                {
                    var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListID);
                    if (bonuses != null)
                    {
                        foreach (ItemBonusRecord bonus in bonuses)
                            AddBonus(bonus.Type, bonus.Value);
                    }
                }
            }
        }

        public void AddBonus(ItemBonusType type, int[] values)
        {
            switch (type)
            {
                case ItemBonusType.ItemLevel:
                    ItemLevelBonus += values[0];
                    HasItemLevelBonus = true;
                    break;
                case ItemBonusType.Stat:
                    {
                        uint statIndex = 0;
                        for (statIndex = 0; statIndex < ItemConst.MaxStats; ++statIndex)
                            if (ItemStatType[statIndex] == values[0] || ItemStatType[statIndex] == -1)
                                break;

                        if (statIndex < ItemConst.MaxStats)
                        {
                            ItemStatType[statIndex] = values[0];
                            ItemStatAllocation[statIndex] += values[1];
                        }
                        break;
                    }
                case ItemBonusType.Quality:
                    if (!_state.HasQualityBonus)
                    {
                        Quality = (ItemQuality)values[0];
                        _state.HasQualityBonus = true;
                    }
                    else if ((uint)Quality < values[0])
                        Quality = (ItemQuality)values[0];
                    break;
                case ItemBonusType.Socket:
                    {
                        uint socketCount = (uint)values[0];
                        for (uint i = 0; i < ItemConst.MaxGemSockets && socketCount != 0; ++i)
                        {
                            if (socketColor[i] == 0)
                            {
                                socketColor[i] = (SocketColor)values[1];
                                --socketCount;
                            }
                        }
                        break;
                    }
                case ItemBonusType.Appearance:
                    if (values[1] < _state.AppearanceModPriority)
                    {
                        AppearanceModID = Convert.ToUInt32(values[0]);
                        _state.AppearanceModPriority = values[1];
                    }
                    break;
                case ItemBonusType.RequiredLevel:
                    RequiredLevel += values[0];
                    break;
                case ItemBonusType.RepairCostMuliplier:
                    RepairCostMultiplier *= Convert.ToSingle(values[0]) * 0.01f;
                    break;
                case ItemBonusType.ScalingStatDistribution:
                case ItemBonusType.ScalingStatDistribution2:
                    if (values[1] < _state.ScalingStatDistributionPriority)
                    {
                        ScalingStatDistribution = (uint)values[0];
                        _state.ScalingStatDistributionPriority = values[1];
                    }
                    break;
                case ItemBonusType.ItemLevelOverride:
                    if (values[1] < _state.ItemLevelOverridePriority)
                    {
                        ItemLevelOverride = (uint)values[0];
                        _state.ItemLevelOverridePriority = values[1];
                    }
                    break;
                case ItemBonusType.Bounding:
                    Bonding = (ItemBondingType)values[0];
                    break;
                case ItemBonusType.RelicType:
                    RelicType = values[0];
                    break;
            }
        }

        public ItemQuality Quality;
        public int ItemLevelBonus;
        public int RequiredLevel;
        public int[] ItemStatType = new int[ItemConst.MaxStats];
        public int[] ItemStatValue = new int[ItemConst.MaxStats];
        public int[] ItemStatAllocation = new int[ItemConst.MaxStats];
        public float[] ItemStatSocketCostMultiplier = new float[ItemConst.MaxStats];
        public SocketColor[] socketColor = new SocketColor[ItemConst.MaxGemSockets];
        public ItemBondingType Bonding;
        public uint AppearanceModID;
        public float RepairCostMultiplier;
        public uint ScalingStatDistribution;
        public uint ItemLevelOverride;
        public uint[] GemItemLevelBonus = new uint[ItemConst.MaxGemSockets];
        public int[] GemRelicType = new int[ItemConst.MaxGemSockets];
        public ushort[] GemRelicRankBonus = new ushort[ItemConst.MaxGemSockets];
        public int RelicType;
        public bool HasItemLevelBonus;
        State _state;

        struct State
        {
            public int AppearanceModPriority;
            public int ScalingStatDistributionPriority;
            public int ItemLevelOverridePriority;
            public bool HasQualityBonus;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public class ItemDynamicFieldArtifactPowers
    {
        public uint ArtifactPowerId;
        public byte PurchasedRank;
        public byte CurrentRankWithBonus;
        public ushort Padding;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class ItemDynamicFieldGems
    {
        public uint ItemId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public ushort[] BonusListIDs = new ushort[16];
        public byte Context;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] Padding = new byte[3];
    }
}
