/*
===========================================================================
Copyright (C) 2015-2019 Project Meteor Dev Team
This file is part of Project Meteor Server.
Project Meteor Server is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Project Meteor Server 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Project Meteor Server. If not, see .
===========================================================================
*/
using Meteor.Map.actors.chara.player;
using Meteor.Map.Actors;
using Meteor.Map.DataObjects;
using Meteor.Map.packets.send.actor.inventory;
using System.Collections.Generic;
using System.Diagnostics;
namespace Meteor.Map.actors.chara
{
    class ReferencedItemPackage
    {
        const uint EMPTY = 0xFFFFFFFF;
        private readonly Player owner;
        private readonly InventoryItem[] referenceList;
        private readonly ushort itemPackageCode;
        private readonly ushort itemPackageCapacity;
        private bool writeToDB = false;
        public ReferencedItemPackage(Player owner, ushort capacity, ushort code)
        {
            this.owner = owner;
            itemPackageCode = code;
            itemPackageCapacity = capacity;
            referenceList = new InventoryItem[capacity];
            if (code == ItemPackage.EQUIPMENT)
                writeToDB = true;
        }
        public void ToggleDBWrite(bool flag)
        {
            writeToDB = flag;
        }
        #region Package Management
        public void SetList(InventoryItem[] toSet)
        {
            Debug.Assert(referenceList.Length == itemPackageCapacity);
            toSet.CopyTo(referenceList, 0);
        }
        public void Set(ushort[] positions, ushort[] itemSlots, ushort itemPackage)
        {
            Debug.Assert(positions.Length == itemSlots.Length);
           
            for (int i = 0; i < positions.Length; i++)
            {
                InventoryItem item = owner.GetItemPackage(itemPackage)?.GetItemAtSlot(itemSlots[i]);
                if (item == null)
                    continue;
                Database.EquipItem(owner, positions[i], item.uniqueId);
                referenceList[positions[i]] = item;
            }
            owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.Id));
            SendUpdate();
            owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.Id));
        }
        
        public void Set(ushort position, ushort itemPackagePosition, ushort itemPackageCode)
        {
            InventoryItem item = owner.GetItemPackage(itemPackageCode).GetItemAtSlot(itemPackagePosition);
            if (item == null)
                return;
            Set(position, item);
        }
        public void Set(ushort position, InventoryItem item)
        {
            if (position >= referenceList.Length)
                return;
            if (writeToDB)
                Database.EquipItem(owner, position, item.uniqueId);
            ItemPackage newPackage = owner.GetItemPackage(item.itemPackage);
            ItemPackage oldPackage = null;
            if (referenceList[position] != null)
            {
                oldPackage = owner.GetItemPackage(referenceList[position].itemPackage);
                InventoryItem oldItem = referenceList[position];
                oldPackage.MarkDirty(oldItem);
            }
            
            newPackage.MarkDirty(item);
            referenceList[position] = item;
            owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.Id));
            if (oldPackage != null)
                oldPackage.SendUpdate();
            newPackage.SendUpdate();
            SendSingleUpdate(position);
            owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.Id));            
        }
        public void Clear(ushort position)
        {
            if (position >= referenceList.Length)
                return;
            if (writeToDB)
                Database.UnequipItem(owner, position);
            ItemPackage oldItemPackage = owner.GetItemPackage(referenceList[position].itemPackage);
            oldItemPackage.MarkDirty(referenceList[position]);
            referenceList[position] = null;
            owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.Id));
            oldItemPackage.SendUpdate();
            SendSingleUpdate(position);
            owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.Id));
        }
        public void ClearAll()
        {
            List packagesToRefresh = new List();
            for (int i = 0; i < referenceList.Length; i++)
            {
                if (referenceList[i] == null)
                    continue;
                if (writeToDB)
                    Database.UnequipItem(owner, (ushort)i);
                ItemPackage package = owner.GetItemPackage(referenceList[i].itemPackage);               
                package.MarkDirty(referenceList[i]);            
                packagesToRefresh.Add(package);
                referenceList[i] = null;               
            }
            owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.Id));
            for (int i = 0; i < packagesToRefresh.Count; i++)
                packagesToRefresh[i].SendUpdate();
            SendUpdate();
            owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.Id));
        }
        #endregion
        #region Send Update Functions
        public void SendSingleUpdate(ushort position)
        {
            owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.Id, itemPackageCapacity, itemPackageCode));
            SendSingleLinkedItemPacket(owner, position);
            owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.Id));
        }
        public void SendUpdate()
        {
            SendUpdate(owner);
        }
        public void SendUpdate(Player targetPlayer)
        {
            List slotsToUpdate = new List();
            for (ushort i = 0; i < referenceList.Length; i++)
            {
                if (referenceList[i] != null)
                    slotsToUpdate.Add(i);
            }
         
            targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.Id, itemPackageCapacity, itemPackageCode));
            SendLinkedItemPackets(targetPlayer, slotsToUpdate);
            targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.Id));
        }
        public void SendUpdateAsItemPackage(Player targetPlayer)
        {
            SendUpdateAsItemPackage(targetPlayer, itemPackageCapacity, itemPackageCode);
        }
        public void SendUpdateAsItemPackage(Player targetPlayer, ushort destinationCapacity, ushort destinationCode)
        {
            List items = new List();
            for (ushort i = 0; i < referenceList.Length; i++)
            {
                if (referenceList[i] == null)
                    continue;
                InventoryItem item = referenceList[i];
                item.linkSlot = i; //We have to set the linkSlot as this is the position in the Referenced IP, not the original IP it's linked from.
                items.Add(referenceList[i]);
            }
            
            targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.Id, destinationCapacity, destinationCode));         
            SendItemPackets(targetPlayer, items);
            targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.Id));
            //Clean Up linkSlots
            for (ushort i = 0; i < referenceList.Length; i++)
            {
                if (referenceList[i] == null)
                    continue;
                InventoryItem item = referenceList[i];
                item.linkSlot = 0xFFFF;
            }
        }
        #endregion
        #region Packet Functions (Private)
        private void SendSingleLinkedItemPacket(Player targetPlayer, ushort position)
        {
            if (referenceList[position] == null)
                targetPlayer.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.Id, position));
            else
                targetPlayer.QueuePacket(LinkedItemListX01Packet.BuildPacket(owner.Id, position, referenceList[position]));
        }
        private void SendLinkedItemPackets(Player targetPlayer, List slotsToUpdate)
        {
            int currentIndex = 0;
            while (true)
            {
                if (slotsToUpdate.Count - currentIndex >= 64)
                    targetPlayer.QueuePacket(LinkedItemListX64Packet.BuildPacket(owner.Id, referenceList, slotsToUpdate, ref currentIndex));
                else if (slotsToUpdate.Count - currentIndex >= 32)
                    targetPlayer.QueuePacket(LinkedItemListX32Packet.BuildPacket(owner.Id, referenceList, slotsToUpdate, ref currentIndex));
                else if (slotsToUpdate.Count - currentIndex >= 16)
                    targetPlayer.QueuePacket(LinkedItemListX16Packet.BuildPacket(owner.Id, referenceList, slotsToUpdate, ref currentIndex));
                else if (slotsToUpdate.Count - currentIndex > 1)
                    targetPlayer.QueuePacket(LinkedItemListX08Packet.BuildPacket(owner.Id, referenceList, slotsToUpdate, ref currentIndex));
                else if (slotsToUpdate.Count - currentIndex == 1)
                {
                    targetPlayer.QueuePacket(LinkedItemListX01Packet.BuildPacket(owner.Id, slotsToUpdate[currentIndex], referenceList[slotsToUpdate[currentIndex]]));
                    currentIndex++;
                }
                else
                    break;
            }
        }
        private void SendItemPackets(Player player, List items)
        {
            int currentIndex = 0;
            while (true)
            {
                if (items.Count - currentIndex >= 64)
                    player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.Id, items, ref currentIndex));
                else if (items.Count - currentIndex >= 32)
                    player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.Id, items, ref currentIndex));
                else if (items.Count - currentIndex >= 16)
                    player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.Id, items, ref currentIndex));
                else if (items.Count - currentIndex > 1)
                    player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.Id, items, ref currentIndex));
                else if (items.Count - currentIndex == 1)
                {
                    player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.Id, items[currentIndex]));
                    currentIndex++;
                }
                else
                    break;
            }
        }
        #endregion
        #region Getters/Setters
        public ushort GetCode()
        {
            return itemPackageCode;
        }
        public int GetCapacity()
        {
            return itemPackageCapacity;
        }
        public Player GetOwner()
        {
            return owner;
        }
        public InventoryItem GetItemAtSlot(ushort position)
        {
            if (position < referenceList.Length)
                return referenceList[position];
            else
                return null;
        }
        #endregion        
    }
}