/*
 * 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 Framework.Dynamic;
using Game.Conditions;
using Game.Entities;
using Game.Groups;
using Game.Network.Packets;
using System;
using System.Collections.Generic;

namespace Game.Loots
{
    public class LootItem
    {
        public LootItem(LootStoreItem li)
        {
            itemid = li.itemid;
            conditions = li.conditions;

            ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid);
            freeforall = proto != null && proto.GetFlags().HasAnyFlag(ItemFlags.MultiDrop);
            follow_loot_rules = proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules);

            needs_quest = li.needs_quest;

            randomSuffix = ItemEnchantment.GenerateEnchSuffixFactor(itemid);
            randomPropertyId = ItemEnchantment.GenerateItemRandomPropertyId(itemid);
            upgradeId = Global.DB2Mgr.GetRulesetItemUpgrade(itemid);
            canSave = true;
        }

        public LootItem()
        {
            canSave = true;
        }

        public bool AllowedForPlayer(Player player)
        {
            // DB conditions check
            if (!Global.ConditionMgr.IsObjectMeetToConditions(player, conditions))
                return false;

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

            // not show loot for players without profession or those who already know the recipe
            if (pProto.GetFlags().HasAnyFlag(ItemFlags.HideUnusableRecipe) && (!player.HasSkill((SkillType)pProto.GetRequiredSkill()) || player.HasSpell(pProto.Effects[1].SpellID)))
                return false;

            // not show loot for not own team
            if (pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionHorde) && player.GetTeam() != Team.Horde)
                return false;

            if (pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionAlliance) && player.GetTeam() != Team.Alliance)
                return false;

            // check quest requirements
            if (!pProto.FlagsCu.HasAnyFlag(ItemFlagsCustom.IgnoreQuestStatus)
                && ((needs_quest || (pProto.GetStartQuest() != 0 && player.GetQuestStatus(pProto.GetStartQuest()) != QuestStatus.None)) && !player.HasQuestForItem(itemid)))
                return false;

            // Don't show bind-when-picked-up unique items if player already has the maximum allowed quantity.
            if (pProto.GetBonding() == ItemBondingType.OnAcquire && pProto.GetMaxCount() != 0 && player.GetItemCount(itemid, true) >= pProto.GetMaxCount())
                return false;

            return true;
        }

        public void AddAllowedLooter(Player player)
        {
            allowedGUIDs.Add(player.GetGUID());
        }

        public List<ObjectGuid> GetAllowedLooters() { return allowedGUIDs; }

        public uint itemid;
        public uint randomSuffix;
        public ItemRandomEnchantmentId randomPropertyId;
        public uint upgradeId;
        public List<uint> BonusListIDs = new List<uint>();
        public byte context;
        public List<Condition> conditions = new List<Condition>();                               // additional loot condition
        public List<ObjectGuid> allowedGUIDs = new List<ObjectGuid>();
        public byte count;
        public bool is_looted;
        public bool is_blocked;
        public bool freeforall;                          // free for all
        public bool is_underthreshold;
        public bool is_counted;
        public bool needs_quest;                          // quest drop
        public bool follow_loot_rules;
        public bool canSave;
    }

    public class QuestItem
    {
        public byte index;                                          // position in quest_items;
        public bool is_looted;

        public QuestItem()
        {
            index = 0;
            is_looted = false;
        }

        public QuestItem(byte _index, bool _islooted = false)
        {
            index = _index;
            is_looted = _islooted;
        }
    }

    public class LootValidatorRef : Reference<Loot, LootValidatorRef>
    {
        public LootValidatorRef()
        {

        }

        public override void targetObjectDestroyLink()
        {

        }

        public override void sourceObjectDestroyLink()
        {

        }
    }

    public class LootValidatorRefManager : RefManager<Loot, LootValidatorRef>
    {
        public new LootValidatorRef getFirst() { return (LootValidatorRef)base.getFirst(); }
        public new LootValidatorRef getLast() { return (LootValidatorRef)base.getLast(); }
    }

    public class Loot
    {
        public Loot(uint _gold = 0)
        {
            gold = _gold;
            unlootedCount = 0;
            loot_type = LootType.None;
            maxDuplicates = 1;
            containerID = ObjectGuid.Empty;
        }

        // Inserts the item into the loot (called by LootTemplate processors)
        public void AddItem(LootStoreItem item)
        {
            ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid);
            if (proto == null)
                return;

            uint count = RandomHelper.URand(item.mincount, item.maxcount);
            uint stacks = (uint)(count / proto.GetMaxStackSize() + (Convert.ToBoolean(count % proto.GetMaxStackSize()) ? 1 : 0));

            List<LootItem> lootItems = item.needs_quest ? quest_items : items;
            uint limit = (uint)(item.needs_quest ? SharedConst.MaxNRQuestItems : SharedConst.MaxNRLootItems);

            for (uint i = 0; i < stacks && lootItems.Count < limit; ++i)
            {
                LootItem generatedLoot = new LootItem(item);
                generatedLoot.context = _difficultyBonusTreeMod;
                generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize());
                if (_difficultyBonusTreeMod != 0)
                {
                    List<uint> bonusListIDs = Global.DB2Mgr.GetItemBonusTree(generatedLoot.itemid, _difficultyBonusTreeMod);
                    generatedLoot.BonusListIDs.AddRange(generatedLoot.BonusListIDs);
                }
                lootItems.Add(generatedLoot);
                count -= proto.GetMaxStackSize();

                // non-conditional one-player only items are counted here,
                // free for all items are counted in FillFFALoot(),
                // non-ffa conditionals are counted in FillNonQuestNonFFAConditionalLoot()
                if (!item.needs_quest && item.conditions.Empty() && !proto.GetFlags().HasAnyFlag(ItemFlags.MultiDrop))
                    ++unlootedCount;
            }
        }

        public LootItem GetItemInSlot(uint lootSlot)
        {
            if (lootSlot < items.Count)
                return items[(int)lootSlot];

            lootSlot -= (uint)items.Count;
            if (lootSlot < quest_items.Count)
                return quest_items[(int)lootSlot];

            return null;
        }

        // Calls processor of corresponding LootTemplate (which handles everything including references)
        public bool FillLoot(uint lootId, LootStore store, Player lootOwner, bool personal, bool noEmptyError = false, LootModes lootMode = LootModes.Default)
        {
            // Must be provided
            if (lootOwner == null)
                return false;

            LootTemplate tab = store.GetLootFor(lootId);

            if (tab == null)
            {
                if (!noEmptyError)
                    Log.outError(LogFilter.Sql, "Table '{0}' loot id #{1} used but it doesn't have records.", store.GetName(), lootId);
                return false;
            }

            _difficultyBonusTreeMod = lootOwner.GetMap().GetDifficultyLootBonusTreeMod();

            tab.Process(this, store.IsRatesAllowed(), (byte)lootMode);          // Processing is done there, callback via Loot.AddItem()

            // Setting access rights for group loot case
            Group group = lootOwner.GetGroup();
            if (!personal && group != null)
            {
                roundRobinPlayer = lootOwner.GetGUID();

                for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
                {
                    Player player = refe.GetSource();
                    if (player)   // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter
                        FillNotNormalLootFor(player, player.IsAtGroupRewardDistance(lootOwner));
                }

                for (byte i = 0; i < items.Count; ++i)
                {
                    ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(items[i].itemid);
                    if (proto != null)
                        if (proto.GetQuality() < group.GetLootThreshold())
                            items[i].is_underthreshold = true;
                }
            }
            // ... for personal loot
            else
                FillNotNormalLootFor(lootOwner, true);

            return true;
        }

        void FillNotNormalLootFor(Player player, bool presentAtLooting)
        {
            ulong plguid = player.GetGUID().GetCounter();

            var questItemList = PlayerQuestItems.LookupByKey(plguid);
            if (questItemList.Empty())
                FillQuestLoot(player);

            questItemList = PlayerFFAItems.LookupByKey(plguid);
            if (questItemList.Empty())
                FillFFALoot(player);

            questItemList = PlayerNonQuestNonFFAConditionalItems.LookupByKey(plguid);
            if (questItemList.Empty())
                FillNonQuestNonFFAConditionalLoot(player, presentAtLooting);

            // if not auto-processed player will have to come and pick it up manually
            if (!presentAtLooting)
                return;

            // Process currency items
            uint max_slot = GetMaxSlotInLootFor(player);
            LootItem item = null;
            int itemsSize = items.Count;
            for (byte i = 0; i < max_slot; ++i)
            {
                if (i < items.Count)
                    item = items[i];
                else
                    item = quest_items[i - itemsSize];

                if (!item.is_looted && item.freeforall && item.AllowedForPlayer(player))
                {
                    ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid);
                    if (proto != null)
                        if (proto.IsCurrencyToken())
                            player.StoreLootItem(i, this);
                }
            }
        }

        List<QuestItem> FillFFALoot(Player player)
        {
            List<QuestItem> ql = new List<QuestItem>();

            for (byte i = 0; i < items.Count; ++i)
            {
                LootItem item = items[i];
                if (!item.is_looted && item.freeforall && item.AllowedForPlayer(player))
                {
                    ql.Add(new QuestItem(i));
                    ++unlootedCount;
                }
            }
            if (ql.Empty())
                return null;


            PlayerFFAItems[player.GetGUID().GetCounter()] = ql;
            return ql;
        }

        List<QuestItem> FillQuestLoot(Player player)
        {
            if (items.Count == SharedConst.MaxNRLootItems)
                return null;

            List<QuestItem> ql = new List<QuestItem>();

            for (byte i = 0; i < quest_items.Count; ++i)
            {
                LootItem item = quest_items[i];

                if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player.GetGroup() != null
                    && ((player.GetGroup().GetLootMethod() == LootMethod.MasterLoot && player.GetGroup().GetLooterGuid() == player.GetGUID()) || player.GetGroup().GetLootMethod() != LootMethod.MasterLoot))))
                {
                    ql.Add(new QuestItem(i));

                    // quest items get blocked when they first appear in a
                    // player's quest vector
                    //
                    // increase once if one looter only, looter-times if free for all
                    if (item.freeforall || !item.is_blocked)
                        ++unlootedCount;
                    if (!player.GetGroup() || player.GetGroup().GetLootMethod() != LootMethod.GroupLoot)
                        item.is_blocked = true;

                    if (items.Count + ql.Count == SharedConst.MaxNRLootItems)
                        break;
                }
            }
            if (ql.Empty())
                return null;

            PlayerQuestItems[player.GetGUID().GetCounter()] = ql;
            return ql;
        }

        List<QuestItem> FillNonQuestNonFFAConditionalLoot(Player player, bool presentAtLooting)
        {
            List<QuestItem> ql = new List<QuestItem>();

            for (byte i = 0; i < items.Count; ++i)
            {
                LootItem item = items[i];
                if (!item.is_looted && !item.freeforall && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player.GetGroup() != null
                    && ((player.GetGroup().GetLootMethod() == LootMethod.MasterLoot && player.GetGroup().GetLooterGuid() == player.GetGUID()) || player.GetGroup().GetLootMethod() != LootMethod.MasterLoot))))
                {
                    if (presentAtLooting)
                        item.AddAllowedLooter(player);
                    if (!item.conditions.Empty())
                    {
                        ql.Add(new QuestItem(i));
                        if (!item.is_counted)
                        {
                            ++unlootedCount;
                            item.is_counted = true;
                        }
                    }
                }
            }
            if (ql.Empty())
                return null;

            PlayerNonQuestNonFFAConditionalItems[player.GetGUID().GetCounter()] = ql;
            return ql;
        }

        public void NotifyItemRemoved(byte lootIndex)
        {
            // notify all players that are looting this that the item was removed
            // convert the index to the slot the player sees
            for (int i = 0; i < PlayersLooting.Count; ++i)
            {
                Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]);
                if (player != null)
                    player.SendNotifyLootItemRemoved(GetGUID(), lootIndex);
                else
                    PlayersLooting.RemoveAt(i);
            }
        }

        public void NotifyMoneyRemoved()
        {
            // notify all players that are looting this that the money was removed
            for (var i = 0; i < PlayersLooting.Count; ++i)
            {
                Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]);
                if (player != null)
                    player.SendNotifyLootMoneyRemoved(GetGUID());
                else
                    PlayersLooting.RemoveAt(i);
            }
        }

        public void NotifyQuestItemRemoved(byte questIndex)
        {
            // when a free for all questitem is looted
            // all players will get notified of it being removed
            // (other questitems can be looted by each group member)
            // bit inefficient but isn't called often
            for (var i = 0; i < PlayersLooting.Count; ++i)
            {
                Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]);
                if (player)
                {
                    var pql = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter());
                    if (!pql.Empty())
                    {
                        byte j;
                        for (j = 0; j < pql.Count; ++j)
                            if (pql[j].index == questIndex)
                                break;

                        if (j < pql.Count)
                            player.SendNotifyLootItemRemoved(GetGUID(), (byte)(items.Count + j));
                    }
                }
                else
                    PlayersLooting.RemoveAt(i);
            }
        }

        public void generateMoneyLoot(uint minAmount, uint maxAmount)
        {
            if (maxAmount > 0)
            {
                if (maxAmount <= minAmount)
                    gold = (uint)(maxAmount * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney));
                else if ((maxAmount - minAmount) < 32700)
                    gold = (uint)(RandomHelper.URand(minAmount, maxAmount) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney));
                else
                    gold = (uint)(RandomHelper.URand(minAmount >> 8, maxAmount >> 8) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)) << 8;
            }
        }

        public void DeleteLootItemFromContainerItemDB(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, containerID.GetCounter());
            stmt.AddValue(1, itemID);
            DB.Characters.Execute(stmt);

            // Mark the item looted to prevent resaving
            foreach (var lootItem in items)
            {
                if (lootItem.itemid != itemID)
                    continue;

                lootItem.canSave = false;
                break;
            }
        }

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

        public LootItem LootItemInSlot(uint lootSlot, Player player)
        {
            QuestItem qitem, ffaitem, conditem;
            return LootItemInSlot(lootSlot, player, out qitem, out ffaitem, out conditem);
        }
        public LootItem LootItemInSlot(uint lootSlot, Player player, out QuestItem qitem, out QuestItem ffaitem, out QuestItem conditem)
        {
            qitem = null;
            ffaitem = null;
            conditem = null;

            LootItem item = null;
            bool is_looted = true;
            if (lootSlot >= items.Count)
            {
                uint questSlot = (uint)(lootSlot - items.Count);
                var questItems = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter());
                if (!questItems.Empty())
                {
                    QuestItem qitem2 = questItems.Find(p => p.index == questSlot);
                    if (qitem2 != null)
                    {
                        qitem = qitem2;
                        item = quest_items[qitem2.index];
                        is_looted = qitem2.is_looted;
                    }
                }
            }
            else
            {
                item = items[(int)lootSlot];
                is_looted = item.is_looted;
                if (item.freeforall)
                {
                    var questItemList = PlayerFFAItems.LookupByKey(player.GetGUID().GetCounter());
                    if (!questItemList.Empty())
                    {
                        foreach (var c in questItemList)
                        {
                            if (c.index == lootSlot)
                            {
                                QuestItem ffaitem2 = c;
                                ffaitem = ffaitem2;
                                is_looted = ffaitem2.is_looted;
                                break;
                            }
                        }
                    }
                }
                else if (!item.conditions.Empty())
                {
                    var questItemList = PlayerNonQuestNonFFAConditionalItems.LookupByKey(player.GetGUID().GetCounter());
                    if (!questItemList.Empty())
                    {
                        foreach (var iter in questItemList)
                        {
                            if (iter.index == lootSlot)
                            {
                                QuestItem conditem2 = iter;
                                conditem = conditem2;
                                is_looted = conditem2.is_looted;
                                break;
                            }
                        }
                    }
                }
            }

            if (is_looted)
                return null;

            return item;
        }

        public uint GetMaxSlotInLootFor(Player player)
        {
            var questItemList = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter());
            return (uint)(items.Count + questItemList.Count);
        }

        // return true if there is any item that is lootable for any player (not quest item, FFA or conditional)
        public bool hasItemForAll()
        {
            // Gold is always lootable
            if (gold != 0)
                return true;

            foreach (LootItem item in items)
                if (!item.is_looted && !item.freeforall && item.conditions.Empty())
                    return true;

            return false;
        }

        // return true if there is any FFA, quest or conditional item for the player.
        public bool hasItemFor(Player player)
        {
            var lootPlayerQuestItems = GetPlayerQuestItems();
            var q_list = lootPlayerQuestItems.LookupByKey(player.GetGUID().GetCounter());
            if (!q_list.Empty())
            {
                foreach (var qi in q_list)
                {
                    LootItem item = quest_items[qi.index];
                    if (!qi.is_looted && !item.is_looted)
                        return true;
                }
            }

            var lootPlayerFFAItems = GetPlayerFFAItems();
            var ffa_list = lootPlayerFFAItems.LookupByKey(player.GetGUID().GetCounter());
            if (!ffa_list.Empty())
            {
                foreach (var fi in ffa_list)
                {
                    LootItem item = items[fi.index];
                    if (!fi.is_looted && !item.is_looted)
                        return true;
                }
            }

            var lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems();
            var conditional_list = lootPlayerNonQuestNonFFAConditionalItems.LookupByKey(player.GetGUID().GetCounter());
            if (!conditional_list.Empty())
            {
                foreach (var ci in conditional_list)
                {
                    LootItem item = items[ci.index];
                    if (!ci.is_looted && !item.is_looted)
                        return true;
                }
            }

            return false;
        }

        // return true if there is any item over the group threshold (i.e. not underthreshold).
        public bool hasOverThresholdItem()
        {
            for (byte i = 0; i < items.Count; ++i)
            {
                if (!items[i].is_looted && !items[i].is_underthreshold && !items[i].freeforall)
                    return true;
            }

            return false;
        }

        public void BuildLootResponse(LootResponse packet, Player viewer, PermissionTypes permission)
        {
            if (permission == PermissionTypes.None)
                return;

            packet.Coins = gold;

            switch (permission)
            {
                case PermissionTypes.Group:
                case PermissionTypes.Master:
                case PermissionTypes.Restricted:
                    {
                        // if you are not the round-robin group looter, you can only see
                        // blocked rolled items and quest items, and !ffa items
                        for (byte i = 0; i < items.Count; ++i)
                        {
                            if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer))
                            {
                                LootSlotType slot_type;

                                if (items[i].is_blocked) // for ML & restricted is_blocked = !is_underthreshold
                                {
                                    switch (permission)
                                    {
                                        case PermissionTypes.Group:
                                            slot_type = LootSlotType.RollOngoing;
                                            break;
                                        case PermissionTypes.Master:
                                            {
                                                if (viewer.GetGroup() && viewer.GetGroup().GetMasterLooterGuid() == viewer.GetGUID())
                                                    slot_type = LootSlotType.Master;
                                                else
                                                    slot_type = LootSlotType.Locked;
                                                break;
                                            }
                                        case PermissionTypes.Restricted:
                                            slot_type = LootSlotType.Locked;
                                            break;
                                        default:
                                            continue;
                                    }
                                }
                                else if (roundRobinPlayer.IsEmpty() || viewer.GetGUID() == roundRobinPlayer || !items[i].is_underthreshold)
                                {
                                    // no round robin owner or he has released the loot
                                    // or it IS the round robin group owner
                                    // => item is lootable
                                    slot_type = LootSlotType.AllowLoot;
                                }
                                else
                                    // item shall not be displayed.
                                    continue;

                                LootItemData lootItem = new LootItemData();
                                lootItem.LootListID = (byte)(i + 1);
                                lootItem.UIType = slot_type;
                                lootItem.Quantity = items[i].count;
                                lootItem.Loot = new ItemInstance(items[i]);
                                packet.Items.Add(lootItem);
                            }
                        }
                        break;
                    }
                case PermissionTypes.All:
                case PermissionTypes.Owner:
                    {
                        for (byte i = 0; i < items.Count; ++i)
                        {
                            if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer))
                            {
                                LootItemData lootItem = new LootItemData();
                                lootItem.LootListID = (byte)(i + 1);
                                lootItem.UIType = (permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot);
                                lootItem.Quantity = items[i].count;
                                lootItem.Loot = new ItemInstance(items[i]);
                                packet.Items.Add(lootItem);
                            }
                        }
                        break;
                    }
                default:
                    return;
            }

            LootSlotType slotType = permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot;
            var lootPlayerQuestItems = GetPlayerQuestItems();
            var q_list = lootPlayerQuestItems.LookupByKey(viewer.GetGUID().GetCounter());
            if (!q_list.Empty())
            {
                foreach (var qi in q_list)
                {
                    LootItem item = quest_items[qi.index];
                    if (!qi.is_looted && !item.is_looted)
                    {
                        LootItemData lootItem = new LootItemData();
                        lootItem.LootListID = (byte)(items.Count + qi.index + 1);
                        lootItem.Quantity = item.count;
                        lootItem.Loot = new ItemInstance(item);

                        if (item.follow_loot_rules)
                        {
                            switch (permission)
                            {
                                case PermissionTypes.Master:
                                    lootItem.UIType = LootSlotType.Master;
                                    break;
                                case PermissionTypes.Restricted:
                                    lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot;
                                    break;
                                case PermissionTypes.Group:
                                    if (!item.is_blocked)
                                        lootItem.UIType = LootSlotType.AllowLoot;
                                    else
                                        lootItem.UIType = LootSlotType.RollOngoing;
                                    break;
                                default:
                                    lootItem.UIType = slotType;
                                    break;
                            }
                        }
                        else
                            lootItem.UIType = slotType;

                        packet.Items.Add(lootItem);
                    }
                }
            }

            var lootPlayerFFAItems = GetPlayerFFAItems();
            var ffa_list = lootPlayerFFAItems.LookupByKey(viewer.GetGUID().GetCounter());
            if (!ffa_list.Empty())
            {
                foreach (var fi in ffa_list)
                {
                    LootItem item = items[fi.index];
                    if (!fi.is_looted && !item.is_looted)
                    {
                        LootItemData lootItem = new LootItemData();
                        lootItem.LootListID = (byte)(items.Count + fi.index + 1);
                        lootItem.UIType = slotType;
                        lootItem.Quantity = item.count;
                        lootItem.Loot = new ItemInstance(item);
                        packet.Items.Add(lootItem);
                    }
                }
            }

            var lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems();
            var conditional_list = lootPlayerNonQuestNonFFAConditionalItems.LookupByKey(viewer.GetGUID().GetCounter());
            if (!conditional_list.Empty())
            {
                foreach (var ci in conditional_list)
                {
                    LootItem item = items[ci.index];
                    if (!ci.is_looted && !item.is_looted)
                    {
                        LootItemData lootItem = new LootItemData();
                        lootItem.LootListID = (byte)(items.Count + ci.index + 1);
                        lootItem.Quantity = item.count;
                        lootItem.Loot = new ItemInstance(item);

                        if (item.follow_loot_rules)
                        {
                            switch (permission)
                            {
                                case PermissionTypes.Master:
                                    lootItem.UIType = LootSlotType.Master;
                                    break;
                                case PermissionTypes.Restricted:
                                    lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot;
                                    break;
                                case PermissionTypes.Group:
                                    if (!item.is_blocked)
                                        lootItem.UIType = LootSlotType.AllowLoot;
                                    else
                                        lootItem.UIType = LootSlotType.RollOngoing;
                                    break;
                                default:
                                    lootItem.UIType = slotType;
                                    break;
                            }
                        }
                        else
                            lootItem.UIType = slotType;

                        packet.Items.Add(lootItem);
                    }
                }
            }
        }

        public void addLootValidatorRef(LootValidatorRef pLootValidatorRef)
        {
            i_LootValidatorRefManager.insertFirst(pLootValidatorRef);
        }

        public void clear()
        {
            PlayerQuestItems.Clear();

            PlayerFFAItems.Clear();

            PlayerNonQuestNonFFAConditionalItems.Clear();

            PlayersLooting.Clear();
            items.Clear();
            quest_items.Clear();
            gold = 0;
            unlootedCount = 0;
            roundRobinPlayer = ObjectGuid.Empty;
            i_LootValidatorRefManager.clearReferences();
            _difficultyBonusTreeMod = 0;
        }

        public bool empty() { return items.Empty() && gold == 0; }
        public bool isLooted() { return gold == 0 && unlootedCount == 0; }

        public void AddLooter(ObjectGuid guid) { PlayersLooting.Add(guid); }
        public void RemoveLooter(ObjectGuid guid) { PlayersLooting.Remove(guid); }

        public ObjectGuid GetGUID() { return _GUID; }
        public void SetGUID(ObjectGuid guid) { _GUID = guid; }

        public MultiMap<ulong, QuestItem> GetPlayerQuestItems() { return PlayerQuestItems; }
        public MultiMap<ulong, QuestItem> GetPlayerFFAItems() { return PlayerFFAItems; }
        public MultiMap<ulong, QuestItem> GetPlayerNonQuestNonFFAConditionalItems() { return PlayerNonQuestNonFFAConditionalItems; }

        public List<LootItem> items = new List<LootItem>();
        public List<LootItem> quest_items = new List<LootItem>();
        public uint gold;
        public byte unlootedCount;
        public ObjectGuid roundRobinPlayer;                                // GUID of the player having the Round-Robin ownership for the loot. If 0, round robin owner has released.
        public LootType loot_type;                                     // required for achievement system
        public byte maxDuplicates;                                    // Max amount of items with the same entry that can drop (default is 1; on 25 man raid mode 3)

        public ObjectGuid containerID;

        List<ObjectGuid> PlayersLooting = new List<ObjectGuid>();
        MultiMap<ulong, QuestItem> PlayerQuestItems = new MultiMap<ulong, QuestItem>();
        MultiMap<ulong, QuestItem> PlayerFFAItems = new MultiMap<ulong, QuestItem>();
        MultiMap<ulong, QuestItem> PlayerNonQuestNonFFAConditionalItems = new MultiMap<ulong, QuestItem>();

        // All rolls are registered here. They need to know, when the loot is not valid anymore
        LootValidatorRefManager i_LootValidatorRefManager = new LootValidatorRefManager();

        // Loot GUID
        ObjectGuid _GUID;
        byte _difficultyBonusTreeMod;
    }

    public class AELootResult
    {
        public void Add(Item item, byte count, LootType lootType)
        {
            var id = _byItem.LookupByKey(item);
            if (id != 0)
            {
                var resultValue = _byOrder[id];
                resultValue.count += count;
            }
            else
            {
                _byItem[item] = _byOrder.Count;
                ResultValue value;
                value.item = item;
                value.count = count;
                value.lootType = lootType;
                _byOrder.Add(value);
            }
        }

        public List<ResultValue> GetByOrder()
        {
            return _byOrder;
        }

        List<ResultValue> _byOrder = new List<ResultValue>();
        Dictionary<Item, int> _byItem = new Dictionary<Item, int>();

        public struct ResultValue
        {
            public Item item;
            public byte count;
            public LootType lootType;
        }
    }
}
