/*
 * 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.Constants;
using Framework.Database;
using Game.BattlePets;
using Game.DataStorage;
using Game.Entities;
using Game.Network;
using Game.Network.Packets;
using System;
using System.Collections.Generic;

namespace Game
{
    public partial class WorldSession
    {
        [WorldPacketHandler(ClientOpcodes.SplitItem)]
        void HandleSplitItem(SplitItem splitItem)
        {
            if (splitItem.Inv.Items.Count != 0)
            {
                Log.outError(LogFilter.Network, "WORLD: HandleSplitItemOpcode - Invalid itemCount ({0})", splitItem.Inv.Items.Count);
                return;
            }

            ushort src = (ushort)((splitItem.FromPackSlot << 8) | splitItem.FromSlot);
            ushort dst = (ushort)((splitItem.ToPackSlot << 8) | splitItem.ToSlot);

            if (src == dst)
                return;

            if (splitItem.Quantity == 0)
                return;                                             //check count - if zero it's fake packet

            if (!_player.IsValidPos(splitItem.FromPackSlot, splitItem.FromSlot, true))
            {
                _player.SendEquipError(InventoryResult.ItemNotFound);
                return;
            }

            if (!_player.IsValidPos(splitItem.ToPackSlot, splitItem.ToSlot, false))       // can be autostore pos
            {
                _player.SendEquipError(InventoryResult.WrongSlot);
                return;
            }

            _player.SplitItem(src, dst, (uint)splitItem.Quantity);
        }

        [WorldPacketHandler(ClientOpcodes.SwapInvItem)]
        void HandleSwapInvenotryItem(SwapInvItem swapInvItem)
        {
            if (swapInvItem.Inv.Items.Count != 2)
            {
                Log.outError(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - Invalid itemCount ({0})", swapInvItem.Inv.Items.Count);
                return;
            }

            // prevent attempt swap same item to current position generated by client at special checting sequence
            if (swapInvItem.Slot1 == swapInvItem.Slot2)
                return;

            if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot1, true))
            {
                GetPlayer().SendEquipError(InventoryResult.ItemNotFound);
                return;
            }

            if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot2, true))
            {
                GetPlayer().SendEquipError(InventoryResult.WrongSlot);
                return;
            }

            if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot1) && !CanUseBank())
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
                return;
            }

            if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot2) && !CanUseBank())
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
                return;
            }

            ushort src = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot1);
            ushort dst = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot2);

            GetPlayer().SwapItem(src, dst);
        }

        [WorldPacketHandler(ClientOpcodes.AutoEquipItemSlot)]
        void HandleAutoEquipItemSlot(AutoEquipItemSlot packet)
        {
            // cheating attempt, client should never send opcode in that case
            if (packet.Inv.Items.Count != 1 || !Player.IsEquipmentPos(InventorySlots.Bag0, packet.ItemDstSlot))
                return;

            Item item = GetPlayer().GetItemByGuid(packet.Item);
            ushort dstPos = (ushort)(packet.ItemDstSlot | (InventorySlots.Bag0 << 8));
            ushort srcPos = (ushort)(packet.Inv.Items[0].Slot | (packet.Inv.Items[0].ContainerSlot << 8));

            if (item == null || item.GetPos() != srcPos || srcPos == dstPos)
                return;

            GetPlayer().SwapItem(srcPos, dstPos);
        }

        [WorldPacketHandler(ClientOpcodes.SwapItem)]
        void HandleSwapItem(SwapItem swapItem)
        {
            if (swapItem.Inv.Items.Count != 2)
            {
                Log.outError(LogFilter.Network, "WORLD: HandleSwapItem - Invalid itemCount ({0})", swapItem.Inv.Items.Count);
                return;
            }

            ushort src = (ushort)((swapItem.ContainerSlotA << 8) | swapItem.SlotA);
            ushort dst = (ushort)((swapItem.ContainerSlotB << 8) | swapItem.SlotB);

            var pl = GetPlayer();

            // prevent attempt swap same item to current position generated by client at special checting sequence
            if (src == dst)
                return;

            if (!pl.IsValidPos(swapItem.ContainerSlotA, swapItem.SlotA, true))
            {
                pl.SendEquipError(InventoryResult.ItemNotFound);
                return;
            }

            if (!pl.IsValidPos(swapItem.ContainerSlotB, swapItem.SlotB, true))
            {
                pl.SendEquipError(InventoryResult.WrongSlot);
                return;
            }


            if (Player.IsBankPos(swapItem.ContainerSlotA, swapItem.SlotA) && !CanUseBank())
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
                return;
            }

            if (Player.IsBankPos(swapItem.ContainerSlotB, swapItem.SlotB) && !CanUseBank())
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
                return;
            }

            pl.SwapItem(src, dst);
        }

        [WorldPacketHandler(ClientOpcodes.AutoEquipItem)]
        void HandleAutoEquipItem(AutoEquipItem autoEquipItem)
        {
            if (autoEquipItem.Inv.Items.Count != 1)
            {
                Log.outError(LogFilter.Network, "WORLD: HandleAutoEquipItem - Invalid itemCount ({0})", autoEquipItem.Inv.Items.Count);
                return;
            }

            var pl = GetPlayer();
            Item srcItem = pl.GetItemByPos(autoEquipItem.PackSlot, autoEquipItem.Slot);
            if (srcItem == null)
                return;                                             // only at cheat

            ushort dest;
            InventoryResult msg = pl.CanEquipItem(ItemConst.NullSlot, out dest, srcItem, !srcItem.IsBag());
            if (msg != InventoryResult.Ok)
            {
                pl.SendEquipError(msg, srcItem);
                return;
            }

            ushort src = srcItem.GetPos();
            if (dest == src)                                           // prevent equip in same slot, only at cheat
                return;

            Item dstItem = pl.GetItemByPos(dest);
            if (dstItem == null)                                         // empty slot, simple case
            {
                if (!srcItem.GetChildItem().IsEmpty())
                {
                    InventoryResult childEquipResult = _player.CanEquipChildItem(srcItem);
                    if (childEquipResult != InventoryResult.Ok)
                    {
                        _player.SendEquipError(msg, srcItem);
                        return;
                    }
                }

                pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, true);
                pl.EquipItem(dest, srcItem, true);
                if (!srcItem.GetChildItem().IsEmpty())
                    _player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem);

                pl.AutoUnequipOffhandIfNeed();
            }
            else                                                    // have currently equipped item, not simple case
            {
                byte dstbag = dstItem.GetBagSlot();
                byte dstslot = dstItem.GetSlot();

                msg = pl.CanUnequipItem(dest, !srcItem.IsBag());
                if (msg != InventoryResult.Ok)
                {
                    pl.SendEquipError(msg, dstItem);
                    return;
                }

                if (!dstItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child))
                {
                    // check dest.src move possibility
                    List<ItemPosCount> sSrc = new List<ItemPosCount>();
                    ushort eSrc = 0;
                    if (pl.IsInventoryPos(src))
                    {
                        msg = pl.CanStoreItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true);
                        if (msg != InventoryResult.Ok)
                            msg = pl.CanStoreItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true);
                        if (msg != InventoryResult.Ok)
                            msg = pl.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true);
                    }
                    else if (Player.IsBankPos(src))
                    {
                        msg = pl.CanBankItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true);
                        if (msg != InventoryResult.Ok)
                            msg = pl.CanBankItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true);
                        if (msg != InventoryResult.Ok)
                            msg = pl.CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true);
                    }
                    else if (Player.IsEquipmentPos(src))
                    {
                        msg = pl.CanEquipItem(autoEquipItem.Slot, out eSrc, dstItem, true);
                        if (msg == InventoryResult.Ok)
                            msg = pl.CanUnequipItem(eSrc, true);
                    }

                    if (msg == InventoryResult.Ok && Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty())
                        msg = _player.CanEquipChildItem(srcItem);

                    if (msg != InventoryResult.Ok)
                    {
                        pl.SendEquipError(msg, dstItem, srcItem);
                        return;
                    }

                    // now do moves, remove...
                    pl.RemoveItem(dstbag, dstslot, false);
                    pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, false);

                    // add to dest
                    pl.EquipItem(dest, srcItem, true);

                    // add to src
                    if (pl.IsInventoryPos(src))
                        pl.StoreItem(sSrc, dstItem, true);
                    else if (Player.IsBankPos(src))
                        pl.BankItem(sSrc, dstItem, true);
                    else if (Player.IsEquipmentPos(src))
                        pl.EquipItem(eSrc, dstItem, true);

                    if (Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty())
                        _player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem);
                }
                else
                {
                    Item parentItem = _player.GetItemByGuid(dstItem.GetGuidValue(ItemFields.Creator));
                    if (parentItem)
                    {
                        if (Player.IsEquipmentPos(dest))
                        {
                            _player.AutoUnequipChildItem(parentItem);
                            // dest is now empty
                            _player.SwapItem(src, dest);
                            // src is now empty
                            _player.SwapItem(parentItem.GetPos(), src);
                        }
                    }
                }

                pl.AutoUnequipOffhandIfNeed();
            }
        }

        [WorldPacketHandler(ClientOpcodes.DestroyItem)]
        void HandleDestroyItem(DestroyItem destroyItem)
        {
            ushort pos = (ushort)((destroyItem.ContainerId << 8) | destroyItem.SlotNum);

            // prevent drop unequipable items (in combat, for example) and non-empty bags
            if (Player.IsEquipmentPos(pos) || Player.IsBagPos(pos))
            {
                InventoryResult msg = _player.CanUnequipItem(pos, false);
                if (msg != InventoryResult.Ok)
                {
                    _player.SendEquipError(msg, _player.GetItemByPos(pos));
                    return;
                }
            }

            Item pItem = _player.GetItemByPos(destroyItem.ContainerId, destroyItem.SlotNum);
            if (pItem == null)
            {
                _player.SendEquipError(InventoryResult.ItemNotFound);
                return;
            }

            if (pItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoUserDestroy))
            {
                _player.SendEquipError(InventoryResult.DropBoundItem);
                return;
            }

            if (destroyItem.Count != 0)
            {
                uint i_count = destroyItem.Count;
                _player.DestroyItemCount(pItem, ref i_count, true);
            }
            else
                _player.DestroyItem(destroyItem.ContainerId, destroyItem.SlotNum, true);
        }

        [WorldPacketHandler(ClientOpcodes.ReadItem)]
        void HandleReadItem(ReadItem readItem)
        {
            Item item = _player.GetItemByPos(readItem.PackSlot, readItem.Slot);
            if (item != null && item.GetTemplate().GetPageText() != 0)
            {
                InventoryResult msg = _player.CanUseItem(item);
                if (msg == InventoryResult.Ok)
                {
                    ReadItemResultOK packet = new ReadItemResultOK();
                    packet.Item = item.GetGUID();
                    SendPacket(packet);
                }
                else
                {
                    /// @todo: 6.x research new values
                    /*WorldPackets.Item.ReadItemResultFailed packet;
                    packet.Item = item->GetGUID();
                    packet.Subcode = ??;
                    packet.Delay = ??;
                    SendPacket(packet.Write());*/

                    Log.outInfo(LogFilter.Network, "STORAGE: Unable to read item");
                    _player.SendEquipError(msg, item);
                }
            }
            else
                _player.SendEquipError(InventoryResult.ItemNotFound);
        }

        [WorldPacketHandler(ClientOpcodes.SellItem)]
        void HandleSellItem(SellItem packet)
        {
            if (packet.ItemGUID.IsEmpty())
                return;

            var pl = GetPlayer();

            Creature creature = pl.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor);
            if (creature == null)
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleSellItemOpcode - {0} not found or you can not interact with him.", packet.VendorGUID.ToString());
                pl.SendSellError(SellResult.CantFindVendor, null, packet.ItemGUID);
                return;
            }

            // remove fake death
            if (pl.HasUnitState(UnitState.Died))
                pl.RemoveAurasByType(AuraType.FeignDeath);

            Item pItem = pl.GetItemByGuid(packet.ItemGUID);
            if (pItem != null)
            {
                // prevent sell not owner item
                if (pl.GetGUID() != pItem.GetOwnerGUID())
                {
                    pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                    return;
                }

                // prevent sell non empty bag by drag-and-drop at vendor's item list
                if (pItem.IsNotEmptyBag())
                {
                    pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                    return;
                }

                // prevent sell currently looted item
                if (pl.GetLootGUID() == pItem.GetGUID())
                {
                    pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                    return;
                }

                // prevent selling item for sellprice when the item is still refundable
                // this probably happens when right clicking a refundable item, the client sends both
                // CMSG_SELL_ITEM and CMSG_REFUND_ITEM (unverified)
                if (pItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable))
                    return; // Therefore, no feedback to client

                // special case at auto sell (sell all)
                if (packet.Amount == 0)
                    packet.Amount = pItem.GetCount();
                else
                {
                    // prevent sell more items that exist in stack (possible only not from client)
                    if (packet.Amount > pItem.GetCount())
                    {
                        pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                        return;
                    }
                }

                ItemTemplate pProto = pItem.GetTemplate();
                if (pProto != null)
                {
                    if (pProto.GetSellPrice() > 0)
                    {
                        if (packet.Amount < pItem.GetCount())               // need split items
                        {
                            Item pNewItem = pItem.CloneItem(packet.Amount, pl);
                            if (pNewItem == null)
                            {
                                Log.outError(LogFilter.Network, "WORLD: HandleSellItemOpcode - could not create clone of item {0}; count = {1}", pItem.GetEntry(), packet.Amount);
                                pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                                return;
                            }

                            pItem.SetCount(pItem.GetCount() - packet.Amount);
                            pl.ItemRemovedQuestCheck(pItem.GetEntry(), packet.Amount);
                            if (pl.IsInWorld)
                                pItem.SendUpdateToPlayer(pl);
                            pItem.SetState(ItemUpdateState.Changed, pl);

                            pl.AddItemToBuyBackSlot(pNewItem);
                            if (pl.IsInWorld)
                                pNewItem.SendUpdateToPlayer(pl);
                        }
                        else
                        {
                            pl.ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount());
                            pl.RemoveItem(pItem.GetBagSlot(), pItem.GetSlot(), true);
                            Item.RemoveItemFromUpdateQueueOf(pItem, pl);
                            pl.AddItemToBuyBackSlot(pItem);
                        }

                        uint money = pProto.GetSellPrice() * packet.Amount;
                        pl.ModifyMoney(money);
                        pl.UpdateCriteria(CriteriaTypes.MoneyFromVendors, money);
                    }
                    else
                        pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
                    return;
                }
            }
            pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
            return;
        }

        [WorldPacketHandler(ClientOpcodes.BuyBackItem)]
        void HandleBuybackItem(BuyBackItem packet)
        {
            Creature creature = _player.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor);
            if (creature == null)
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleBuybackItem - {0} not found or you can not interact with him.", packet.VendorGUID.ToString());
                _player.SendSellError(SellResult.CantFindVendor, null, ObjectGuid.Empty);
                return;
            }

            // remove fake death
            if (_player.HasUnitState(UnitState.Died))
                _player.RemoveAurasByType(AuraType.FeignDeath);

            Item pItem = _player.GetItemFromBuyBackSlot(packet.Slot);
            if (pItem != null)
            {
                uint price = _player.GetUInt32Value(PlayerFields.BuyBackPrice1 + (int)(packet.Slot - InventorySlots.BuyBackStart));
                if (!_player.HasEnoughMoney(price))
                {
                    _player.SendBuyError(BuyResult.NotEnoughtMoney, creature, pItem.GetEntry());
                    return;
                }

                List<ItemPosCount> dest = new List<ItemPosCount>();
                InventoryResult msg = _player.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, pItem, false);
                if (msg == InventoryResult.Ok)
                {
                    _player.ModifyMoney(-price);
                    _player.RemoveItemFromBuyBackSlot(packet.Slot, false);
                    _player.ItemAddedQuestCheck(pItem.GetEntry(), pItem.GetCount());
                    _player.UpdateCriteria(CriteriaTypes.ReceiveEpicItem, pItem.GetEntry(), pItem.GetCount());
                    _player.StoreItem(dest, pItem, true);
                }
                else
                    _player.SendEquipError(msg, pItem);
                return;
            }
            else
                _player.SendBuyError(BuyResult.CantFindItem, creature, 0);
        }

        [WorldPacketHandler(ClientOpcodes.BuyItem)]
        void HandleBuyItem(BuyItem packet)
        {
            // client expects count starting at 1, and we send vendorslot+1 to client already
            if (packet.Muid > 0)
                --packet.Muid;
            else
                return; // cheating

            switch (packet.ItemType)
            {
                case ItemVendorType.Item:
                    Item bagItem = GetPlayer().GetItemByGuid(packet.ContainerGUID);

                    byte bag = ItemConst.NullBag;
                    if (bagItem != null && bagItem.IsBag())
                        bag = bagItem.GetSlot();
                    else if (packet.ContainerGUID == GetPlayer().GetGUID()) // The client sends the player guid when trying to store an item in the default backpack
                        bag = InventorySlots.Bag0;

                    GetPlayer().BuyItemFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity, bag, (byte)packet.Slot);
                    break;
                case ItemVendorType.Currency:
                    GetPlayer().BuyCurrencyFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity);
                    break;
                default:
                    Log.outDebug(LogFilter.Network, "WORLD: received wrong itemType {0} in HandleBuyItem", packet.ItemType);
                    break;
            }
        }

        [WorldPacketHandler(ClientOpcodes.AutoStoreBagItem)]
        void HandleAutoStoreBagItem(AutoStoreBagItem packet)
        {
            if (!packet.Inv.Items.Empty())
            {
                Log.outError(LogFilter.Network, "HandleAutoStoreBagItemOpcode - Invalid itemCount ({0})", packet.Inv.Items.Count);
                return;
            }

            Item item = GetPlayer().GetItemByPos(packet.ContainerSlotA, packet.SlotA);
            if (!item)
                return;

            if (!GetPlayer().IsValidPos(packet.ContainerSlotB, ItemConst.NullSlot, false))      // can be autostore pos
            {
                GetPlayer().SendEquipError(InventoryResult.WrongSlot);
                return;
            }

            ushort src = item.GetPos();
            InventoryResult msg;
            // check unequip potability for equipped items and bank bags
            if (Player.IsEquipmentPos(src) || Player.IsBagPos(src))
            {
                msg = GetPlayer().CanUnequipItem(src, !Player.IsBagPos(src));
                if (msg != InventoryResult.Ok)
                {
                    GetPlayer().SendEquipError(msg, item);
                    return;
                }
            }

            List<ItemPosCount> dest = new List<ItemPosCount>();
            msg = GetPlayer().CanStoreItem(packet.ContainerSlotB, ItemConst.NullSlot, dest, item, false);
            if (msg != InventoryResult.Ok)
            {
                GetPlayer().SendEquipError(msg, item);
                return;
            }

            // no-op: placed in same slot
            if (dest.Count == 1 && dest[0].pos == src)
            {
                // just remove grey item state
                GetPlayer().SendEquipError(InventoryResult.InternalBagError, item);
                return;
            }

            GetPlayer().RemoveItem(packet.ContainerSlotA, packet.SlotA, true);
            GetPlayer().StoreItem(dest, item, true);
        }

        public void SendEnchantmentLog(ObjectGuid owner, ObjectGuid caster, ObjectGuid itemGuid, uint itemId, uint enchantId, uint slot)
        {
            EnchantmentLog packet = new EnchantmentLog();
            packet.Caster = caster;
            packet.Owner = owner;
            packet.ItemGUID = itemGuid;
            packet.ItemID = itemId;
            packet.Enchantment = enchantId;
            packet.EnchantSlot = slot;

            GetPlayer().SendMessageToSet(packet, true);
        }

        public void SendItemEnchantTimeUpdate(ObjectGuid Playerguid, ObjectGuid Itemguid, uint slot, uint Duration)
        {
            ItemEnchantTimeUpdate data = new ItemEnchantTimeUpdate();
            data.ItemGuid = Itemguid;
            data.DurationLeft = Duration;
            data.Slot = slot;
            data.OwnerGuid = Playerguid;
            SendPacket(data);
        }

        [WorldPacketHandler(ClientOpcodes.WrapItem)]
        void HandleWrapItem(WrapItem packet)
        {
            if (packet.Inv.Items.Count != 2)
            {
                Log.outError(LogFilter.Network, "HandleWrapItem - Invalid itemCount ({0})", packet.Inv.Items.Count);
                return;
            }

            // Gift
            byte giftContainerSlot = packet.Inv.Items[0].ContainerSlot;
            byte giftSlot = packet.Inv.Items[0].Slot;
            // Item
            byte itemContainerSlot = packet.Inv.Items[1].ContainerSlot;
            byte itemSlot = packet.Inv.Items[1].Slot;

            Item gift = GetPlayer().GetItemByPos(giftContainerSlot, giftSlot);
            if (!gift)
            {
                GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift);
                return;
            }

            if (!gift.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsWrapper)) // cheating: non-wrapper wrapper
            {
                GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift);
                return;
            }

            Item item = GetPlayer().GetItemByPos(itemContainerSlot, itemSlot);
            if (!item)
            {
                GetPlayer().SendEquipError(InventoryResult.ItemNotFound, item);
                return;
            }

            if (item == gift) // not possable with pacjket from real client
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item);
                return;
            }

            if (item.IsEquipped())
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapEquipped, item);
                return;
            }

            if (item.GetUInt64Value(ItemFields.GiftCreator) != 0) // HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED);
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item);
                return;
            }

            if (item.IsBag())
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapBags, item);
                return;
            }

            if (item.IsSoulBound())
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapBound, item);
                return;
            }

            if (item.GetMaxStackCount() != 1)
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapStackable, item);
                return;
            }

            // maybe not correct check  (it is better than nothing)
            if (item.GetTemplate().GetMaxCount() > 0)
            {
                GetPlayer().SendEquipError(InventoryResult.CantWrapUnique, item);
                return;
            }

            SQLTransaction trans = new SQLTransaction();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_GIFT);
            stmt.AddValue(0, item.GetOwnerGUID().GetCounter());
            stmt.AddValue(1, item.GetGUID().GetCounter());
            stmt.AddValue(2, item.GetEntry());
            stmt.AddValue(3, item.GetUInt32Value(ItemFields.Flags));
            trans.Append(stmt);

            item.SetEntry(gift.GetEntry());

            switch (item.GetEntry())
            {
                case 5042:
                    item.SetEntry(5043);
                    break;
                case 5048:
                    item.SetEntry(5044);
                    break;
                case 17303:
                    item.SetEntry(17302);
                    break;
                case 17304:
                    item.SetEntry(17305);
                    break;
                case 17307:
                    item.SetEntry(17308);
                    break;
                case 21830:
                    item.SetEntry(21831);
                    break;
            }

            item.SetGuidValue(ItemFields.GiftCreator, GetPlayer().GetGUID());
            item.SetUInt32Value(ItemFields.Flags, (uint)ItemFieldFlags.Wrapped);
            item.SetState(ItemUpdateState.Changed, GetPlayer());

            if (item.GetState() == ItemUpdateState.New) // save new item, to have alway for `character_gifts` record in `item_instance`
            {
                // after save it will be impossible to remove the item from the queue
                Item.RemoveItemFromUpdateQueueOf(item, GetPlayer());
                item.SaveToDB(trans); // item gave inventory record unchanged and can be save standalone
            }
            DB.Characters.CommitTransaction(trans);

            uint count = 1;
            GetPlayer().DestroyItemCount(gift, ref count, true);
        }

        [WorldPacketHandler(ClientOpcodes.SocketGems)]
        void HandleSocketGems(SocketGems socketGems)
        {
            if (socketGems.ItemGuid.IsEmpty())
                return;

            //cheat . tried to socket same gem multiple times
            if ((!socketGems.GemItem[0].IsEmpty() && (socketGems.GemItem[0] == socketGems.GemItem[1] || socketGems.GemItem[0] == socketGems.GemItem[2])) ||
                (!socketGems.GemItem[1].IsEmpty() && (socketGems.GemItem[1] == socketGems.GemItem[2])))
                return;

            Item itemTarget = GetPlayer().GetItemByGuid(socketGems.ItemGuid);
            if (!itemTarget)                                         //missing item to socket
                return;

            ItemTemplate itemProto = itemTarget.GetTemplate();
            if (itemProto == null)
                return;

            //this slot is excepted when applying / removing meta gem bonus
            byte slot = itemTarget.IsEquipped() ? itemTarget.GetSlot() : ItemConst.NullSlot;

            Item[] gems = new Item[ItemConst.MaxGemSockets];
            ItemDynamicFieldGems[] gemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets];
            GemPropertiesRecord[] gemProperties = new GemPropertiesRecord[ItemConst.MaxGemSockets];
            ItemDynamicFieldGems[] oldGemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets];


            for (int i = 0; i < ItemConst.MaxGemSockets; ++i)
            {
                Item gem = _player.GetItemByGuid(socketGems.GemItem[i]);
                if (gem)
                {
                    gems[i] = gem;
                    gemData[i].ItemId = gem.GetEntry();
                    gemData[i].Context = (byte)gem.GetUInt32Value(ItemFields.Context);
                    for (ushort b = 0; b < gem.GetDynamicValues(ItemDynamicFields.BonusListIds).Count && b < 16; ++b)
                        gemData[i].BonusListIDs[b] = (ushort)gem.GetDynamicValue(ItemDynamicFields.BonusListIds, b);

                    gemProperties[i] = CliDB.GemPropertiesStorage.LookupByKey(gem.GetTemplate().GetGemProperties());
                }

                oldGemData[i] = itemTarget.GetGem((ushort)i);
            }

            // Find first prismatic socket
            uint firstPrismatic = 0;
            while (firstPrismatic < ItemConst.MaxGemSockets && itemTarget.GetSocketColor(firstPrismatic) != 0)
                ++firstPrismatic;

            for (uint i = 0; i < ItemConst.MaxGemSockets; ++i)                //check for hack maybe
            {
                if (gemProperties[i] == null)
                    continue;

                // tried to put gem in socket where no socket exists (take care about prismatic sockets)
                if (itemTarget.GetSocketColor(i) == 0)
                {
                    // no prismatic socket
                    if (itemTarget.GetEnchantmentId(EnchantmentSlot.Prismatic) == 0)
                        return;

                    if (i != firstPrismatic)
                        return;
                }     

                // Gem must match socket color
                if (ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)] != gemProperties[i].Type)
                {
                    // unless its red, blue, yellow or prismatic
                    if (!ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)].HasAnyFlag(SocketColor.Prismatic) || !gemProperties[i].Type.HasAnyFlag(SocketColor.Prismatic))
                        return;
                }
            }

            // check unique-equipped conditions
            for (int i = 0; i < ItemConst.MaxGemSockets; ++i)
            {
                if (!gems[i])
                    continue;

                // continue check for case when attempt add 2 similar unique equipped gems in one item.
                ItemTemplate iGemProto = gems[i].GetTemplate();

                // unique item (for new and already placed bit removed enchantments
                if (iGemProto.GetFlags().HasAnyFlag(ItemFlags.UniqueEquippable))
                {
                    for (int j = 0; j < ItemConst.MaxGemSockets; ++j)
                    {
                        if (i == j)                                    // skip self
                            continue;

                        if (gems[j])
                        {
                            if (iGemProto.GetId() == gems[j].GetEntry())
                            {
                                GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
                                return;
                            }
                        }
                        else if (oldGemData[j] != null)
                        {
                            if (iGemProto.GetId() == oldGemData[j].ItemId)
                            {
                                GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
                                return;
                            }
                        }
                    }
                }

                // unique limit type item
                int limit_newcount = 0;
                if (iGemProto.GetItemLimitCategory() != 0)
                {
                    ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(iGemProto.GetItemLimitCategory());
                    if (limitEntry != null)
                    {
                        // NOTE: limitEntry.mode is not checked because if item has limit then it is applied in equip case
                        for (int j = 0; j < ItemConst.MaxGemSockets; ++j)
                        {
                            if (gems[j])
                            {
                                // new gem
                                if (iGemProto.GetItemLimitCategory() == gems[j].GetTemplate().GetItemLimitCategory())
                                    ++limit_newcount;
                            }
                            else if (oldGemData[j] != null)
                            {
                                // existing gem
                                ItemTemplate jProto = Global.ObjectMgr.GetItemTemplate(oldGemData[j].ItemId);
                                if (jProto != null)
                                    if (iGemProto.GetItemLimitCategory() == jProto.GetItemLimitCategory())
                                        ++limit_newcount;
                            }
                        }

                        if (limit_newcount > 0 && limit_newcount > limitEntry.Quantity)
                        {
                            GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
                            return;
                        }
                    }
                }

                // for equipped item check all equipment for duplicate equipped gems
                if (itemTarget.IsEquipped())
                {
                    InventoryResult res = GetPlayer().CanEquipUniqueItem(gems[i], slot, (uint)Math.Max(limit_newcount, 0));
                    if (res != 0)
                    {
                        GetPlayer().SendEquipError(res, itemTarget);
                        return;
                    }
                }
            }

            bool SocketBonusActivated = itemTarget.GemsFitSockets();    //save state of socketbonus
            GetPlayer().ToggleMetaGemsActive(slot, false);             //turn off all metagems (except for the target item)

            //if a meta gem is being equipped, all information has to be written to the item before testing if the conditions for the gem are met

            //remove ALL mods - gem can change item level
            if (itemTarget.IsEquipped())
                _player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), false);

            for (ushort i = 0; i < ItemConst.MaxGemSockets; ++i)
            {
                if (gems[i])
                {
                    uint gemScalingLevel = _player.getLevel();
                    uint fixedLevel = gems[i].GetModifier(ItemModifier.ScalingStatDistributionFixedLevel);
                    if (fixedLevel != 0)
                        gemScalingLevel = fixedLevel;

                    itemTarget.SetGem(i, gemData[i], gemScalingLevel);

                    if (gemProperties[i] != null && gemProperties[i].EnchantID != 0)
                        itemTarget.SetEnchantment(EnchantmentSlot.Sock1 + i, gemProperties[i].EnchantID, 0, 0, GetPlayer().GetGUID());

                    uint gemCount = 1;
                    GetPlayer().DestroyItemCount(gems[i], ref gemCount, true);
                }
            }

            if (itemTarget.IsEquipped())
                _player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), true);

            Item childItem = _player.GetChildItemByGuid(itemTarget.GetChildItem());
            if (childItem)
            {
                if (childItem.IsEquipped())
                    _player._ApplyItemMods(childItem, childItem.GetSlot(), false);
                childItem.CopyArtifactDataFromParent(itemTarget);
                if (childItem.IsEquipped())
                    _player._ApplyItemMods(childItem, childItem.GetSlot(), true);
            }

            bool SocketBonusToBeActivated = itemTarget.GemsFitSockets();//current socketbonus state
            if (SocketBonusActivated ^ SocketBonusToBeActivated)     //if there was a change...
            {
                GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, false);
                itemTarget.SetEnchantment(EnchantmentSlot.Bonus, SocketBonusToBeActivated ? itemTarget.GetTemplate().GetSocketBonus() : 0, 0, 0, GetPlayer().GetGUID());
                GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, true);
                //it is not displayed, client has an inbuilt system to determine if the bonus is activated
            }

            GetPlayer().ToggleMetaGemsActive(slot, true);              //turn on all metagems (except for target item)

            GetPlayer().RemoveTradeableItem(itemTarget);
            itemTarget.ClearSoulboundTradeable(GetPlayer());           // clear tradeable flag

            itemTarget.SendUpdateSockets();
        }

        [WorldPacketHandler(ClientOpcodes.CancelTempEnchantment)]
        void HandleCancelTempEnchantment(CancelTempEnchantment packet)
        {
            // apply only to equipped item
            if (!Player.IsEquipmentPos(InventorySlots.Bag0, (byte)packet.Slot))
                return;

            Item item = GetPlayer().GetItemByPos(InventorySlots.Bag0, (byte)packet.Slot);
            if (!item)
                return;

            if (item.GetEnchantmentId(EnchantmentSlot.Temp) == 0)
                return;

            GetPlayer().ApplyEnchantment(item, EnchantmentSlot.Temp, false);
            item.ClearEnchantment(EnchantmentSlot.Temp);
        }

        [WorldPacketHandler(ClientOpcodes.GetItemPurchaseData)]
        void HandleGetItemPurchaseData(GetItemPurchaseData packet)
        {
            Item item = GetPlayer().GetItemByGuid(packet.ItemGUID);
            if (!item)
            {
                Log.outDebug(LogFilter.Network, "HandleGetItemPurchaseData: Item {0} not found!", packet.ItemGUID.ToString());
                return;
            }

            GetPlayer().SendRefundInfo(item);
        }

        [WorldPacketHandler(ClientOpcodes.ItemPurchaseRefund)]
        void HandleItemRefund(ItemPurchaseRefund packet)
        {
            Item item = GetPlayer().GetItemByGuid(packet.ItemGUID);
            if (!item)
            {
                Log.outDebug(LogFilter.Network, "WorldSession.HandleItemRefund: Item {0} not found!", packet.ItemGUID.ToString());
                return;
            }

            // Don't try to refund item currently being disenchanted
            if (GetPlayer().GetLootGUID() == packet.ItemGUID)
                return;

            GetPlayer().RefundItem(item);
        }

        bool CanUseBank(ObjectGuid bankerGUID = default(ObjectGuid))
        {
            // bankerGUID parameter is optional, set to 0 by default.
            if (bankerGUID.IsEmpty())
                bankerGUID = m_currentBankerGUID;

            bool isUsingBankCommand = (bankerGUID == GetPlayer().GetGUID() && bankerGUID == m_currentBankerGUID);

            if (!isUsingBankCommand)
            {
                Creature creature = GetPlayer().GetNPCIfCanInteractWith(bankerGUID, NPCFlags.Banker);
                if (!creature)
                    return false;
            }

            return true;
        }

        [WorldPacketHandler(ClientOpcodes.UseCritterItem)]
        void HandleUseCritterItem(UseCritterItem useCritterItem)
        {
            Item item = GetPlayer().GetItemByGuid(useCritterItem.ItemGuid);
            if (!item)
                return;

            if (item.GetTemplate().Effects.Count < 2)
                return;

            uint spellToLearn = item.GetTemplate().Effects[1].SpellID;
            foreach (BattlePetSpeciesRecord entry in CliDB.BattlePetSpeciesStorage.Values)
            {
                if (entry.SummonSpellID == spellToLearn)
                {
                    GetBattlePetMgr().AddPet(entry.Id, entry.CreatureID, BattlePetMgr.RollPetBreed(entry.Id), BattlePetMgr.GetDefaultPetQuality(entry.Id));
                    _player.UpdateCriteria(CriteriaTypes.OwnBattlePetCount);
                    break;
                }
            }

            GetPlayer().DestroyItem(item.GetBagSlot(), item.GetSlot(), true);
        }

        [WorldPacketHandler(ClientOpcodes.UpgradeItem)]
        void HandleUpgradeItem(UpgradeItem upgradeItem)
        {
            ItemUpgradeResult itemUpgradeResult = new ItemUpgradeResult();
            if (!_player.GetNPCIfCanInteractWith(upgradeItem.ItemMaster, NPCFlags.ItemUpgradeMaster))
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - {0} not found or player can't interact with it.", upgradeItem.ItemMaster.ToString());
                itemUpgradeResult.Success = false;
                SendPacket(itemUpgradeResult);
                return;
            }

            Item item = _player.GetItemByGuid(upgradeItem.ItemGUID);
            if (!item)
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems: Item {0} not found!", upgradeItem.ItemGUID.ToString());
                itemUpgradeResult.Success = false;
                SendPacket(itemUpgradeResult);
                return;
            }

            ItemUpgradeRecord itemUpgradeEntry = CliDB.ItemUpgradeStorage.LookupByKey(upgradeItem.UpgradeID);
            if (itemUpgradeEntry == null)
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - ItemUpgradeEntry ({0}) not found.", upgradeItem.UpgradeID);
                itemUpgradeResult.Success = false;
                SendPacket(itemUpgradeResult);
                return;
            }

            // Check if player has enough currency
            if (!_player.HasCurrency(itemUpgradeEntry.CurrencyID, itemUpgradeEntry.CurrencyCost))
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - Player has not enougth currency (ID: {0}, Cost: {1}) not found.", itemUpgradeEntry.CurrencyID, itemUpgradeEntry.CurrencyCost);
                itemUpgradeResult.Success = false;
                SendPacket(itemUpgradeResult);
                return;
            }

            uint currentUpgradeId = item.GetModifier(ItemModifier.UpgradeId);
            if (currentUpgradeId != itemUpgradeEntry.PrevItemUpgradeID)
            {
                Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - ItemUpgradeEntry ({0}) is not related to this ItemUpgradePath ({1}).", itemUpgradeEntry.Id, currentUpgradeId);
                itemUpgradeResult.Success = false;
                SendPacket(itemUpgradeResult);
                return;
            }

            itemUpgradeResult.Success = true;
            SendPacket(itemUpgradeResult);

            if (item.IsEquipped())
                _player._ApplyItemBonuses(item, item.GetSlot(), false);

            item.SetModifier(ItemModifier.UpgradeId, itemUpgradeEntry.Id);

            if (item.IsEquipped())
                _player._ApplyItemBonuses(item, item.GetSlot(), true);

            item.SetState(ItemUpdateState.Changed, _player);
            _player.ModifyCurrency((CurrencyTypes)itemUpgradeEntry.CurrencyID, -(int)itemUpgradeEntry.CurrencyCost);
        }

    }
}
