/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * 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 Affero 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/>.
 */

#include "InstanceScript.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "GameObjectAI.h"
#include "TemporarySummon.h"
#include "Cell.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "ObjectDefines.h"
#include "ObjectMgr.h"
#include "molten_core.h"

#include "ProgressionSystem.h"

MinionData const minionData[] =
        {
                { NPC_FIRESWORN,                DATA_GARR },
                { NPC_FLAMEWALKER,              DATA_GEHENNAS },
                { NPC_FLAMEWALKER_PROTECTOR,    DATA_LUCIFRON },
                { NPC_FLAMEWALKER_PRIEST,       DATA_SULFURON },
                { NPC_FLAMEWALKER_HEALER,       DATA_MAJORDOMO_EXECUTUS },
                { NPC_FLAMEWALKER_ELITE,        DATA_MAJORDOMO_EXECUTUS },
                { 0, 0 } // END
        };

struct MCBossObject
{
    uint32 bossId;
    uint32 runeId;
    uint32 circleId;
};

constexpr uint8 MAX_MC_LINKED_BOSS_OBJ = 7;
MCBossObject const linkedBossObjData[MAX_MC_LINKED_BOSS_OBJ]=
        {
                { DATA_MAGMADAR,    GO_RUNE_KRESS,      GO_CIRCLE_MAGMADAR  },
                { DATA_GEHENNAS,    GO_RUNE_MOHN,       GO_CIRCLE_GEHENNAS  },
                { DATA_GARR,        GO_RUNE_BLAZ,       GO_CIRCLE_GARR      },
                { DATA_SHAZZRAH,    GO_RUNE_MAZJ,       GO_CIRCLE_SHAZZRAH  },
                { DATA_GEDDON,      GO_RUNE_ZETH,       GO_CIRCLE_GEDDON    },
                { DATA_GOLEMAGG,    GO_RUNE_THERI,      GO_CIRCLE_GOLEMAGG  },
                { DATA_SULFURON,    GO_RUNE_KORO,       GO_CIRCLE_SULFURON  },
        };

constexpr uint8 SAY_SPAWN = 1;

class instance_molten_core_60_1_A : public InstanceMapScript
{
public:
    instance_molten_core_60_1_A() : InstanceMapScript(MCScriptName, 409) {}

    struct instance_molten_core_InstanceMapScript : public InstanceScript
    {
        instance_molten_core_InstanceMapScript(Map* map) : InstanceScript(map)
        {
            SetBossNumber(MAX_ENCOUNTER);
            LoadMinionData(minionData);
        }

        void OnPlayerEnter(Player* /*player*/) override
        {
            if (CheckMajordomoExecutus())
            {
                SummonMajordomoExecutus();
            }
        }

        void OnCreatureCreate(Creature* creature) override
        {
            switch (creature->GetEntry())
            {
                case NPC_GOLEMAGG_THE_INCINERATOR:
                {
                    _golemaggGUID = creature->GetGUID();
                    break;
                }
                case NPC_CORE_RAGER:
                {
                    _golemaggMinionsGUIDS.insert(creature->GetGUID());
                    break;
                }
                case NPC_MAJORDOMO_EXECUTUS:
                {
                    _majordomoExecutusGUID = creature->GetGUID();
                    break;
                }
                case NPC_GARR:
                {
                    _garrGUID = creature->GetGUID();
                    break;
                }
                case NPC_RAGNAROS:
                {
                    _ragnarosGUID = creature->GetGUID();
                    break;
                }
                case NPC_FIRESWORN:
                case NPC_FLAMEWALKER:
                case NPC_FLAMEWALKER_PROTECTOR:
                case NPC_FLAMEWALKER_PRIEST:
                case NPC_FLAMEWALKER_HEALER:
                case NPC_FLAMEWALKER_ELITE:
                {
                    AddMinion(creature, true);
                    break;
                }
            }
        }

        void OnCreatureRemove(Creature* creature) override
        {
            switch (creature->GetEntry())
            {
                case NPC_FIRESWORN:
                {
                    AddMinion(creature, false);
                    break;
                }
                case NPC_FLAMEWALKER:
                case NPC_FLAMEWALKER_PROTECTOR:
                case NPC_FLAMEWALKER_PRIEST:
                case NPC_FLAMEWALKER_HEALER:
                case NPC_FLAMEWALKER_ELITE:
                {
                    AddMinion(creature, false);
                    break;
                }
            }
        }

