/*
 * 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.Configuration;
using Framework.Constants;
using Framework.Database;
using Game.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Maps;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;

namespace Game.DungeonFinding
{
    public class LFGManager : Singleton<LFGManager>
    {
        LFGManager()
        {
            m_lfgProposalId = 1;
            m_options = (LfgOptions)ConfigMgr.GetDefaultValue("DungeonFinder.OptionsMask", 1);

            new LFGPlayerScript();
            new LFGGroupScript();
        }

        public string ConcatenateDungeons(List<uint> dungeons)
        {
            StringBuilder dungeonstr = new StringBuilder();
            if (!dungeons.Empty())
            {
                foreach (var id in dungeons)
                {
                    if (dungeonstr.Capacity != 0)
                        dungeonstr.AppendFormat(", {0}", id);
                    else
                        dungeonstr.AppendFormat("{0}", id);
                }
            }
            return dungeonstr.ToString();
        }

        public void _LoadFromDB(SQLFields field, ObjectGuid guid)
        {
            if (field == null)
                return;

            if (!guid.IsParty())
                return;

            SetLeader(guid, ObjectGuid.Create(HighGuid.Player, field.Read<ulong>(0)));

            uint dungeon = field.Read<uint>(18);
            LfgState state = (LfgState)field.Read<byte>(19);

            if (dungeon == 0 || state == 0)
                return;

            SetDungeon(guid, dungeon);

            switch (state)
            {
                case LfgState.Dungeon:
                case LfgState.FinishedDungeon:
                    SetState(guid, state);
                    break;
                default:
                    break;
            }
        }

        void _SaveToDB(ObjectGuid guid, uint db_guid)
        {
            if (!guid.IsParty())
                return;

            SQLTransaction trans = new SQLTransaction();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_LFG_DATA);
            stmt.AddValue(0, db_guid);
            trans.Append(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_LFG_DATA);
            stmt.AddValue(0, db_guid);
            stmt.AddValue(1, GetDungeon(guid));
            stmt.AddValue(2, GetState(guid));
            trans.Append(stmt);

            DB.Characters.CommitTransaction(trans);
        }

        public void LoadRewards()
        {
            uint oldMSTime = Time.GetMSTime();

            RewardMapStore.Clear();

            // ORDER BY is very important for GetRandomDungeonReward!
            SQLResult result = DB.World.Query("SELECT dungeonId, maxLevel, firstQuestId, otherQuestId FROM lfg_dungeon_rewards ORDER BY dungeonId, maxLevel ASC");

            if (result.IsEmpty())
            {
                Log.outError(LogFilter.ServerLoading, "Loaded 0 lfg dungeon rewards. DB table `lfg_dungeon_rewards` is empty!");
                return;
            }

            uint count = 0;

            do
            {
                uint dungeonId = result.Read<uint>(0);
                uint maxLevel = result.Read<byte>(1);
                uint firstQuestId = result.Read<uint>(2);
                uint otherQuestId = result.Read<uint>(3);

                if (GetLFGDungeonEntry(dungeonId) == 0)
                {
                    Log.outError(LogFilter.Sql, "Dungeon {0} specified in table `lfg_dungeon_rewards` does not exist!", dungeonId);
                    continue;
                }

                if (maxLevel == 0 || maxLevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
                {
                    Log.outError(LogFilter.Sql, "Level {0} specified for dungeon {1} in table `lfg_dungeon_rewards` can never be reached!", maxLevel, dungeonId);
                    maxLevel = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel);
                }

                if (firstQuestId == 0 || Global.ObjectMgr.GetQuestTemplate(firstQuestId) == null)
                {
                    Log.outError(LogFilter.Sql, "First quest {0} specified for dungeon {1} in table `lfg_dungeon_rewards` does not exist!", firstQuestId, dungeonId);
                    continue;
                }

                if (otherQuestId != 0 && Global.ObjectMgr.GetQuestTemplate(otherQuestId) == null)
                {
                    Log.outError(LogFilter.Sql, "Other quest {0} specified for dungeon {1} in table `lfg_dungeon_rewards` does not exist!", otherQuestId, dungeonId);
                    otherQuestId = 0;
                }

                RewardMapStore.Add(dungeonId, new LfgReward(maxLevel, firstQuestId, otherQuestId));
                ++count;
            }
            while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} lfg dungeon rewards in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        LFGDungeonData GetLFGDungeon(uint id)
        {
            return LfgDungeonStore.LookupByKey(id);
        }

        public void LoadLFGDungeons(bool reload = false)
        {
            uint oldMSTime = Time.GetMSTime();

            LfgDungeonStore.Clear();

            // Initialize Dungeon map with data from dbcs
            foreach (var dungeon in CliDB.LfgDungeonsStorage.Values)
            {
                switch (dungeon.Type)
                {
                    case LfgType.Dungeon:
                    case LfgType.Raid:
                    case LfgType.RandomDungeon:
                    case LfgType.Zone:
                        LfgDungeonStore[dungeon.Id] = new LFGDungeonData(dungeon);
                        break;
                }
            }

            // Fill teleport locations from DB
            SQLResult result = DB.World.Query("SELECT dungeonId, position_x, position_y, position_z, orientation, requiredItemLevel FROM lfg_dungeon_template");
            if (result.IsEmpty())
            {
                Log.outError(LogFilter.ServerLoading, "Loaded 0 lfg dungeon templates. DB table `lfg_dungeon_template` is empty!");
                return;
            }

            uint count = 0;

            do
            {
                uint dungeonId = result.Read<uint>(0);
                if (!LfgDungeonStore.ContainsKey(dungeonId))
                {
                    Log.outError(LogFilter.Sql, "table `lfg_entrances` contains coordinates for wrong dungeon {0}", dungeonId);
                    continue;
                }

                var data = LfgDungeonStore[dungeonId];
                data.x = result.Read<float>(1);
                data.y = result.Read<float>(2);
                data.z = result.Read<float>(3);
                data.o = result.Read<float>(4);
                data.requiredItemLevel = result.Read<ushort>(5);

                ++count;
            }
            while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} lfg dungeon templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));

            // Fill all other teleport coords from areatriggers
            foreach (var pair in LfgDungeonStore)
            {
                LFGDungeonData dungeon = pair.Value;

                // No teleport coords in database, load from areatriggers
                if (dungeon.type != LfgType.RandomDungeon && dungeon.x == 0.0f && dungeon.y == 0.0f && dungeon.z == 0.0f)
                {
                    AreaTriggerStruct at = Global.ObjectMgr.GetMapEntranceTrigger(dungeon.map);
                    if (at == null)
                    {
                        Log.outError(LogFilter.Lfg, "LoadLFGDungeons: Failed to load dungeon {0} (Id: {1}), cant find areatrigger for map {2}", dungeon.name, dungeon.id, dungeon.map);
                        continue;
                    }

                    dungeon.map = at.target_mapId;
                    dungeon.x = at.target_X;
                    dungeon.y = at.target_Y;
                    dungeon.z = at.target_Z;
                    dungeon.o = at.target_Orientation;
                }

                if (dungeon.type != LfgType.RandomDungeon)
                    CachedDungeonMapStore.Add((byte)dungeon.group, dungeon.id);
                CachedDungeonMapStore.Add(0, dungeon.id);
            }

            if (reload)
                CachedDungeonMapStore.Clear();
        }

        public void Update(uint diff)
        {
            if (!isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser))
                return;

            long currTime = Time.UnixTime;

            // Remove obsolete role checks
            foreach (var pairCheck in RoleChecksStore)
            {
                LfgRoleCheck roleCheck = pairCheck.Value;
                if (currTime < roleCheck.cancelTime)
                    continue;
                roleCheck.state = LfgRoleCheckState.MissingRole;

                foreach (var pairRole in roleCheck.roles)
                {
                    ObjectGuid guid = pairRole.Key;
                    RestoreState(guid, "Remove Obsolete RoleCheck");
                    SendLfgRoleCheckUpdate(guid, roleCheck);
                    if (guid == roleCheck.leader)
                        SendLfgJoinResult(guid, new LfgJoinResultData(LfgJoinResult.RoleCheckFailed, LfgRoleCheckState.MissingRole));
                }

                RestoreState(pairCheck.Key, "Remove Obsolete RoleCheck");
                RoleChecksStore.Remove(pairCheck.Key);
            }

            // Remove obsolete proposals
            foreach (var removePair in ProposalsStore.ToList())
            {
                if (removePair.Value.cancelTime < currTime)
                    RemoveProposal(removePair, LfgUpdateType.ProposalFailed);
            }

            // Remove obsolete kicks
            foreach (var itBoot in BootsStore)
            {
                LfgPlayerBoot boot = itBoot.Value;
                if (boot.cancelTime < currTime)
                {
                    boot.inProgress = false;
                    foreach (var itVotes in boot.votes)
                    {
                        ObjectGuid pguid = itVotes.Key;
                        if (pguid != boot.victim)
                            SendLfgBootProposalUpdate(pguid, boot);
                    }
                    SetVoteKick(itBoot.Key, false);
                    BootsStore.Remove(itBoot.Key);
                }
            }

            uint lastProposalId = m_lfgProposalId;
            // Check if a proposal can be formed with the new groups being added
            foreach (var it in QueuesStore)
            {
                byte newProposals = it.Value.FindGroups();
                if (newProposals != 0)
                    Log.outDebug(LogFilter.Lfg, "Update: Found {0} new groups in queue {1}", newProposals, it.Key);
            }

            if (lastProposalId != m_lfgProposalId)
            {
                // FIXME lastProposalId ? lastProposalId +1 ?
                foreach (var itProposal in ProposalsStore.SkipWhile(p => p.Key == m_lfgProposalId))
                {
                    uint proposalId = itProposal.Key;
                    LfgProposal proposal = ProposalsStore[proposalId];

                    ObjectGuid guid = ObjectGuid.Empty;
                    foreach (var itPlayers in proposal.players)
                    {
                        guid = itPlayers.Key;
                        SetState(guid, LfgState.Proposal);
                        ObjectGuid gguid = GetGroup(guid);
                        if (!gguid.IsEmpty())
                        {
                            SetState(gguid, LfgState.Proposal);
                            SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.ProposalBegin, GetSelectedDungeons(guid), GetComment(guid)), true);
                        }
                        else
                            SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.ProposalBegin, GetSelectedDungeons(guid), GetComment(guid)), false);
                        SendLfgUpdateProposal(guid, proposal);
                    }

                    if (proposal.state == LfgProposalState.Success)
                        UpdateProposal(proposalId, guid, true);
                }
            }

            // Update all players status queue info
            if (m_QueueTimer > SharedConst.LFGQueueUpdateInterval)
            {
                m_QueueTimer = 0;
                foreach (var it in QueuesStore)
                    it.Value.UpdateQueueTimers(it.Key, currTime);
            }
            else
                m_QueueTimer += diff;
        }

        public void JoinLfg(Player player, LfgRoles roles, List<uint> dungeons, string comment)
        {
            if (!player || player.GetSession() == null || dungeons.Empty())
                return;

            Group grp = player.GetGroup();
            ObjectGuid guid = player.GetGUID();
            ObjectGuid gguid = grp ? grp.GetGUID() : guid;
            LfgJoinResultData joinData = new LfgJoinResultData();
            List<ObjectGuid> players = new List<ObjectGuid>();
            uint rDungeonId = 0;
            bool isContinue = grp && grp.isLFGGroup() && GetState(gguid) != LfgState.FinishedDungeon;

            // Do not allow to change dungeon in the middle of a current dungeon
            if (isContinue)
            {
                dungeons.Clear();
                dungeons.Add(GetDungeon(gguid));
            }

            // Already in queue?
            LfgState state = GetState(gguid);
            if (state == LfgState.Queued)
            {
                LFGQueue queue = GetQueue(gguid);
                queue.RemoveFromQueue(gguid);
            }

            // Check player or group member restrictions
            if (!player.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder))
                joinData.result = LfgJoinResult.NotMeetReqs;
            else if (player.InBattleground() || player.InArena() || player.InBattlegroundQueue())
                joinData.result = LfgJoinResult.UsingBgSystem;
            else if (player.HasAura(SharedConst.LFGSpellDungeonDeserter))
                joinData.result = LfgJoinResult.Deserter;
            else if (player.HasAura(SharedConst.LFGSpellDungeonCooldown))
                joinData.result = LfgJoinResult.RandomCooldown;
            else if (dungeons.Empty())
                joinData.result = LfgJoinResult.NotMeetReqs;
            else if (player.HasAura(9454)) // check Freeze debuff
                joinData.result = LfgJoinResult.NotMeetReqs;
            else if (grp)
            {
                if (grp.GetMembersCount() > MapConst.MaxGroupSize)
                    joinData.result = LfgJoinResult.TooMuchMembers;
                else
                {
                    byte memberCount = 0;
                    for (GroupReference refe = grp.GetFirstMember(); refe != null && joinData.result == LfgJoinResult.Ok; refe = refe.next())
                    {
                        Player plrg = refe.GetSource();
                        if (plrg)
                        {
                            if (!plrg.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder))
                                joinData.result = LfgJoinResult.InternalError;
                            if (plrg.HasAura(SharedConst.LFGSpellDungeonDeserter))
                                joinData.result = LfgJoinResult.PartyDeserter;
                            else if (plrg.HasAura(SharedConst.LFGSpellDungeonCooldown))
                                joinData.result = LfgJoinResult.PartyRandomCooldown;
                            else if (plrg.InBattleground() || plrg.InArena() || plrg.InBattlegroundQueue())
                                joinData.result = LfgJoinResult.UsingBgSystem;
                            else if (plrg.HasAura(9454)) // check Freeze debuff
                                joinData.result = LfgJoinResult.PartyNotMeetReqs;
                            ++memberCount;
                            players.Add(plrg.GetGUID());
                        }
                    }

                    if (joinData.result == LfgJoinResult.Ok && memberCount != grp.GetMembersCount())
                        joinData.result = LfgJoinResult.Disconnected;
                }
            }
            else
                players.Add(player.GetGUID());

            // Check if all dungeons are valid
            bool isRaid = false;
            if (joinData.result == LfgJoinResult.Ok)
            {
                bool isDungeon = false;
                foreach (var it in dungeons)
                {
                    if (joinData.result != LfgJoinResult.Ok)
                        break;

                    LfgType type = (LfgType)(it >> 24);
                    switch (type)
                    {
                        case LfgType.RandomDungeon:
                            if (dungeons.Count > 1)               // Only allow 1 random dungeon
                                joinData.result = LfgJoinResult.DungeonInvalid;
                            else
                                rDungeonId = dungeons.First();
                            goto case LfgType.Dungeon;
                        case LfgType.Dungeon:
                            if (isRaid)
                                joinData.result = LfgJoinResult.MixedRaidDungeon;
                            isDungeon = true;
                            break;
                        case LfgType.Raid:
                            if (isDungeon)
                                joinData.result = LfgJoinResult.MixedRaidDungeon;
                            isRaid = true;
                            break;
                        default:
                            Log.outError(LogFilter.Lfg, "Wrong dungeon type {0} for dungeon {1}", type, it);
                            joinData.result = LfgJoinResult.DungeonInvalid;
                            break;
                    }
                }

                // it could be changed
                if (joinData.result == LfgJoinResult.Ok)
                {
                    // Expand random dungeons and check restrictions
                    if (rDungeonId != 0)
                        dungeons = GetDungeonsByRandom(rDungeonId);

                    // if we have lockmap then there are no compatible dungeons
                    GetCompatibleDungeons(dungeons, players, joinData.lockmap, isContinue);
                    if (dungeons.Empty())
                        joinData.result = grp ? LfgJoinResult.InternalError : LfgJoinResult.NotMeetReqs;
                }
            }

            // Can't join. Send result
            if (joinData.result != LfgJoinResult.Ok)
            {
                Log.outDebug(LogFilter.Lfg, "Join: [{0}] joining with {1} members. result: {2}", guid, grp ? grp.GetMembersCount() : 1, joinData.result);
                if (!dungeons.Empty())                             // Only should show lockmap when have no dungeons available
                    joinData.lockmap.Clear();
                player.GetSession().SendLfgJoinResult(joinData);
                return;
            }

            SetComment(guid, comment);

            if (isRaid)
            {
                Log.outDebug(LogFilter.Lfg, "Join: [{0}] trying to join raid browser and it's disabled.", guid);
                return;
            }

            string debugNames = "";
            if (grp)                                               // Begin rolecheck
            {
                // Create new rolecheck
                LfgRoleCheck roleCheck = new LfgRoleCheck();
                roleCheck.cancelTime = Time.UnixTime + SharedConst.LFGTimeRolecheck;
                roleCheck.state = LfgRoleCheckState.Initialiting;
                roleCheck.leader = guid;
                roleCheck.dungeons = dungeons;
                roleCheck.rDungeonId = rDungeonId;

                RoleChecksStore[gguid] = roleCheck;

                if (rDungeonId != 0)
                {
                    dungeons.Clear();
                    dungeons.Add(rDungeonId);
                }

                SetState(gguid, LfgState.Rolecheck);
                // Send update to player
                LfgUpdateData updateData = new LfgUpdateData(LfgUpdateType.JoinQueue, dungeons, comment);
                for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next())
                {
                    Player plrg = refe.GetSource();
                    if (plrg)
                    {
                        ObjectGuid pguid = plrg.GetGUID();
                        plrg.GetSession().SendLfgUpdateStatus(updateData, true);
                        SetState(pguid, LfgState.Rolecheck);
                        if (!isContinue)
                            SetSelectedDungeons(pguid, dungeons);
                        roleCheck.roles[pguid] = 0;
                        if (!string.IsNullOrEmpty(debugNames))
                            debugNames += ", ";
                        debugNames += plrg.GetName();
                    }
                }
                // Update leader role
                UpdateRoleCheck(gguid, guid, roles);
            }
            else                                                   // Add player to queue
            {
                Dictionary<ObjectGuid, LfgRoles> rolesMap = new Dictionary<ObjectGuid, LfgRoles>();
                rolesMap[guid] = roles;
                LFGQueue queue = GetQueue(guid);
                queue.AddQueueData(guid, Time.UnixTime, dungeons, rolesMap);

                if (!isContinue)
                {
                    if (rDungeonId != 0)
                    {
                        dungeons.Clear();
                        dungeons.Add(rDungeonId);
                    }
                    SetSelectedDungeons(guid, dungeons);
                }
                // Send update to player
                SetRoles(guid, roles);
                player.GetSession().SendLfgUpdateStatus(new LfgUpdateData(LfgUpdateType.JoinQueue, dungeons, comment), false);
                player.GetSession().SendLfgJoinResult(joinData);
                SetState(gguid, LfgState.Queued);
                debugNames += player.GetName();
            }
            StringBuilder o = new StringBuilder();
            o.AppendFormat("Join: [{0}] joined ({1}{2}) Members: {3}. Dungeons ({4}): ", guid, (grp ? "group" : "player"), debugNames, dungeons.Count, ConcatenateDungeons(dungeons));
            Log.outDebug(LogFilter.Lfg, o.ToString());
        }

        public void LeaveLfg(ObjectGuid guid, bool disconnected = false)
        {
            Log.outDebug(LogFilter.Lfg, "LeaveLfg: [{0}]", guid);

            ObjectGuid gguid = guid.IsParty() ? guid : GetGroup(guid);
            LfgState state = GetState(guid);
            switch (state)
            {
                case LfgState.Queued:
                    if (!gguid.IsEmpty())
                    {
                        LFGQueue queue = GetQueue(gguid);
                        queue.RemoveFromQueue(gguid);
                        SetState(gguid, LfgState.None);
                        List<ObjectGuid> players = GetPlayers(gguid);
                        foreach (var it in players)
                        {
                            SetState(it, LfgState.None);
                            SendLfgUpdateStatus(it, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), true);
                        }
                    }
                    else
                    {
                        SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), false);
                        LFGQueue queue = GetQueue(guid);
                        queue.RemoveFromQueue(guid);
                        SetState(guid, LfgState.None);
                    }
                    break;
                case LfgState.Rolecheck:
                    if (!gguid.IsEmpty())
                        UpdateRoleCheck(gguid);                    // No player to update role = LFG_ROLECHECK_ABORTED
                    break;
                case LfgState.Proposal:
                    {
                        // Remove from Proposals
                        KeyValuePair<uint, LfgProposal> it = new KeyValuePair<uint, LfgProposal>();
                        ObjectGuid pguid = gguid == guid ? GetLeader(gguid) : guid;
                        foreach (var test in ProposalsStore)
                        {
                            it = test;
                            var itPlayer = it.Value.players.LookupByKey(pguid);
                            if (itPlayer != null)
                            {
                                // Mark the player/leader of group who left as didn't accept the proposal
                                itPlayer.accept = LfgAnswer.Deny;
                                break;
                            }
                        }

                        // Remove from queue - if proposal is found, RemoveProposal will call RemoveFromQueue
                        if (it.Value != null)
                            RemoveProposal(it, LfgUpdateType.ProposalDeclined);
                        break;
                    }
                case LfgState.None:
                case LfgState.Raidbrowser:
                    break;
                case LfgState.Dungeon:
                case LfgState.FinishedDungeon:
                    if (guid != gguid && !disconnected) // Player
                        SetState(guid, LfgState.None);
                    break;
            }
        }

        public void UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid = default(ObjectGuid), LfgRoles roles = LfgRoles.None)
        {
            if (gguid.IsEmpty())
                return;

            Dictionary<ObjectGuid, LfgRoles> check_roles;
            var roleCheck = RoleChecksStore.LookupByKey(gguid);
            if (roleCheck == null)
                return;

            bool sendRoleChosen = roleCheck.state != LfgRoleCheckState.Default && !guid.IsEmpty();

            if (guid.IsEmpty())
                roleCheck.state = LfgRoleCheckState.Aborted;
            else if (roles < LfgRoles.Tank)                            // Player selected no role.
                roleCheck.state = LfgRoleCheckState.NoRole;
            else
            {
                roleCheck.roles[guid] = roles;

                // Check if all players have selected a role
                bool done = false;
                foreach (var rolePair in roleCheck.roles)
                {
                    if (rolePair.Value != LfgRoles.None)
                        continue;
                    done = true;
                }

                if (done)
                {
                    // use temporal var to check roles, CheckGroupRoles modifies the roles
                    check_roles = roleCheck.roles;
                    roleCheck.state = CheckGroupRoles(check_roles) ? LfgRoleCheckState.Finished : LfgRoleCheckState.WrongRoles;
                }
            }

            List<uint> dungeons = new List<uint>();
            if (roleCheck.rDungeonId != 0)
                dungeons.Add(roleCheck.rDungeonId);
            else
                dungeons = roleCheck.dungeons;

            LfgJoinResult joinResult = LfgJoinResult.Failed;
            if (roleCheck.state == LfgRoleCheckState.MissingRole || roleCheck.state == LfgRoleCheckState.WrongRoles)
                joinResult = LfgJoinResult.RoleCheckFailed;

            LfgJoinResultData joinData = new LfgJoinResultData(joinResult, roleCheck.state);
            foreach (var it in roleCheck.roles)
            {
                ObjectGuid pguid = it.Key;

                if (sendRoleChosen)
                    SendLfgRoleChosen(pguid, guid, roles);

                SendLfgRoleCheckUpdate(pguid, roleCheck);
                switch (roleCheck.state)
                {
                    case LfgRoleCheckState.Initialiting:
                        continue;
                    case LfgRoleCheckState.Finished:
                        SetState(pguid, LfgState.Queued);
                        SetRoles(pguid, it.Value);
                        SendLfgUpdateStatus(pguid, new LfgUpdateData(LfgUpdateType.AddedToQueue, dungeons, GetComment(pguid)), true);
                        break;
                    default:
                        if (roleCheck.leader == pguid)
                            SendLfgJoinResult(pguid, joinData);
                        SendLfgUpdateStatus(pguid, new LfgUpdateData(LfgUpdateType.RolecheckFailed), true);
                        RestoreState(pguid, "Rolecheck Failed");
                        break;
                }
            }

            if (roleCheck.state == LfgRoleCheckState.Finished)
            {
                SetState(gguid, LfgState.Queued);
                LFGQueue queue = GetQueue(gguid);
                queue.AddQueueData(gguid, Time.UnixTime, roleCheck.dungeons, roleCheck.roles);
                RoleChecksStore.Remove(gguid);
            }
            else if (roleCheck.state != LfgRoleCheckState.Initialiting)
            {
                RestoreState(gguid, "Rolecheck Failed");
                RoleChecksStore.Remove(gguid);
            }
        }

        void GetCompatibleDungeons(List<uint> dungeons, List<ObjectGuid> players, Dictionary<ObjectGuid, Dictionary<uint, LfgLockInfoData>> lockMap, bool isContinue)
        {
            lockMap.Clear();
            Dictionary<uint, uint> lockedDungeons = new Dictionary<uint, uint>();

            foreach (var guid in players)
            {
                if (dungeons.Empty())
                    break;

                var cachedLockMap = GetLockedDungeons(guid);
                Player player = Global.ObjAccessor.FindConnectedPlayer(guid);
                foreach (var it2 in cachedLockMap)
                {
                    if (dungeons.Empty())
                        break;

                    uint dungeonId = (it2.Key & 0x00FFFFFF); // Compare dungeon ids
                    if (dungeons.Contains(dungeonId))
                    {
                        bool eraseDungeon = true;

                        // Don't remove the dungeon if team members are trying to continue a locked instance
                        if (it2.Value.lockStatus == LfgLockStatusType.RaidLocked && isContinue)
                        {
                            LFGDungeonData dungeon = GetLFGDungeon(dungeonId);
                            Contract.Assert(dungeon != null);
                            Contract.Assert(player);
                            InstanceBind playerBind = player.GetBoundInstance(dungeon.map, dungeon.difficulty);
                            if (playerBind != null)
                            {
                                InstanceSave playerSave = playerBind.save;
                                if (playerSave != null)
                                {
                                    uint dungeonInstanceId = playerSave.GetInstanceId();
                                    var itLockedDungeon = lockedDungeons.LookupByKey(dungeonId);
                                    if (itLockedDungeon == 0 || itLockedDungeon == dungeonInstanceId)
                                        eraseDungeon = false;

                                    lockedDungeons[dungeonId] = dungeonInstanceId;
                                }
                            }
                        }

                        if (eraseDungeon)
                            dungeons.Remove(dungeonId);

                        if (!lockMap.ContainsKey(guid))
                            lockMap[guid] = new Dictionary<uint, LfgLockInfoData>();

                        lockMap[guid][it2.Key] = it2.Value;
                    }
                }
            }
            if (!dungeons.Empty())
                lockMap.Clear();
        }

        public bool CheckGroupRoles(Dictionary<ObjectGuid, LfgRoles> groles)
        {
            if (groles.Empty())
                return false;

            byte damage = 0;
            byte tank = 0;
            byte healer = 0;

            List<ObjectGuid> keys = new List<ObjectGuid>(groles.Keys);
            for (int i = 0; i < keys.Count; i++)
            {
                var it = groles[keys[i]];
                LfgRoles role = it & ~LfgRoles.Leader;
                if (role == LfgRoles.None)
                    return false;

                if (role.HasAnyFlag(LfgRoles.Damage))
                {
                    if (role != LfgRoles.Damage)
                    {
                        it -= (byte)LfgRoles.Damage;
                        if (CheckGroupRoles(groles))
                            return true;
                        it += (byte)LfgRoles.Damage;
                    }
                    else if (damage == SharedConst.LFGDPSNeeded)
                        return false;
                    else
                        damage++;
                }

                if (role.HasAnyFlag(LfgRoles.Healer))
                {
                    if (role != LfgRoles.Healer)
                    {
                        it -= (byte)LfgRoles.Healer;
                        if (CheckGroupRoles(groles))
                            return true;
                        it += (byte)LfgRoles.Healer;
                    }
                    else if (healer == SharedConst.LFGHealersNeeded)
                        return false;
                    else
                        healer++;
                }

                if (role.HasAnyFlag(LfgRoles.Tank))
                {
                    if (role != LfgRoles.Tank)
                    {
                        it -= (byte)LfgRoles.Tank;
                        if (CheckGroupRoles(groles))
                            return true;
                        it += (byte)LfgRoles.Tank;
                    }
                    else if (tank == SharedConst.LFGTanksNeeded)
                        return false;
                    else
                        tank++;
                }
            }
            return (tank + healer + damage) == (byte)groles.Count;
        }

        void MakeNewGroup(LfgProposal proposal)
        {
            List<ObjectGuid> players = new List<ObjectGuid>();
            List<ObjectGuid> playersToTeleport = new List<ObjectGuid>();

            foreach (var it in proposal.players)
            {
                ObjectGuid guid = it.Key;
                if (guid == proposal.leader)
                    players.Insert(0, guid);
                else
                    players.Add(guid);

                if (proposal.isNew || GetGroup(guid) != proposal.group)
                    playersToTeleport.Add(guid);
            }

            // Set the dungeon difficulty
            LFGDungeonData dungeon = GetLFGDungeon(proposal.dungeonId);
            Contract.Assert(dungeon != null);

            Group grp = !proposal.group.IsEmpty() ? Global.GroupMgr.GetGroupByGUID(proposal.group) : null;
            foreach (var pguid in players)
            {
                Player player = Global.ObjAccessor.FindConnectedPlayer(pguid);
                if (!player)
                    continue;

                Group group = player.GetGroup();
                if (group && group != grp)
                    group.RemoveMember(player.GetGUID());

                if (!grp)
                {
                    grp = new Group();
                    grp.ConvertToLFG();
                    grp.Create(player);
                    ObjectGuid gguid = grp.GetGUID();
                    SetState(gguid, LfgState.Proposal);
                    Global.GroupMgr.AddGroup(grp);
                }
                else if (group != grp)
                    grp.AddMember(player);

                grp.SetLfgRoles(pguid, proposal.players.LookupByKey(pguid).role);

                // Add the cooldown spell if queued for a random dungeon
                if (dungeon.type == LfgType.RandomDungeon)
                    player.CastSpell(player, SharedConst.LFGSpellDungeonCooldown, false);
            }

            grp.SetDungeonDifficultyID(dungeon.difficulty);
            ObjectGuid _guid = grp.GetGUID();
            SetDungeon(_guid, dungeon.Entry());
            SetState(_guid, LfgState.Dungeon);

            _SaveToDB(_guid, grp.GetDbStoreId());

            // Teleport Player
            foreach (var it in playersToTeleport)
            {
                Player player = Global.ObjAccessor.FindPlayer(it);
                if (player)
                    TeleportPlayer(player, false);
            }

            // Update group info
            grp.SendUpdate();
        }

        public uint AddProposal(LfgProposal proposal)
        {
            proposal.id = ++m_lfgProposalId;
            ProposalsStore[m_lfgProposalId] = proposal;
            return m_lfgProposalId;
        }

        public void UpdateProposal(uint proposalId, ObjectGuid guid, bool accept)
        {
            // Check if the proposal exists
            var proposal = ProposalsStore.LookupByKey(proposalId);
            if (proposal == null)
                return;

            // Check if proposal have the current player
            var player = proposal.players.LookupByKey(guid);
            if (player == null)
                return;

            player.accept = (LfgAnswer)Convert.ToInt32(accept);

            Log.outDebug(LogFilter.Lfg, "UpdateProposal: Player [{0}] of proposal {1} selected: {2}", guid, proposalId, accept);
            if (!accept)
            {
                RemoveProposal(new KeyValuePair<uint, LfgProposal>(proposalId, proposal), LfgUpdateType.ProposalDeclined);
                return;
            }

            // check if all have answered and reorder players (leader first)
            bool allAnswered = true;
            foreach (var itPlayers in proposal.players)
                if (itPlayers.Value.accept != LfgAnswer.Agree)   // No answer (-1) or not accepted (0)
                    allAnswered = false;

            if (!allAnswered)
            {
                foreach (var it in proposal.players)
                    SendLfgUpdateProposal(it.Key, proposal);

                return;
            }

            bool sendUpdate = proposal.state != LfgProposalState.Success;
            proposal.state = LfgProposalState.Success;
            long joinTime = Time.UnixTime;

            LFGQueue queue = GetQueue(guid);
            LfgUpdateData updateData = new LfgUpdateData(LfgUpdateType.GroupFound);
            foreach (var it in proposal.players)
            {
                ObjectGuid pguid = it.Key;
                ObjectGuid gguid = it.Value.group;
                uint dungeonId = GetSelectedDungeons(pguid).First();
                int waitTime = -1;
                if (sendUpdate)
                    SendLfgUpdateProposal(pguid, proposal);

                if (!gguid.IsEmpty())
                {
                    waitTime = (int)((joinTime - queue.GetJoinTime(gguid)) / Time.InMilliseconds);
                    SendLfgUpdateStatus(pguid, updateData, false);
                }
                else
                {
                    waitTime = (int)((joinTime - queue.GetJoinTime(pguid)) / Time.InMilliseconds);
                    SendLfgUpdateStatus(pguid, updateData, false);
                }
                updateData.updateType = LfgUpdateType.RemovedFromQueue;
                SendLfgUpdateStatus(pguid, updateData, true);
                SendLfgUpdateStatus(pguid, updateData, false);

                // Update timers
                LfgRoles role = GetRoles(pguid);
                role &= ~LfgRoles.Leader;
                switch (role)
                {
                    case LfgRoles.Damage:
                        queue.UpdateWaitTimeDps(waitTime, dungeonId);
                        break;
                    case LfgRoles.Healer:
                        queue.UpdateWaitTimeHealer(waitTime, dungeonId);
                        break;
                    case LfgRoles.Tank:
                        queue.UpdateWaitTimeTank(waitTime, dungeonId);
                        break;
                    default:
                        queue.UpdateWaitTimeAvg(waitTime, dungeonId);
                        break;
                }

                SetState(pguid, LfgState.Dungeon);
            }

            // Remove players/groups from Queue
            foreach (var it in proposal.queues)
                queue.RemoveFromQueue(it);

            MakeNewGroup(proposal);
            ProposalsStore.Remove(proposalId);
        }

        void RemoveProposal(KeyValuePair<uint, LfgProposal> itProposal, LfgUpdateType type)
        {
            LfgProposal proposal = itProposal.Value;
            proposal.state = LfgProposalState.Failed;

            Log.outDebug(LogFilter.Lfg, "RemoveProposal: Proposal {0}, state FAILED, UpdateType {1}", itProposal.Key, type);
            // Mark all people that didn't answered as no accept
            if (type == LfgUpdateType.ProposalFailed)
                foreach (var it in proposal.players)
                    if (it.Value.accept == LfgAnswer.Pending)
                        it.Value.accept = LfgAnswer.Deny;

            // Mark players/groups to be removed
            List<ObjectGuid> toRemove = new List<ObjectGuid>();
            foreach (var it in proposal.players)
            {
                if (it.Value.accept == LfgAnswer.Agree)
                    continue;

                ObjectGuid guid = !it.Value.group.IsEmpty() ? it.Value.group : it.Key;
                // Player didn't accept or still pending when no secs left
                if (it.Value.accept == LfgAnswer.Deny || type == LfgUpdateType.ProposalFailed)
                {
                    it.Value.accept = LfgAnswer.Deny;
                    toRemove.Add(guid);
                }
            }

            // Notify players
            foreach (var it in proposal.players)
            {
                ObjectGuid guid = it.Key;
                ObjectGuid gguid = !it.Value.group.IsEmpty() ? it.Value.group : guid;

                SendLfgUpdateProposal(guid, proposal);

                if (toRemove.Contains(gguid))         // Didn't accept or in same group that someone that didn't accept
                {
                    LfgUpdateData updateData = new LfgUpdateData();
                    if (it.Value.accept == LfgAnswer.Deny)
                    {
                        updateData.updateType = type;
                        Log.outDebug(LogFilter.Lfg, "RemoveProposal: [{0}] didn't accept. Removing from queue and compatible cache", guid);
                    }
                    else
                    {
                        updateData.updateType = LfgUpdateType.RemovedFromQueue;
                        Log.outDebug(LogFilter.Lfg, "RemoveProposal: [{0}] in same group that someone that didn't accept. Removing from queue and compatible cache", guid);
                    }

                    RestoreState(guid, "Proposal Fail (didn't accepted or in group with someone that didn't accept");
                    if (gguid != guid)
                    {
                        RestoreState(it.Value.group, "Proposal Fail (someone in group didn't accepted)");
                        SendLfgUpdateStatus(guid, updateData, true);
                    }
                    else
                        SendLfgUpdateStatus(guid, updateData, false);
                }
                else
                {
                    Log.outDebug(LogFilter.Lfg, "RemoveProposal: Readding [{0}] to queue.", guid);
                    SetState(guid, LfgState.Queued);
                    if (gguid != guid)
                    {
                        SetState(gguid, LfgState.Queued);
                        SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.AddedToQueue, GetSelectedDungeons(guid), GetComment(guid)), true);
                    }
                    else
                        SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.AddedToQueue, GetSelectedDungeons(guid), GetComment(guid)), false);
                }
            }

            LFGQueue queue = GetQueue(proposal.players.First().Key);
            // Remove players/groups from queue
            foreach (var guid in toRemove)
            {
                queue.RemoveFromQueue(guid);
                proposal.queues.Remove(guid);
            }

            // Readd to queue
            foreach (var guid in proposal.queues)
                queue.AddToQueue(guid, true);

            ProposalsStore.Remove(itProposal.Key);
        }

        public void InitBoot(ObjectGuid gguid, ObjectGuid kicker, ObjectGuid victim, string reason)
        {
            SetVoteKick(gguid, true);

            LfgPlayerBoot boot = BootsStore[gguid];
            boot.inProgress = true;
            boot.cancelTime = Time.UnixTime + SharedConst.LFGTimeBoot;
            boot.reason = reason;
            boot.victim = victim;

            List<ObjectGuid> players = GetPlayers(gguid);

            // Set votes
            foreach (var guid in players)
                boot.votes[guid] = LfgAnswer.Pending;

            boot.votes[victim] = LfgAnswer.Deny;                  // Victim auto vote NO
            boot.votes[kicker] = LfgAnswer.Agree;                 // Kicker auto vote YES

            // Notify players
            foreach (var it in players)
                SendLfgBootProposalUpdate(it, boot);
        }

        public void UpdateBoot(ObjectGuid guid, bool accept)
        {
            ObjectGuid gguid = GetGroup(guid);
            if (gguid.IsEmpty())
                return;

            var boot = BootsStore.LookupByKey(gguid);
            if (boot == null)
                return;

            if (boot.votes[guid] != LfgAnswer.Pending)    // Cheat check: Player can't vote twice
                return;

            boot.votes[guid] = (LfgAnswer)Convert.ToInt32(accept);

            byte votesNum = 0;
            byte agreeNum = 0;
            foreach (var itVotes in boot.votes)
            {
                if (itVotes.Value != LfgAnswer.Pending)
                {
                    ++votesNum;
                    if (itVotes.Value == LfgAnswer.Agree)
                        ++agreeNum;
                }
            }

            // if we don't have enough votes (agree or deny) do nothing
            if (agreeNum < SharedConst.LFGKickVotesNeeded && (votesNum - agreeNum) < SharedConst.LFGKickVotesNeeded)
                return;

            // Send update info to all players
            boot.inProgress = false;
            foreach (var itVotes in boot.votes)
            {
                ObjectGuid pguid = itVotes.Key;
                if (pguid != boot.victim)
                    SendLfgBootProposalUpdate(pguid, boot);
            }

            SetVoteKick(gguid, false);
            if (agreeNum == SharedConst.LFGKickVotesNeeded)           // Vote passed - Kick player
            {
                Group group = Global.GroupMgr.GetGroupByGUID(gguid);
                if (group)
                    Player.RemoveFromGroup(group, boot.victim, RemoveMethod.KickLFG);
                DecreaseKicksLeft(gguid);
            }
            BootsStore.Remove(gguid);
        }

        public void TeleportPlayer(Player player, bool outt, bool fromOpcode = false)
        {
            LFGDungeonData dungeon = null;
            Group group = player.GetGroup();

            if (group && group.isLFGGroup())
                dungeon = GetLFGDungeon(GetDungeon(group.GetGUID()));

            if (dungeon == null)
            {
                Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} not in group/lfggroup or dungeon not found!", player.GetName());
                player.GetSession().SendLfgTeleportError(LfgTeleportResult.InvalidLocation);
                return;
            }

            if (outt)
            {
                Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} is being teleported out. Current Map {1} - Expected Map {2}", player.GetName(), player.GetMapId(), dungeon.map);
                if (player.GetMapId() == dungeon.map)
                    player.TeleportToBGEntryPoint();

                return;
            }

            LfgTeleportResult error = LfgTeleportResult.Ok;
            if (!player.IsAlive())
                error = LfgTeleportResult.PlayerDead;
            else if (player.IsFalling() || player.HasUnitState(UnitState.Jumping))
                error = LfgTeleportResult.Falling;
            else if (player.IsMirrorTimerActive(MirrorTimerType.Fatigue))
                error = LfgTeleportResult.Fatigue;
            else if (player.GetVehicle())
                error = LfgTeleportResult.InVehicle;
            else if (!player.GetCharmGUID().IsEmpty())
                error = LfgTeleportResult.Charming;
            else if (player.HasAura(9454)) // check Freeze debuff
                error = LfgTeleportResult.InvalidLocation;
            else if (player.GetMapId() != dungeon.map)  // Do not teleport players in dungeon to the entrance
            {
                uint mapid = dungeon.map;
                float x = dungeon.x;
                float y = dungeon.y;
                float z = dungeon.z;
                float orientation = dungeon.o;

                if (!fromOpcode)
                {
                    // Select a player inside to be teleported to
                    for (GroupReference refe = group.GetFirstMember(); refe != null && mapid == 0; refe = refe.next())
                    {
                        Player plrg = refe.GetSource();
                        if (plrg && plrg != player && plrg.GetMapId() == dungeon.map)
                        {
                            mapid = plrg.GetMapId();
                            x = plrg.GetPositionX();
                            y = plrg.GetPositionY();
                            z = plrg.GetPositionZ();
                            orientation = plrg.GetOrientation();
                            break;
                        }
                    }
                }

                if (!player.GetMap().IsDungeon())
                    player.SetBattlegroundEntryPoint();

                if (player.IsInFlight())
                {
                    player.GetMotionMaster().MovementExpired();
                    player.CleanupAfterTaxiFlight();
                }

                if (!player.TeleportTo(mapid, x, y, z, orientation))
                    error = LfgTeleportResult.InvalidLocation;
            }
            else
                error = LfgTeleportResult.InvalidLocation;

            if (error != LfgTeleportResult.Ok)
                player.GetSession().SendLfgTeleportError(error);

            Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} is being teleported in to map {1} (x: {2}, y: {3}, z: {4}) Result: {5}", player.GetName(), dungeon.map, dungeon.x, dungeon.y, dungeon.z, error);
        }

        public void FinishDungeon(ObjectGuid gguid, uint dungeonId)
        {
            uint gDungeonId = GetDungeon(gguid);
            if (gDungeonId != dungeonId)
            {
                Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Finished dungeon {1} but group queued for {2}. Ignoring", gguid, dungeonId, gDungeonId);
                return;
            }

            if (GetState(gguid) == LfgState.FinishedDungeon) // Shouldn't happen. Do not reward multiple times
            {
                Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Already rewarded group. Ignoring", gguid);
                return;
            }

            SetState(gguid, LfgState.FinishedDungeon);

            List<ObjectGuid> players = GetPlayers(gguid);
            foreach (var guid in players)
            {
                if (GetState(guid) == LfgState.FinishedDungeon)
                {
                    Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Already rewarded player. Ignoring", guid);
                    continue;
                }

                uint rDungeonId = 0;
                List<uint> dungeons = GetSelectedDungeons(guid);
                if (!dungeons.Empty())
                    rDungeonId = dungeons.First();

                SetState(guid, LfgState.FinishedDungeon);

                // Give rewards only if its a random dungeon
                LFGDungeonData dungeon = GetLFGDungeon(rDungeonId);

                if (dungeon == null || (dungeon.type != LfgType.RandomDungeon && !dungeon.seasonal))
                {
                    Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] dungeon {1} is not random or seasonal", guid, rDungeonId);
                    continue;
                }

                Player player = Global.ObjAccessor.FindPlayer(guid);
                if (!player || !player.IsInWorld)
                {
                    Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] not found in world", guid);
                    continue;
                }

                LFGDungeonData dungeonDone = GetLFGDungeon(dungeonId);
                uint mapId = dungeonDone != null ? dungeonDone.map : 0;

                if (player.GetMapId() != mapId)
                {
                    Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] is in map {1} and should be in {2} to get reward", guid, player.GetMapId(), mapId);
                    continue;
                }

                // Update achievements
                if (dungeon.difficulty == Difficulty.Heroic)
                    player.UpdateCriteria(CriteriaTypes.UseLfdToGroupWithPlayers, 1);

                LfgReward reward = GetRandomDungeonReward(rDungeonId, player.getLevel());
                if (reward == null)
                    continue;

                bool done = false;
                Quest quest = Global.ObjectMgr.GetQuestTemplate(reward.firstQuest);
                if (quest == null)
                    continue;

                // if we can take the quest, means that we haven't done this kind of "run", IE: First Heroic Random of Day.
                if (player.CanRewardQuest(quest, false))
                    player.RewardQuest(quest, 0, null, false);
                else
                {
                    done = true;
                    quest = Global.ObjectMgr.GetQuestTemplate(reward.otherQuest);
                    if (quest == null)
                        continue;
                    // we give reward without informing client (retail does this)
                    player.RewardQuest(quest, 0, null, false);
                }

                // Give rewards
                Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] done dungeon {1}, {2} previously done.", player.GetGUID(), GetDungeon(gguid), done ? " " : " not");
                LfgPlayerRewardData data = new LfgPlayerRewardData(dungeon.Entry(), GetDungeon(gguid, false), done, quest);
                player.GetSession().SendLfgPlayerReward(data);
            }
        }

        List<uint> GetDungeonsByRandom(uint randomdungeon)
        {
            LFGDungeonData dungeon = GetLFGDungeon(randomdungeon);
            byte group = (byte)(dungeon != null ? dungeon.group : 0);
            return CachedDungeonMapStore.LookupByKey(group);
        }

        public LfgReward GetRandomDungeonReward(uint dungeon, uint level)
        {
            LfgReward reward = null;
            var bounds = RewardMapStore.LookupByKey(dungeon & 0x00FFFFFF);
            foreach (var rew in bounds)
            {
                reward = rew;
                // ordered properly at loading
                if (rew.maxLevel >= level)
                    break;
            }

            return reward;
        }

        public LfgType GetDungeonType(uint dungeonId)
        {
            LFGDungeonData dungeon = GetLFGDungeon(dungeonId);
            if (dungeon == null)
                return LfgType.None;

            return dungeon.type;
        }

        public LfgState GetState(ObjectGuid guid)
        {
            LfgState state;
            if (guid.IsParty())
            {
                if (!GroupsStore.ContainsKey(guid))
                    return LfgState.None;

                state = GroupsStore[guid].GetState();
            }
            else
            {
                AddPlayerData(guid);
                state = PlayersStore[guid].GetState();
            }

            Log.outDebug(LogFilter.Lfg, "GetState: [{0}] = {1}", guid, state);
            return state;
        }

        public LfgState GetOldState(ObjectGuid guid)
        {
            LfgState state;
            if (guid.IsParty())
                state = GroupsStore[guid].GetOldState();
            else
            {
                AddPlayerData(guid);
                state = PlayersStore[guid].GetOldState();
            }

            Log.outDebug(LogFilter.Lfg, "GetOldState: [{0}] = {1}", guid, state);
            return state;
        }

        public bool IsVoteKickActive(ObjectGuid gguid)
        {
            Contract.Assert(gguid.IsParty());

            bool active = GroupsStore[gguid].IsVoteKickActive();
            Log.outInfo(LogFilter.Lfg, "Group: {0}, Active: {1}", gguid.ToString(), active);

            return active;
        }

        public uint GetDungeon(ObjectGuid guid, bool asId = true)
        {
            if (!GroupsStore.ContainsKey(guid))
                return 0;

            uint dungeon = GroupsStore[guid].GetDungeon(asId);
            Log.outDebug(LogFilter.Lfg, "GetDungeon: [{0}] asId: {1} = {2}", guid, asId, dungeon);
            return dungeon;
        }

        public uint GetDungeonMapId(ObjectGuid guid)
        {
            uint dungeonId = GroupsStore[guid].GetDungeon(true);
            uint mapId = 0;
            if (dungeonId != 0)
            {
                LFGDungeonData dungeon = GetLFGDungeon(dungeonId);
                if (dungeon != null)
                    mapId = dungeon.map;
            }

            Log.outError(LogFilter.Lfg, "GetDungeonMapId: [{0}] = {1} (DungeonId = {2})", guid, mapId, dungeonId);
            return mapId;
        }

        public LfgRoles GetRoles(ObjectGuid guid)
        {
            LfgRoles roles = PlayersStore[guid].GetRoles();
            Log.outDebug(LogFilter.Lfg, "GetRoles: [{0}] = {1}", guid, roles);
            return roles;
        }

        public string GetComment(ObjectGuid guid)
        {
            Log.outDebug(LogFilter.Lfg, "GetComment: [{0}] = {1}", guid, PlayersStore[guid].GetComment());
            return PlayersStore[guid].GetComment();
        }

        public List<uint> GetSelectedDungeons(ObjectGuid guid)
        {
            Log.outDebug(LogFilter.Lfg, "GetSelectedDungeons: [{0}]", guid);
            return PlayersStore[guid].GetSelectedDungeons();
        }

        public Dictionary<uint, LfgLockInfoData> GetLockedDungeons(ObjectGuid guid)
        {
            Dictionary<uint, LfgLockInfoData> lockDic = new Dictionary<uint, LfgLockInfoData>();
            Player player = Global.ObjAccessor.FindConnectedPlayer(guid);
            if (!player)
            {
                Log.outWarn(LogFilter.Lfg, "{0} not ingame while retrieving his LockedDungeons.", guid.ToString());
                return lockDic;
            }

            uint level = player.getLevel();
            Expansion expansion = player.GetSession().GetExpansion();
            var dungeons = GetDungeonsByRandom(0);
            bool denyJoin = !player.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder);

            foreach (var it in dungeons)
            {
                LFGDungeonData dungeon = GetLFGDungeon(it);
                if (dungeon == null) // should never happen - We provide a list from sLFGDungeonStore
                    continue;

                LfgLockStatusType lockStatus = 0;
                AccessRequirement ar;
                if (denyJoin)
                    lockStatus = LfgLockStatusType.RaidLocked;
                else if (dungeon.expansion > (uint)expansion)
                    lockStatus = LfgLockStatusType.InsufficientExpansion;
                else if (Global.DisableMgr.IsDisabledFor(DisableType.Map, dungeon.map, player))
                    lockStatus = LfgLockStatusType.RaidLocked;
                else if (dungeon.difficulty > Difficulty.Normal && player.GetBoundInstance(dungeon.map, dungeon.difficulty) != null)
                    lockStatus = LfgLockStatusType.RaidLocked;
                else if (dungeon.minlevel > level)
                    lockStatus = LfgLockStatusType.TooLowLevel;
                else if (dungeon.maxlevel < level)
                    lockStatus = LfgLockStatusType.TooHighLevel;
                else if (dungeon.seasonal && !IsSeasonActive(dungeon.id))
                    lockStatus = LfgLockStatusType.NotInSeason;
                else if (dungeon.requiredItemLevel > player.GetAverageItemLevel())
                    lockStatus = LfgLockStatusType.TooLowGearScore;
                else if ((ar = Global.ObjectMgr.GetAccessRequirement(dungeon.map, dungeon.difficulty)) != null)
                {
                    if (ar.achievement != 0 && !player.HasAchieved(ar.achievement))
                        lockStatus = LfgLockStatusType.MissingAchievement;
                    else if (player.GetTeam() == Team.Alliance && ar.quest_A != 0 && !player.GetQuestRewardStatus(ar.quest_A))
                        lockStatus = LfgLockStatusType.QuestNotCompleted;
                    else if (player.GetTeam() == Team.Horde && ar.quest_H != 0 && !player.GetQuestRewardStatus(ar.quest_H))
                        lockStatus = LfgLockStatusType.QuestNotCompleted;
                    else
                        if (ar.item != 0)
                    {
                        if (!player.HasItemCount(ar.item) && (ar.item2 == 0 || !player.HasItemCount(ar.item2)))
                            lockStatus = LfgLockStatusType.MissingItem;
                    }
                    else if (ar.item2 != 0 && !player.HasItemCount(ar.item2))
                        lockStatus = LfgLockStatusType.MissingItem;
                }

                /* @todo VoA closed if WG is not under team control (LFG_LOCKSTATUS_RAID_LOCKED)
                lockData = LFG_LOCKSTATUS_TOO_HIGH_GEAR_SCORE;
                lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_LOW_LEVEL;
                lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_HIGH_LEVEL;
                */
                if (lockStatus == 0)
                {

                }
                if (lockStatus != 0)
                    lockDic[dungeon.Entry()] = new LfgLockInfoData(lockStatus, dungeon.requiredItemLevel, player.GetAverageItemLevel());
            }

            return lockDic;
        }

        public byte GetKicksLeft(ObjectGuid guid)
        {
            byte kicks = GroupsStore[guid].GetKicksLeft();
            Log.outDebug(LogFilter.Lfg, "GetKicksLeft: [{0}] = {1}", guid, kicks);
            return kicks;
        }

        void RestoreState(ObjectGuid guid, string debugMsg)
        {
            if (guid.IsParty())
            {
                var data = GroupsStore[guid];
                data.RestoreState();
            }
            else
            {
                var data = PlayersStore[guid];
                data.RestoreState();
            }
        }

        public void SetState(ObjectGuid guid, LfgState state)
        {
            if (guid.IsParty())
            {
                if (!GroupsStore.ContainsKey(guid))
                    GroupsStore[guid] = new LFGGroupData();
                var data = GroupsStore[guid];
                data.SetState(state);
            }
            else
            {
                var data = PlayersStore[guid];
                data.SetState(state);
            }
        }

        void SetVoteKick(ObjectGuid gguid, bool active)
        {
            Contract.Assert(gguid.IsParty());

            var data = GroupsStore[gguid];
            Log.outInfo(LogFilter.Lfg, "Group: {0}, New state: {1}, Previous: {2}", gguid.ToString(), active, data.IsVoteKickActive());

            data.SetVoteKick(active);
        }

        void SetDungeon(ObjectGuid guid, uint dungeon)
        {
            AddPlayerData(guid);
            Log.outDebug(LogFilter.Lfg, "SetDungeon: [{0}] dungeon {1}", guid, dungeon);
            GroupsStore[guid].SetDungeon(dungeon);
        }

        void SetRoles(ObjectGuid guid, LfgRoles roles)
        {
            AddPlayerData(guid);
            Log.outDebug(LogFilter.Lfg, "SetRoles: [{0}] roles: {1}", guid, roles);
            PlayersStore[guid].SetRoles(roles);
        }

        public void SetComment(ObjectGuid guid, string comment)
        {
            AddPlayerData(guid);
            Log.outDebug(LogFilter.Lfg, "SetComment: [{0}] comment: {1}", guid, comment);
            PlayersStore[guid].SetComment(comment);
        }

        public void SetSelectedDungeons(ObjectGuid guid, List<uint> dungeons)
        {
            AddPlayerData(guid);
            Log.outDebug(LogFilter.Lfg, "SetSelectedDungeons: [{0}] Dungeons: {1}", guid, ConcatenateDungeons(dungeons));
            PlayersStore[guid].SetSelectedDungeons(dungeons);
        }

        void DecreaseKicksLeft(ObjectGuid guid)
        {
            Log.outDebug(LogFilter.Lfg, "DecreaseKicksLeft: [{0}]", guid);
            GroupsStore[guid].DecreaseKicksLeft();
        }

        void AddPlayerData(ObjectGuid guid)
        {
            if (PlayersStore.ContainsKey(guid))
                return;

            PlayersStore[guid] = new LFGPlayerData();
        }

        void RemovePlayerData(ObjectGuid guid)
        {
            Log.outDebug(LogFilter.Lfg, "RemovePlayerData: [{0}]", guid);
            PlayersStore.Remove(guid);
        }

        public void RemoveGroupData(ObjectGuid guid)
        {
            Log.outDebug(LogFilter.Lfg, "RemoveGroupData: [{0}]", guid);
            var it = GroupsStore.LookupByKey(guid);
            if (it == null)
                return;

            LfgState state = GetState(guid);
            // If group is being formed after proposal success do nothing more
            List<ObjectGuid> players = it.GetPlayers();
            foreach (var _guid in players)
            {
                SetGroup(_guid, ObjectGuid.Empty);
                if (state != LfgState.Proposal)
                {
                    SetState(_guid, LfgState.None);
                    SendLfgUpdateStatus(_guid, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), true);
                }
            }
            GroupsStore.Remove(guid);
        }

        Team GetTeam(ObjectGuid guid)
        {
            return PlayersStore[guid].GetTeam();
        }

        public byte RemovePlayerFromGroup(ObjectGuid gguid, ObjectGuid guid)
        {
            return GroupsStore[gguid].RemovePlayer(guid);
        }

        public void AddPlayerToGroup(ObjectGuid gguid, ObjectGuid guid)
        {
            GroupsStore[gguid].AddPlayer(guid);
        }

        public void SetLeader(ObjectGuid gguid, ObjectGuid leader)
        {
            if (!GroupsStore.ContainsKey(gguid))
                GroupsStore[gguid] = new LFGGroupData();
            GroupsStore[gguid].SetLeader(leader);
        }

        public void SetTeam(ObjectGuid guid, Team team)
        {
            if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup))
                team = 0;

            PlayersStore[guid].SetTeam(team);
        }

        public ObjectGuid GetGroup(ObjectGuid guid)
        {
            AddPlayerData(guid);
            return PlayersStore[guid].GetGroup();
        }

        public void SetGroup(ObjectGuid guid, ObjectGuid group)
        {
            AddPlayerData(guid);
            PlayersStore[guid].SetGroup(group);
        }

        List<ObjectGuid> GetPlayers(ObjectGuid guid)
        {
            return GroupsStore[guid].GetPlayers();
        }

        public byte GetPlayerCount(ObjectGuid guid)
        {
            return GroupsStore[guid].GetPlayerCount();
        }

        public ObjectGuid GetLeader(ObjectGuid guid)
        {
            return GroupsStore[guid].GetLeader();
        }

        public bool HasIgnore(ObjectGuid guid1, ObjectGuid guid2)
        {
            Player plr1 = Global.ObjAccessor.FindPlayer(guid1);
            Player plr2 = Global.ObjAccessor.FindPlayer(guid2);
            return plr1 && plr2 && (plr1.GetSocial().HasIgnore(guid2) || plr2.GetSocial().HasIgnore(guid1));
        }

        public void SendLfgRoleChosen(ObjectGuid guid, ObjectGuid pguid, LfgRoles roles)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgRoleChosen(pguid, roles);
        }

        public void SendLfgRoleCheckUpdate(ObjectGuid guid, LfgRoleCheck roleCheck)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgRoleCheckUpdate(roleCheck);
        }

        public void SendLfgUpdateStatus(ObjectGuid guid, LfgUpdateData data, bool party)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgUpdateStatus(data, party);
        }

        public void SendLfgJoinResult(ObjectGuid guid, LfgJoinResultData data)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgJoinResult(data);
        }

        public void SendLfgBootProposalUpdate(ObjectGuid guid, LfgPlayerBoot boot)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgBootProposalUpdate(boot);
        }

        public void SendLfgUpdateProposal(ObjectGuid guid, LfgProposal proposal)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgProposalUpdate(proposal);
        }

        public void SendLfgQueueStatus(ObjectGuid guid, LfgQueueStatusData data)
        {
            Player player = Global.ObjAccessor.FindPlayer(guid);
            if (player)
                player.GetSession().SendLfgQueueStatus(data);
        }

        public bool IsLfgGroup(ObjectGuid guid)
        {
            return !guid.IsEmpty() && guid.IsParty() && GroupsStore[guid].IsLfgGroup();
        }

        public byte GetQueueId(ObjectGuid guid)
        {
            if (guid.IsParty())
            {
                List<ObjectGuid> players = GetPlayers(guid);
                ObjectGuid pguid = players.Empty() ? ObjectGuid.Empty : players.First();
                if (!pguid.IsEmpty())
                    return (byte)GetTeam(pguid);
            }

            return (byte)GetTeam(guid);
        }

        public LFGQueue GetQueue(ObjectGuid guid)
        {
            byte queueId = GetQueueId(guid);
            if (!QueuesStore.ContainsKey(queueId))
                QueuesStore[queueId] = new LFGQueue();

            return QueuesStore[queueId];
        }

        public bool AllQueued(List<ObjectGuid> check)
        {
            if (check.Empty())
                return false;

            foreach (var guid in check)
            {
                LfgState state = GetState(guid);
                if (state != LfgState.Queued)
                {
                    if (state != LfgState.Proposal)
                        Log.outDebug(LogFilter.Lfg, "Unexpected state found while trying to form new group. Guid: {0}, State: {1}", guid.ToString(), state);

                    return false;
                }
            }
            return true;
        }

        public long GetQueueJoinTime(ObjectGuid guid)
        {
            byte queueId = GetQueueId(guid);
            var lfgQueue = QueuesStore.LookupByKey(queueId);
            if (lfgQueue != null)
                return lfgQueue.GetJoinTime(guid);

            return 0;
        }

        // Only for debugging purposes
        public void Clean()
        {
            QueuesStore.Clear();
        }

        public bool isOptionEnabled(LfgOptions option)
        {
            return m_options.HasAnyFlag(option);
        }

        public LfgOptions GetOptions()
        {
            return m_options;
        }

        public void SetOptions(LfgOptions options)
        {
            m_options = options;
        }

        public LfgUpdateData GetLfgStatus(ObjectGuid guid)
        {
            var playerData = PlayersStore[guid];
            return new LfgUpdateData(LfgUpdateType.UpdateStatus, playerData.GetState(), playerData.GetSelectedDungeons());
        }

        bool IsSeasonActive(uint dungeonId)
        {
            switch (dungeonId)
            {
                case 285: // The Headless Horseman
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.HallowsEnd);
                case 286: // The Frost Lord Ahune
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.FireFestival);
                case 287: // Coren Direbrew
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.Brewfest);
                case 288: // The Crown Chemical Co.
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.LoveIsInTheAir);
                case 744: // Random Timewalking Dungeon (Burning Crusade)
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingOutlands);
                case 995: // Random Timewalking Dungeon (Wrath of the Lich King)
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingNorthrend);
                case 1146: // Random Timewalking Dungeon (Cataclysm)
                    return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingCataclysm);
            }
            return false;
        }

        public string DumpQueueInfo(bool full)
        {

            uint size = (uint)QueuesStore.Count;

            string str = "Number of Queues: " + size + "\n";
            foreach (var pair in QueuesStore)
            {
                string queued = pair.Value.DumpQueueInfo();
                string compatibles = pair.Value.DumpCompatibleInfo(full);
                str += queued + compatibles;
            }

            return str;
        }

        public void SetupGroupMember(ObjectGuid guid, ObjectGuid gguid)
        {
            List<uint> dungeons = new List<uint>();
            dungeons.Add(GetDungeon(gguid));
            SetSelectedDungeons(guid, dungeons);
            SetState(guid, GetState(gguid));
            SetGroup(guid, gguid);
            AddPlayerToGroup(gguid, guid);
        }

        public bool selectedRandomLfgDungeon(ObjectGuid guid)
        {
            if (GetState(guid) != LfgState.None)
            {
                List<uint> dungeons = GetSelectedDungeons(guid);
                if (!dungeons.Empty())
                {
                    LFGDungeonData dungeon = GetLFGDungeon(dungeons.First());
                    if (dungeon != null && (dungeon.type == LfgType.RandomDungeon || dungeon.seasonal))
                        return true;
                }
            }

            return false;
        }

        public bool inLfgDungeonMap(ObjectGuid guid, uint map, Difficulty difficulty)
        {
            if (!guid.IsParty())
                guid = GetGroup(guid);

            uint dungeonId = GetDungeon(guid, true);
            if (dungeonId != 0)
            {
                LFGDungeonData dungeon = GetLFGDungeon(dungeonId);
                if (dungeon != null)
                    if (dungeon.map == map && dungeon.difficulty == difficulty)
                        return true;
            }

            return false;
        }

        public uint GetLFGDungeonEntry(uint id)
        {
            if (id != 0)
            {
                LFGDungeonData dungeon = GetLFGDungeon(id);
                if (dungeon != null)
                    return dungeon.Entry();
            }

            return 0;
        }

        public List<uint> GetRandomAndSeasonalDungeons(uint level, uint expansion)
        {
            List<uint> randomDungeons = new List<uint>();
            foreach (var dungeon in LfgDungeonStore.Values)
            {
                if ((dungeon.seasonal && IsSeasonActive(dungeon.id) || !dungeon.seasonal && dungeon.type == LfgType.RandomDungeon) && dungeon.expansion <= expansion && dungeon.minlevel <= level && level <= dungeon.maxlevel)
                    randomDungeons.Add(dungeon.Entry());
            }
            return randomDungeons;
        }

        // General variables
        uint m_QueueTimer;     //< used to check interval of update
        uint m_lfgProposalId;  //< used as internal counter for proposals
        LfgOptions m_options;        //< Stores config options

        Dictionary<byte, LFGQueue> QueuesStore = new Dictionary<byte, LFGQueue>();                     //< Queues
        MultiMap<byte, uint> CachedDungeonMapStore = new MultiMap<byte, uint>(); //< Stores all dungeons by groupType
        // Reward System
        MultiMap<uint, LfgReward> RewardMapStore = new MultiMap<uint, LfgReward>();                    //< Stores rewards for random dungeons
        Dictionary<uint, LFGDungeonData> LfgDungeonStore = new Dictionary<uint, LFGDungeonData>();
        // Rolecheck - Proposal - Vote Kicks
        Dictionary<ObjectGuid, LfgRoleCheck> RoleChecksStore = new Dictionary<ObjectGuid, LfgRoleCheck>();       //< Current Role checks
        Dictionary<uint, LfgProposal> ProposalsStore = new Dictionary<uint, LfgProposal>();            //< Current Proposals
        Dictionary<ObjectGuid, LfgPlayerBoot> BootsStore = new Dictionary<ObjectGuid, LfgPlayerBoot>();          //< Current player kicks
        Dictionary<ObjectGuid, LFGPlayerData> PlayersStore = new Dictionary<ObjectGuid, LFGPlayerData>();        //< Player data
        Dictionary<ObjectGuid, LFGGroupData> GroupsStore = new Dictionary<ObjectGuid, LFGGroupData>();           //< Group data
    }

    public class LfgJoinResultData
    {
        public LfgJoinResultData(LfgJoinResult _result = LfgJoinResult.Ok, LfgRoleCheckState _state = LfgRoleCheckState.Default)
        {
            result = _result;
            state = _state;
        }

        public LfgJoinResult result;
        public LfgRoleCheckState state;
        public Dictionary<ObjectGuid, Dictionary<uint, LfgLockInfoData>> lockmap = new Dictionary<ObjectGuid, Dictionary<uint, LfgLockInfoData>>();
    }

    public class LfgUpdateData
    {
        public LfgUpdateData(LfgUpdateType _type = LfgUpdateType.Default)
        {
            updateType = _type;
            state = LfgState.None;
            comment = "";
        }
        public LfgUpdateData(LfgUpdateType _type, List<uint> _dungeons, string _comment)
        {
            updateType = _type;
            state = LfgState.None;
            dungeons = _dungeons;
            comment = _comment;
        }
        public LfgUpdateData(LfgUpdateType _type, LfgState _state, List<uint> _dungeons, string _comment = "")
        {
            updateType = _type;
            state = _state;
            dungeons = _dungeons;
            comment = _comment;
        }

        public LfgUpdateType updateType;
        public LfgState state;
        public List<uint> dungeons = new List<uint>();
        public string comment;
    }

    public class LfgQueueStatusData
    {
        public LfgQueueStatusData(byte _queueId = 0, uint _dungeonId = 0, long _joinTime = 0, int _waitTime = -1, int _waitTimeAvg = -1, int _waitTimeTank = -1, int _waitTimeHealer = -1,
            int _waitTimeDps = -1, uint _queuedTime = 0, byte _tanks = 0, byte _healers = 0, byte _dps = 0)
        {
            queueId = _queueId;
            dungeonId = _dungeonId;
            joinTime = _joinTime;
            waitTime = _waitTime;
            waitTimeAvg = _waitTimeAvg;
            waitTimeTank = _waitTimeTank;
            waitTimeHealer = _waitTimeHealer;
            waitTimeDps = _waitTimeDps;
            queuedTime = _queuedTime;
            tanks = _tanks;
            healers = _healers;
            dps = _dps;
        }

        public byte queueId;
        public uint dungeonId;
        public long joinTime;
        public int waitTime;
        public int waitTimeAvg;
        public int waitTimeTank;
        public int waitTimeHealer;
        public int waitTimeDps;
        public uint queuedTime;
        public byte tanks;
        public byte healers;
        public byte dps;
    }

    public class LfgPlayerRewardData
    {
        public LfgPlayerRewardData(uint random, uint current, bool _done, Quest _quest)
        {
            rdungeonEntry = random;
            sdungeonEntry = current;
            done = _done;
            quest = _quest;
        }

        public uint rdungeonEntry;
        public uint sdungeonEntry;
        public bool done;
        public Quest quest;
    }

    public class LfgReward
    {
        public LfgReward(uint _maxLevel = 0, uint _firstQuest = 0, uint _otherQuest = 0)
        {
            maxLevel = _maxLevel;
            firstQuest = _firstQuest;
            otherQuest = _otherQuest;
        }

        public uint maxLevel;
        public uint firstQuest;
        public uint otherQuest;
    }

    public class LfgProposalPlayer
    {
        public LfgProposalPlayer()
        {
            role = 0;
            accept = LfgAnswer.Pending;
            group = ObjectGuid.Empty;
        }

        public LfgRoles role;
        public LfgAnswer accept;
        public ObjectGuid group;
    }

    public class LfgProposal
    {
        public LfgProposal(uint dungeon = 0)
        {
            id = 0;
            dungeonId = dungeon;
            state = LfgProposalState.Initiating;
            group = ObjectGuid.Empty;
            leader = ObjectGuid.Empty;
            cancelTime = 0;
            encounters = 0;
            isNew = true;
        }

        public uint id;
        public uint dungeonId;
        public LfgProposalState state;
        public ObjectGuid group;
        public ObjectGuid leader;
        public long cancelTime;
        public uint encounters;
        public bool isNew;
        public List<ObjectGuid> queues = new List<ObjectGuid>();
        public List<ulong> showorder = new List<ulong>();
        public Dictionary<ObjectGuid, LfgProposalPlayer> players = new Dictionary<ObjectGuid, LfgProposalPlayer>();                  ///< Players data
    }

    public class LfgRoleCheck
    {
        public long cancelTime;
        public Dictionary<ObjectGuid, LfgRoles> roles = new Dictionary<ObjectGuid, LfgRoles>();
        public LfgRoleCheckState state;
        public List<uint> dungeons = new List<uint>();
        public uint rDungeonId;
        public ObjectGuid leader;
    }

    public class LfgPlayerBoot
    {
        public long cancelTime;
        public bool inProgress;
        public Dictionary<ObjectGuid, LfgAnswer> votes = new Dictionary<ObjectGuid, LfgAnswer>();
        public ObjectGuid victim;
        public string reason;
    }

    public class LFGDungeonData
    {
        public LFGDungeonData(LfgDungeonsRecord dbc)
        {
            id = dbc.Id;
            name = dbc.Name[Global.WorldMgr.GetDefaultDbcLocale()];
            map = (uint)dbc.MapID;
            type = dbc.Type;
            expansion = dbc.Expansion;
            group = dbc.GroupID;
            minlevel = dbc.MinLevel;
            maxlevel = dbc.MaxLevel;
            difficulty = dbc.DifficultyID;
            seasonal = dbc.Flags.HasAnyFlag(LfgFlags.Seasonal);
        }

        public uint id;
        public string name;
        public uint map;
        public LfgType type;
        public uint expansion;
        public uint group;
        public uint minlevel;
        public uint maxlevel;
        public Difficulty difficulty;
        public bool seasonal;
        public float x, y, z, o;
        public ushort requiredItemLevel;

        // Helpers
        public uint Entry() { return (uint)(id + ((int)type << 24)); }
    }

    public class LfgLockInfoData
    {
        public LfgLockInfoData(LfgLockStatusType _lockStatus = 0, ushort _requiredItemLevel = 0, float _currentItemLevel = 0)
        {
            lockStatus = _lockStatus;
            requiredItemLevel = _requiredItemLevel;
            currentItemLevel = _currentItemLevel;
        }

        public LfgLockStatusType lockStatus;
        public ushort requiredItemLevel;
        public float currentItemLevel;
    }
}