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

using Framework.Constants;
using Framework.Database;
using Game.DataStorage;
using Game.Groups;
using Game.Guilds;
using Game.Maps;
using Game.Network.Packets;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Game.Entities
{
    public partial class Player
    {
        public override void SetMap(Map map)
        {
            base.SetMap(map);
        }

        public Difficulty GetDifficultyID(MapRecord mapEntry)
        {
            if (!mapEntry.IsRaid())
                return m_dungeonDifficulty;

            MapDifficultyRecord defaultDifficulty = Global.DB2Mgr.GetDefaultMapDifficulty(mapEntry.Id);
            if (defaultDifficulty == null)
                return m_legacyRaidDifficulty;

            DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(defaultDifficulty.DifficultyID);
            if (difficulty == null || difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy))
                return m_legacyRaidDifficulty;

            return m_raidDifficulty;
        }
        public Difficulty GetDungeonDifficultyID() { return m_dungeonDifficulty; }
        public Difficulty GetRaidDifficultyID() { return m_raidDifficulty; }
        public Difficulty GetLegacyRaidDifficultyID() { return m_legacyRaidDifficulty; }
        public void SetDungeonDifficultyID(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; }
        public void SetRaidDifficultyID(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; }
        public void SetLegacyRaidDifficultyID(Difficulty raid_difficulty) { m_legacyRaidDifficulty = raid_difficulty; }

        public static Difficulty CheckLoadedDungeonDifficultyID(Difficulty difficulty)
        {
            DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
            if (difficultyEntry == null)
                return Difficulty.Normal;

            if (difficultyEntry.InstanceType != MapTypes.Instance)
                return Difficulty.Normal;

            if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect))
                return Difficulty.Normal;

            return difficulty;
        }
        public static Difficulty CheckLoadedRaidDifficultyID(Difficulty difficulty)
        {
            DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
            if (difficultyEntry == null)
                return Difficulty.NormalRaid;

            if (difficultyEntry.InstanceType != MapTypes.Raid)
                return Difficulty.NormalRaid;

            if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
                return Difficulty.NormalRaid;

            return difficulty;
        }
        public static Difficulty CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty)
        {
            DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
            if (difficultyEntry == null)
                return Difficulty.Raid10N;

            if (difficultyEntry.InstanceType != MapTypes.Raid)
                return Difficulty.Raid10N;

            if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || !difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
                return Difficulty.Raid10N;

            return difficulty;
        }

        public void SendRaidGroupOnlyMessage(RaidGroupReason reason, int delay)
        {
            RaidGroupOnly raidGroupOnly = new RaidGroupOnly();
            raidGroupOnly.Delay = delay;
            raidGroupOnly.Reason = reason;

            SendPacket(raidGroupOnly);
        }

        void UpdateArea(uint newArea)
        {
            // FFA_PVP flags are area and not zone id dependent
            // so apply them accordingly
            m_areaUpdateId = newArea;

            AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(newArea);
            pvpInfo.IsInFFAPvPArea = area != null && area.Flags[0].HasAnyFlag(AreaFlags.Arena);
            UpdatePvPState(true);

            UpdateAreaDependentAuras(newArea);
            UpdateAreaAndZonePhase();

            // previously this was in UpdateZone (but after UpdateArea) so nothing will break
            pvpInfo.IsInNoPvPArea = false;
            if (area != null && area.IsSanctuary())    // in sanctuary
            {
                SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);
                pvpInfo.IsInNoPvPArea = true;
                if (duel == null)
                    CombatStopWithPets();
            }
            else
                RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);

            AreaFlags areaRestFlag = (GetTeam() == Team.Alliance) ? AreaFlags.RestZoneAlliance : AreaFlags.RestZoneHorde;
            if (area != null && area.Flags[0].HasAnyFlag(areaRestFlag))
                _restMgr.SetRestFlag(RestFlag.FactionArea);
            else
                _restMgr.RemoveRestFlag(RestFlag.FactionArea);
        }

        public void UpdateZone(uint newZone, uint newArea)
        {
            if (m_zoneUpdateId != newZone)
            {
                Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
                Global.OutdoorPvPMgr.HandlePlayerEnterZone(this, newZone);
                Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
                Global.BattleFieldMgr.HandlePlayerEnterZone(this, newZone);
                SendInitWorldStates(newZone, newArea);              // only if really enters to new zone, not just area change, works strange...
                Guild guild = GetGuild();
                if (guild)
                    guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone);
            }

            // group update
            if (GetGroup())
            {
                SetGroupUpdateFlag(GroupUpdateFlags.Full);

                Pet pet = GetPet();
                if (pet)
                    pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Full);
            }

            m_zoneUpdateId = newZone;
            m_zoneUpdateTimer = 1 * Time.InMilliseconds;

            // zone changed, so area changed as well, update it
            UpdateArea(newArea);

            AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(newZone);
            if (zone == null)
                return;

            if (WorldConfig.GetBoolValue(WorldCfg.Weather) && !HasAuraType(AuraType.ForceWeather))
            {
                Weather weather = Global.WeatherMgr.FindWeather(newZone);
                if (weather != null)
                    weather.SendWeatherUpdateToPlayer(this);
                else if (Global.WeatherMgr.AddWeather(newZone) == null)
                {
                    // send fine weather packet to remove old zone's weather
                    Global.WeatherMgr.SendFineWeatherUpdateToPlayer(this);                    
                }
            }


            Global.ScriptMgr.OnPlayerUpdateZone(this, newZone, newArea);

            // in PvP, any not controlled zone (except zone.team == 6, default case)
            // in PvE, only opposition team capital
            switch ((AreaTeams)zone.FactionGroupMask)
            {
                case AreaTeams.Ally:
                    pvpInfo.IsInHostileArea = GetTeam() != Team.Alliance && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
                    break;
                case AreaTeams.Horde:
                    pvpInfo.IsInHostileArea = GetTeam() != Team.Horde && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
                    break;
                case AreaTeams.None:
                    // overwrite for Battlegrounds, maybe batter some zone flags but current known not 100% fit to this
                    pvpInfo.IsInHostileArea = Global.WorldMgr.IsPvPRealm() || InBattleground() || zone.Flags[0].HasAnyFlag(AreaFlags.Wintergrasp);
                    break;
                default:                                            // 6 in fact
                    pvpInfo.IsInHostileArea = false;
                    break;
            }

            // Treat players having a quest flagging for PvP as always in hostile area
            pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();

            if (zone.Flags[0].HasAnyFlag(AreaFlags.Capital))                     // Is in a capital city
            {
                if (!pvpInfo.IsInHostileArea || zone.IsSanctuary())
                    _restMgr.SetRestFlag(RestFlag.City);
                pvpInfo.IsInNoPvPArea = true;
            }
            else
                _restMgr.RemoveRestFlag(RestFlag.City);

            UpdatePvPState();

            // remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
            // if player resurrected at teleport this will be applied in resurrect code
            if (IsAlive())
                DestroyZoneLimitedItem(true, newZone);

            // check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
            AutoUnequipOffhandIfNeed();

            // recent client version not send leave/join channel packets for built-in local channels
            UpdateLocalChannels(newZone);

            UpdateZoneDependentAuras(newZone);

            UpdateAreaAndZonePhase();
        }

        public InstanceBind GetBoundInstance(uint mapid, Difficulty difficulty, bool withExpired = false)
        {
            // some instances only have one difficulty
            MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref difficulty);
            if (mapDiff == null)
                return null;

            var bind = m_boundInstances[(int)difficulty].LookupByKey(mapid);
            if (bind != null)
                if (bind.extendState != 0 || withExpired)
                    return bind;

            return null;
        }
        public Dictionary<uint, InstanceBind> GetBoundInstances(Difficulty difficulty) { return m_boundInstances[(int)difficulty]; }

        public InstanceSave GetInstanceSave(uint mapid)
        {
            MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
            InstanceBind pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry));
            InstanceSave pSave = pBind?.save;
            if (pBind == null || !pBind.perm)
            {
                Group group = GetGroup();
                if (group)
                {
                    InstanceBind groupBind = group.GetBoundInstance(GetDifficultyID(mapEntry), mapid);
                    if (groupBind != null)
                        pSave = groupBind.save;
                }
            }

            return pSave;
        }

        public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false)
        {
            var bound = m_boundInstances[(int)difficulty].LookupByKey(mapid);
            if (bound != null)
            {
                if (!unload)
                {
                    PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);

                    stmt.AddValue(0, GetGUID().GetCounter());
                    stmt.AddValue(1, bound.save.GetInstanceId());

                    DB.Characters.Execute(stmt);
                }

                if (bound.perm)
                    GetSession().SendCalendarRaidLockout(bound.save, false);

                bound.save.RemovePlayer(this);               // save can become invalid
                m_boundInstances[(int)difficulty].Remove(mapid);
            }
        }

        public void UnbindInstance(KeyValuePair<uint, InstanceBind> pair, Difficulty difficulty, bool unload)
        {
            if (pair.Value != null)
            {
                if (!unload)
                {
                    PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);

                    stmt.AddValue(0, GetGUID().GetCounter());
                    stmt.AddValue(1, pair.Value.save.GetInstanceId());

                    DB.Characters.Execute(stmt);
                }

                if (pair.Value.perm)
                    GetSession().SendCalendarRaidLockout(pair.Value.save, false);

                pair.Value.save.RemovePlayer(this);               // save can become invalid
                m_boundInstances[(int)difficulty].Remove(pair.Key);
            }
        }

        public InstanceBind BindToInstance(InstanceSave save, bool permanent, BindExtensionState extendState = BindExtensionState.Normal, bool load = false)
        {
            if (save != null)
            {
                InstanceBind bind = new InstanceBind();
                if (m_boundInstances[(int)save.GetDifficultyID()].ContainsKey(save.GetMapId()))
                    bind = m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()];

                if (extendState == BindExtensionState.Keep) // special flag, keep the player's current extend state when updating for new boss down
                {
                    if (save == bind.save)
                        extendState = bind.extendState;
                    else
                        extendState = BindExtensionState.Normal;
                }

                if (!load)
                {
                    PreparedStatement stmt;
                    if (bind.save != null)
                    {
                        // update the save when the group kills a boss
                        if (permanent != bind.perm || save != bind.save || extendState != bind.extendState)
                        {
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INSTANCE);

                            stmt.AddValue(0, save.GetInstanceId());
                            stmt.AddValue(1, permanent);
                            stmt.AddValue(2, extendState);
                            stmt.AddValue(3, GetGUID().GetCounter());
                            stmt.AddValue(4, bind.save.GetInstanceId());

                            DB.Characters.Execute(stmt);
                        }
                    }
                    else
                    {
                        stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_INSTANCE);
                        stmt.AddValue(0, GetGUID().GetCounter());
                        stmt.AddValue(1, save.GetInstanceId());
                        stmt.AddValue(2, permanent);
                        stmt.AddValue(3, extendState);
                        DB.Characters.Execute(stmt);
                    }
                }

                if (bind.save != save)
                {
                    if (bind.save != null)
                        bind.save.RemovePlayer(this);
                    save.AddPlayer(this);
                }

                if (permanent)
                    save.SetCanReset(false);

                bind.save = save;
                bind.perm = permanent;
                bind.extendState = extendState;
                if (!load)
                    Log.outDebug(LogFilter.Maps, "Player.BindToInstance: Player '{0}' ({1}) is now bound to map (ID: {2}, Instance {3}, Difficulty {4})", GetName(), GetGUID().ToString(), save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID());

                Global.ScriptMgr.OnPlayerBindToInstance(this, save.GetDifficultyID(), save.GetMapId(), permanent, extendState);

                m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = bind;
                return bind;
            }

            return null;
        }

        public void BindToInstance()
        {
            InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(_pendingBindId);
            if (mapSave == null) //it seems sometimes mapSave is NULL, but I did not check why
                return;

            InstanceSaveCreated data = new InstanceSaveCreated();
            data.Gm = IsGameMaster();
            SendPacket(data);
            if (!IsGameMaster())
            {
                BindToInstance(mapSave, true, BindExtensionState.Keep);
                GetSession().SendCalendarRaidLockout(mapSave, true);
            }
        }

        public void SetPendingBind(uint instanceId, uint bindTimer)
        {
            _pendingBindId = instanceId;
            _pendingBindTimer = bindTimer;
        }

        public void SendRaidInfo()
        {
            InstanceInfoPkt instanceInfo = new InstanceInfoPkt();

            long now = Time.UnixTime;
            for (byte i = 0; i < (int)Difficulty.Max; ++i)
            {
                foreach (var pair in m_boundInstances[i])
                {
                    InstanceBind bind = pair.Value;
                    if (bind.perm)
                    {
                        InstanceSave save = pair.Value.save;

                        InstanceLockInfos lockInfos;

                        lockInfos.InstanceID = save.GetInstanceId();
                        lockInfos.MapID = save.GetMapId();
                        lockInfos.DifficultyID = (uint)save.GetDifficultyID();
                        if (bind.extendState != BindExtensionState.Extended)
                            lockInfos.TimeRemaining = (int)(save.GetResetTime() - now);
                        else
                            lockInfos.TimeRemaining = (int)(Global.InstanceSaveMgr.GetSubsequentResetTime(save.GetMapId(), save.GetDifficultyID(), save.GetResetTime()) - now);

                        lockInfos.CompletedMask = 0;
                        Map map = Global.MapMgr.FindMap(save.GetMapId(), save.GetInstanceId());
                        if (map != null)
                        {
                            InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
                            if (instanceScript != null)
                                lockInfos.CompletedMask = instanceScript.GetCompletedEncounterMask();
                        }

                        lockInfos.Locked = bind.extendState != BindExtensionState.Expired;
                        lockInfos.Extended = bind.extendState == BindExtensionState.Extended;

                        instanceInfo.LockList.Add(lockInfos);
                    }
                }
            }

            SendPacket(instanceInfo);
        }

        public bool Satisfy(AccessRequirement ar, uint target_map, bool report = false)
        {
            if (!IsGameMaster() && ar != null)
            {
                byte LevelMin = 0;
                byte LevelMax = 0;

                MapRecord mapEntry = CliDB.MapStorage.LookupByKey(target_map);
                if (mapEntry == null)
                    return false;

                if (!WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreLevel))
                {
                    if (ar.levelMin != 0 && getLevel() < ar.levelMin)
                        LevelMin = ar.levelMin;
                    if (ar.levelMax != 0 && getLevel() > ar.levelMax)
                        LevelMax = ar.levelMax;
                }

                uint missingItem = 0;
                if (ar.item != 0)
                {
                    if (!HasItemCount(ar.item) &&
                        (ar.item2 == 0 || !HasItemCount(ar.item2)))
                        missingItem = ar.item;
                }
                else if (ar.item2 != 0 && !HasItemCount(ar.item2))
                    missingItem = ar.item2;

                if (Global.DisableMgr.IsDisabledFor(DisableType.Map, target_map, this))
                {
                    GetSession().SendNotification("{0}", Global.ObjectMgr.GetCypherString(CypherStrings.InstanceClosed));
                    return false;
                }

                uint missingQuest = 0;
                if (GetTeam() == Team.Alliance && ar.quest_A != 0 && !GetQuestRewardStatus(ar.quest_A))
                    missingQuest = ar.quest_A;
                else if (GetTeam() == Team.Horde && ar.quest_H != 0 && !GetQuestRewardStatus(ar.quest_H))
                    missingQuest = ar.quest_H;

                uint missingAchievement = 0;
                Player leader = this;
                ObjectGuid leaderGuid = GetGroup() != null ? GetGroup().GetLeaderGUID() : GetGUID();
                if (leaderGuid != GetGUID())
                    leader = Global.ObjAccessor.FindPlayer(leaderGuid);

                if (ar.achievement != 0)
                    if (leader == null || !leader.HasAchieved(ar.achievement))
                        missingAchievement = ar.achievement;

                Difficulty target_difficulty = GetDifficultyID(mapEntry);
                MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(target_map, ref target_difficulty);
                if (LevelMin != 0 || LevelMax != 0 || missingItem != 0 || missingQuest != 0 || missingAchievement != 0)
                {
                    if (report)
                    {
                        if (missingQuest != 0 && !string.IsNullOrEmpty(ar.questFailedText))
                            SendSysMessage("{0}", ar.questFailedText);
                        else if (mapDiff.Message_lang.HasString(Global.WorldMgr.GetDefaultDbcLocale())) // if (missingAchievement) covered by this case
                            SendTransferAborted(target_map, TransferAbortReason.Difficulty, (byte)target_difficulty);
                        else if (missingItem != 0)
                            GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequiredAndItem), LevelMin, Global.ObjectMgr.GetItemTemplate(missingItem).GetName());
                        else if (LevelMin != 0)
                            GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequired), LevelMin);
                    }
                    return false;
                }
            }
            return true;
        }

        bool IsInstanceLoginGameMasterException()
        {
            if (!CanBeGameMaster())
                return false;

            SendSysMessage(CypherStrings.InstanceLoginGamemasterException);
            return true;
        }

        public bool CheckInstanceValidity(bool isLogin)
        {
            // game masters' instances are always valid
            if (IsGameMaster())
                return true;

            // non-instances are always valid
            Map map = GetMap();
            if (!map || !map.IsDungeon())
                return true;

            // raid instances require the player to be in a raid group to be valid
            if (map.IsRaid() && !WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid))
                if (!GetGroup() || !GetGroup().isRaidGroup())
                    return false;

            Group group = GetGroup();
            if (group)
            {
                // check if player's group is bound to this instance
                InstanceBind bind = group.GetBoundInstance(map.GetDifficultyID(), map.GetId());
                if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
                    return false;

                var players = map.GetPlayers();
                if (!players.Empty())
                    foreach (var otherPlayer in players)
                    {
                        if (otherPlayer.IsGameMaster())
                            continue;
                        if (!otherPlayer.m_InstanceValid) // ignore players that currently have a homebind timer active
                            continue;
                        if (group != otherPlayer.GetGroup())
                            return false;
                    }
            }
            else
            {
                // instance is invalid if we are not grouped and there are other players
                if (map.GetPlayersCountExceptGMs() > 1)
                    return false;

                // check if the player is bound to this instance
                InstanceBind bind = GetBoundInstance(map.GetId(), map.GetDifficultyID());
                if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
                    return false;
            }

            return true;
        }

        public bool CheckInstanceCount(uint instanceId)
        {
            if (_instanceResetTimes.Count < WorldConfig.GetIntValue(WorldCfg.MaxInstancesPerHour))
                return true;
            return _instanceResetTimes.ContainsKey(instanceId);
        }

        public void AddInstanceEnterTime(uint instanceId, long enterTime)
        {
            if (!_instanceResetTimes.ContainsKey(instanceId))
                _instanceResetTimes.Add(instanceId, enterTime + Time.Hour);
        }

        public void SendDungeonDifficulty(int forcedDifficulty = -1)
        {
            DungeonDifficultySet dungeonDifficultySet = new DungeonDifficultySet();
            dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)GetDungeonDifficultyID() : forcedDifficulty;
            SendPacket(dungeonDifficultySet);
        }

        public void SendRaidDifficulty(bool legacy, int forcedDifficulty = -1)
        {
            RaidDifficultySet raidDifficultySet = new RaidDifficultySet();
            raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)(legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty;
            raidDifficultySet.Legacy = legacy;
            SendPacket(raidDifficultySet);
        }

        public void SendResetFailedNotify(uint mapid)
        {
            SendPacket(new ResetFailedNotify());
        }

        // Reset all solo instances and optionally send a message on success for each
        public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy)
        {
            // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN

            // we assume that when the difficulty changes, all instances that can be reset will be
            Difficulty diff = GetDungeonDifficultyID();
            if (isRaid)
            {
                if (!isLegacy)
                    diff = GetRaidDifficultyID();
                else
                    diff = GetLegacyRaidDifficultyID();
            }

            foreach (var pair in m_boundInstances[(int)diff].ToList())
            {
                InstanceSave p = pair.Value.save;
                MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key);
                if (entry == null || entry.IsRaid() != isRaid || !p.CanReset())
                    continue;

                if (method == InstanceResetMethod.All)
                {
                    // the "reset all instances" method can only reset normal maps
                    if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic)
                        continue;
                }

                // if the map is loaded, reset it
                Map map = Global.MapMgr.FindMap(p.GetMapId(), p.GetInstanceId());
                if (map != null && map.IsDungeon())
                    if (!map.ToInstanceMap().Reset(method))
                        continue;

                // since this is a solo instance there should not be any players inside
                if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty)
                    SendResetInstanceSuccess(p.GetMapId());

                p.DeleteFromDB();
                m_boundInstances[(int)diff].Remove(pair.Key);

                // the following should remove the instance save from the manager and delete it as well
                p.RemovePlayer(this);
            }
        }

        public void SendResetInstanceSuccess(uint MapId)
        {
            InstanceReset data = new InstanceReset();
            data.MapID = MapId;
            SendPacket(data);
        }

        public void SendResetInstanceFailed(ResetFailedReason reason, uint MapId)
        {
            InstanceResetFailed data = new InstanceResetFailed();
            data.MapID = MapId;
            data.ResetFailedReason = reason;
            SendPacket(data);
        }

        public void SendTransferAborted(uint mapid, TransferAbortReason reason, byte arg = 0)
        {
            TransferAborted transferAborted = new TransferAborted();
            transferAborted.MapID = mapid;
            transferAborted.Arg = arg;
            transferAborted.TransfertAbort = reason;
            SendPacket(transferAborted);
        }

        public void SendInstanceResetWarning(uint mapid, Difficulty difficulty, uint time, bool welcome)
        {
            // type of warning, based on the time remaining until reset
            InstanceResetWarningType type;
            if (welcome)
                type = InstanceResetWarningType.Welcome;
            else if (time > 21600)
                type = InstanceResetWarningType.Welcome;
            else if (time > 3600)
                type = InstanceResetWarningType.WarningHours;
            else if (time > 300)
                type = InstanceResetWarningType.WarningMin;
            else
                type = InstanceResetWarningType.WarningMinSoon;

            RaidInstanceMessage raidInstanceMessage = new RaidInstanceMessage();
            raidInstanceMessage.Type = type;
            raidInstanceMessage.MapID = mapid;
            raidInstanceMessage.DifficultyID = difficulty;

            InstanceBind bind = GetBoundInstance(mapid, difficulty);
            if (bind != null)
                raidInstanceMessage.Locked = bind.perm;
            else
                raidInstanceMessage.Locked = false;
            raidInstanceMessage.Extended = false;
            SendPacket(raidInstanceMessage);
        }

        public override void UpdateUnderwaterState(Map m, float x, float y, float z)
        {
            LiquidData liquid_status;
            ZLiquidStatus res = m.getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status);
            if (res == 0)
            {
                m_MirrorTimerFlags &= ~(PlayerUnderwaterState.InWater | PlayerUnderwaterState.InLava | PlayerUnderwaterState.InSlime | PlayerUnderwaterState.InDarkWater);
                if (_lastLiquid != null && _lastLiquid.SpellID != 0)
                    RemoveAurasDueToSpell(_lastLiquid.SpellID);

                _lastLiquid = null;
                return;
            }
            uint liqEntry = liquid_status.entry;
            if (liqEntry != 0)
            {
                LiquidTypeRecord liquid = CliDB.LiquidTypeStorage.LookupByKey(liqEntry);
                if (_lastLiquid != null && _lastLiquid.SpellID != 0 && _lastLiquid != liquid)
                    RemoveAurasDueToSpell(_lastLiquid.SpellID);

                if (liquid != null && liquid.SpellID != 0)
                {
                    if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater))
                    {
                        if (!HasAura(liquid.SpellID))
                            CastSpell(this, liquid.SpellID, true);
                    }
                    else
                        RemoveAurasDueToSpell(liquid.SpellID);
                }

                _lastLiquid = liquid;
            }
            else if (_lastLiquid != null && _lastLiquid.SpellID != 0)
            {
                RemoveAurasDueToSpell(_lastLiquid.SpellID);
                _lastLiquid = null;
            }


            // All liquids type - check under water position
            if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean | MapConst.MapLiquidTypeMagma | MapConst.MapLiquidTypeSlime))
            {
                if (res.HasAnyFlag(ZLiquidStatus.UnderWater))
                    m_MirrorTimerFlags |= PlayerUnderwaterState.InWater;
                else
                    m_MirrorTimerFlags &= ~PlayerUnderwaterState.InWater;
            }

            // Allow travel in dark water on taxi or transport
            if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeDarkWater) && !IsInFlight() && GetTransport() == null)
                m_MirrorTimerFlags |= PlayerUnderwaterState.InDarkWater;
            else
                m_MirrorTimerFlags &= ~PlayerUnderwaterState.InDarkWater;

            // in lava check, anywhere in lava level
            if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeMagma))
            {
                if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
                    m_MirrorTimerFlags |= PlayerUnderwaterState.InLava;
                else
                    m_MirrorTimerFlags &= ~PlayerUnderwaterState.InLava;
            }
            // in slime check, anywhere in slime level
            if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeSlime))
            {
                if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
                    m_MirrorTimerFlags |= PlayerUnderwaterState.InSlime;
                else
                    m_MirrorTimerFlags &= ~PlayerUnderwaterState.InSlime;
            }
        }
    }
}