        void OnGameObjectCreate(GameObject* go) override
        {
            switch (go->GetEntry())
            {
                case GO_CACHE_OF_THE_FIRELORD:
                {
                    _cacheOfTheFirelordGUID = go->GetGUID();
                    break;
                }
                case GO_CIRCLE_GEDDON:
                case GO_CIRCLE_GARR:
                case GO_CIRCLE_GEHENNAS:
                case GO_CIRCLE_GOLEMAGG:
                case GO_CIRCLE_MAGMADAR:
                case GO_CIRCLE_SHAZZRAH:
                case GO_CIRCLE_SULFURON:
                {
                    for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i)
                    {
                        if (linkedBossObjData[i].circleId != go->GetEntry())
                        {
                            continue;
                        }

                        if (GetBossState(linkedBossObjData[i].bossId) == DONE)
                        {
                            go->DespawnOrUnsummon();
                        }
                        else
                        {
                            _circlesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID();
                        }
                    }

                    break;
                }
                case GO_RUNE_KRESS:
                case GO_RUNE_MOHN:
                case GO_RUNE_BLAZ:
                case GO_RUNE_MAZJ:
                case GO_RUNE_ZETH:
                case GO_RUNE_THERI:
                case GO_RUNE_KORO:
                {
                    for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i)
                    {
                        if (linkedBossObjData[i].runeId != go->GetEntry())
                        {
                            continue;
                        }

                        if (GetBossState(linkedBossObjData[i].bossId) == DONE)
                        {
                            go->SetGoState(GO_STATE_ACTIVE);
                            _runeStatus[go->GetEntry()] = true;
                        }
                        else
                        {
                            _runesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID();
                            _runeStatus[go->GetEntry()] = false;
                        }
                    }
                    break;
                }
                case GO_LAVA_STEAM:
                {
                    _lavaSteamGUID = go->GetGUID();
                    break;
                }
                case GO_LAVA_SPLASH:
                {
                    _lavaSplashGUID = go->GetGUID();
                    break;
                }
                case GO_LAVA_BURST:
                {
                    if (Creature* ragnaros = instance->GetCreature(_ragnarosGUID))
                    {
                        ragnaros->AI()->SetGUID(go->GetGUID(), GO_LAVA_BURST);
                    }
                    break;
                }
            }
        }

        ObjectGuid GetGuidData(uint32 type) const override
        {
            switch (type)
            {
                case DATA_GOLEMAGG:
                    return _golemaggGUID;
                case DATA_MAJORDOMO_EXECUTUS:
                    return _majordomoExecutusGUID;
                case DATA_GARR:
                    return _garrGUID;
                case DATA_LAVA_STEAM:
                    return _lavaSteamGUID;
                case DATA_LAVA_SPLASH:
                    return _lavaSplashGUID;
                case DATA_RAGNAROS:
                    return _ragnarosGUID;
            }

            return ObjectGuid::Empty;
        }

        bool SetBossState(uint32 bossId, EncounterState state) override
        {
            if (!InstanceScript::SetBossState(bossId, state))
            {
                return false;
            }

            if (bossId == DATA_MAJORDOMO_EXECUTUS && state == DONE)
            {
                DoRespawnGameObject(_cacheOfTheFirelordGUID, 7 * DAY);
            }
            else if (bossId == DATA_GOLEMAGG)
            {
                switch (state)
                {
                    case NOT_STARTED:
                    case FAIL:
                    {
                        if (!_golemaggMinionsGUIDS.empty())
                        {
                            for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS)
                            {
                                Creature* minion = instance->GetCreature(minionGuid);
                                if (minion && minion->isDead())
                                {
                                    minion->Respawn();
                                }
                            }
                        }
                        break;
                    }
                    case IN_PROGRESS:
                    {
                        if (!_golemaggMinionsGUIDS.empty())
                        {
                            for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS)
                            {
                                if (Creature* minion = instance->GetCreature(minionGuid))
                                {
                                    minion->AI()->DoZoneInCombat(nullptr, 150.0f);
                                }
                            }
                        }
                        break;
                    }
                    case DONE:
                    {
                        if (!_golemaggMinionsGUIDS.empty())
                        {
                            for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS)
                            {
                                if (Creature* minion = instance->GetCreature(minionGuid))
                                {
                                    minion->CastSpell(minion, SPELL_CORE_RAGER_QUIET_SUICIDE, true);
                                }
                            }
                            _golemaggMinionsGUIDS.clear();
                        }
                        break;
                    }
                    default:
                        break;
                }
            }
            // Perform needed checks for Majordomu
            if (bossId < DATA_MAJORDOMO_EXECUTUS && state == DONE)
            {
                if (GameObject* rune = instance->GetGameObject(_runesGUIDs[bossId]))
                {
                    if (sConfigMgr->GetOption<int>("IndividualProgression.MoltenCore.ManualRuneHandling", 1) == 1)
                    {
                        rune->SetGoState(GO_STATE_ACTIVE);
                        rune->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
                    }
                    else
                    {
                        if (GameObject* circle = instance->GetGameObject(_circlesGUIDs[bossId]))
                        {
                            circle->DespawnOrUnsummon();
                            _circlesGUIDs[bossId].Clear();
                        }

                        rune->SetGoState(GO_STATE_ACTIVE);

                        if (CheckMajordomoExecutus())
                        {
                            SummonMajordomoExecutus();
                        }
                    }

                    _runesGUIDs[bossId].Clear();
                }
            }

            return true;
        }

        void SetData(uint32 index, uint32 data) override
        {
            if (index == DATA_RUNE_STATUS)
            {
                _runeStatus[data] = true;
            }
        }

        void DoAction(int32 action) override
        {
            if (action == ACTION_RESET_GOLEMAGG_ENCOUNTER)
            {
                if (Creature* golemagg = instance->GetCreature(_golemaggGUID))
                {
                    golemagg->AI()->EnterEvadeMode();
                }

                if (!_golemaggMinionsGUIDS.empty())
                {
                    for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS)
                    {
                        if (Creature* minion = instance->GetCreature(minionGuid))
                        {
                            minion->AI()->EnterEvadeMode();
                        }
                    }
                }
            }
            else if (action == ACTION_CHECK_RUNES)
            {
                if (CheckMajordomoExecutus())
                {
                    SummonMajordomoExecutus();
                }
            }
        }

        void SummonMajordomoExecutus()
        {
            if (instance->GetCreature(_majordomoExecutusGUID))
            {
                return;
            }

            if (GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE)
            {
                if (Creature* creature = instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoSummonPos))
                {
                    creature->AI()->Talk(SAY_SPAWN);
                }
            }
            else
            {
                instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoRagnaros);
            }
        }

        bool CheckMajordomoExecutus() const
        {
            if (GetBossState(DATA_RAGNAROS) == DONE)
            {
                return false;
            }

            for (uint8 i = 0; i < DATA_MAJORDOMO_EXECUTUS; ++i)
            {
                if (i == DATA_LUCIFRON)
                {
                    continue;
                }

                if (GetBossState(i) != DONE)
                {
                    return false;
                }
            }

            // Prevent spawning if Ragnaros is present
            if (instance->GetCreature(_ragnarosGUID))
            {
                return false;
            }

            if (sConfigMgr->GetOption<int>("IndividualProgression.MoltenCore.ManualRuneHandling", 1) == 1)
            {
                return CheckFirelordRunes();
            }
			else
			{
                return true;
			}
        }

        bool CheckFirelordRunes() const
        {
            for (auto const& status : _runeStatus)
            {
                if (!status.second)
                {
                    return false;
                }
            }

            return true;
        }


    private:
        std::unordered_map<uint32 /*bossid*/, ObjectGuid /*circleGUID*/> _circlesGUIDs;
        std::unordered_map<uint32/*bossid*/, ObjectGuid/*runeGUID*/> _runesGUIDs;
        std::unordered_map<uint32 /*bossid*/, bool /*status*/> _runeStatus;

        // Golemagg encounter related
        ObjectGuid _golemaggGUID;
        GuidSet _golemaggMinionsGUIDS;

        // Ragnaros encounter related
        ObjectGuid _ragnarosGUID;
        ObjectGuid _lavaSteamGUID;
        ObjectGuid _lavaSplashGUID;

        ObjectGuid _majordomoExecutusGUID;
        ObjectGuid _cacheOfTheFirelordGUID;
        ObjectGuid _garrGUID;
        ObjectGuid _magmadarGUID;
    };

    InstanceScript* GetInstanceScript(InstanceMap* map) const override
    {
        return new instance_molten_core_InstanceMapScript(map);
    }
};

constexpr uint32 SPELL_AQUAL_QUINTESSENCE = 21358;

class go_firelord_rune : public GameObjectScript
{
public:
    go_firelord_rune() : GameObjectScript("go_firelord_rune") { }

    bool OnGossipHello(Player* player, GameObject* go) override
    {
        go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);

        if (GameObject* circle = go->GetLinkedTrap())
        {
            circle->DespawnOrUnsummon();
        }

        if (InstanceScript* instance = go->GetInstanceScript())
        {
            instance->SetData(DATA_RUNE_STATUS, go->GetEntry());
            instance->DoAction(ACTION_CHECK_RUNES);
        }

        if (uint32 cooldown = player->GetSpellCooldownDelay(SPELL_AQUAL_QUINTESSENCE))
        {
            int32 cooldownredux = sConfigMgr->GetOption<int>("IndividualProgression.MoltenCore.AqualEssenceCooldownReduction", 0);
            player->ModifySpellCooldown(SPELL_AQUAL_QUINTESSENCE, -(cooldownredux * MINUTE * IN_MILLISECONDS));
        }

        return true;
    }
};

void AddSC_instance_molten_core_60_1_A()
{
    new instance_molten_core_60_1_A();
    new go_firelord_rune();
}
