/*
 * Originally written by Xinef - Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-AGPL
*/

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "InstanceScript.h"
#include "stratholme.h"
#include "Player.h"

const Position BlackGuardPos[10] = 
{
    {4032.73f+0.0f, -3378.26f+0.0f, 119.76f, 4.67f},
    {4032.73f+2.0f, -3378.26f+2.0f, 119.76f, 4.67f},
    {4032.73f+2.0f, -3378.26f-2.0f, 119.76f, 4.67f},
    {4032.73f-2.0f, -3378.26f+2.0f, 119.76f, 4.67f},
    {4032.73f-2.0f, -3378.26f-2.0f, 119.76f, 4.67f},
    
    {4032.73f+0.0f, -3407.38f+0.0f, 115.56f, 0.0f},
    {4032.73f+2.0f, -3407.38f+2.0f, 115.56f, 0.0f},
    {4032.73f+2.0f, -3407.38f-2.0f, 115.56f, 0.0f},
    {4032.73f-2.0f, -3407.38f+2.0f, 115.56f, 0.0f},
    {4032.73f-2.0f, -3407.38f-2.0f, 115.56f, 0.0f}
};

class instance_stratholme : public InstanceMapScript
{
    public:
        instance_stratholme() : InstanceMapScript("instance_stratholme", 329) { }

        struct instance_stratholme_InstanceMapScript : public InstanceScript
        {
            instance_stratholme_InstanceMapScript(Map* map) : InstanceScript(map)
            {
            }

            void Initialize()
            {
                _baronRunProgress = 0;
                _baronRunTime = 0;
                _zigguratState1 = 0;
                _zigguratState2 = 0;
                _zigguratState3 = 0;
                _slaughterProgress = 0;
                _slaughterNPCs = 0;
                _postboxesOpened = 0;

                _zigguratDoorsGUID1 = 0;
                _zigguratDoorsGUID2 = 0;
                _zigguratDoorsGUID3 = 0;
                _zigguratDoorsGUID4 = 0;
                _zigguratDoorsGUID5 = 0;
                _gauntletGateGUID = 0;
                _slaughterGateGUID = 0;
                _baronRivendareGUID = 0;

                events.Reset();
            }

            void OnPlayerEnter(Player* player)
            {
                if (_baronRunTime > 0)
                    if (Aura* aura = player->AddAura(SPELL_BARON_ULTIMATUM, player))
                        aura->SetDuration(_baronRunTime*MINUTE*IN_MILLISECONDS);
            }

            void OnCreatureCreate(Creature* creature)
            {
                switch (creature->GetEntry())
                {
                    case NPC_BARON_RIVENDARE:
                        _baronRivendareGUID = creature->GetGUID();
                        break;
                    case NPC_VENOM_BELCHER:
                    case NPC_BILE_SPEWER:
                        if (_slaughterProgress == 0)
                            ++_slaughterNPCs;
                        break;
                    case NPC_RAMSTEIN_THE_GORGER:
                        if (_slaughterProgress == 1)
                            ++_slaughterNPCs;
                        break;
                    case NPC_MINDLESS_UNDEAD:
                        if (_slaughterProgress == 2)
                            ++_slaughterNPCs;
                        break;
                    case NPC_BLACK_GUARD:
                        if (_slaughterProgress == 3)
                            ++_slaughterNPCs;
                        break;
                }
            }

            void ProcessSlaughterEvent()
            {
                if (_slaughterProgress == 1)
                {
                    if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
                        baron->AI()->Talk(SAY_BRAON_SUMMON_RAMSTEIN);

                    Position pos = {4032.20f, -3378.06f, 119.75f, 4.67f};
                    instance->SummonCreature(NPC_RAMSTEIN_THE_GORGER, pos);
                }
                if (_slaughterProgress == 2)
                {
                    for (uint32 i = 0; i < 33; ++i)
                        events.ScheduleEvent(EVENT_SPAWN_MINDLESS, 5000 + i*210);
                    if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
                        if (GameObject* gate = baron->FindNearestGameObject(GO_SLAUGHTER_GATE_SIDE, 200.0f))
                            gate->SetGoState(GO_STATE_ACTIVE);
                }
                if (_slaughterProgress == 3)
                {
                    events.ScheduleEvent(EVENT_SPAWN_BLACK_GUARD, 20000);
                }
                if (_slaughterProgress == 4)
                {
                    if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
                        baron->AI()->Talk(SAY_BARON_GUARD_DEAD);
                    if (GameObject* gate = instance->GetGameObject(_zigguratDoorsGUID5))
                        gate->SetGoState(GO_STATE_ACTIVE);

                }
            }

            void OnUnitDeath(Unit* unit)
            {
                switch (unit->GetEntry())
                {
                    case NPC_VENOM_BELCHER:
                    case NPC_BILE_SPEWER:
                    case NPC_RAMSTEIN_THE_GORGER:
                    case NPC_MINDLESS_UNDEAD:
                    case NPC_BLACK_GUARD:
                        if (--_slaughterNPCs == 0)
                        {
                            ++_slaughterProgress;
                            ProcessSlaughterEvent();
                            SaveToDB();
                        }
                        break;
                }
            }

            void OnGameObjectCreate(GameObject* go)
            {
                switch (go->GetEntry())
                {
                    case GO_ZIGGURAT_DOORS1:
                        _zigguratDoorsGUID1 = go->GetGUID();
                        if (GetData(TYPE_ZIGGURAT1) >= 1)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_ZIGGURAT_DOORS2:
                        _zigguratDoorsGUID2 = go->GetGUID();
                        if (GetData(TYPE_ZIGGURAT2) >= 1)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_ZIGGURAT_DOORS3:
                        _zigguratDoorsGUID3 = go->GetGUID();
                        if (GetData(TYPE_ZIGGURAT3) >= 1)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_GAUNTLET_GATE:
                        _gauntletGateGUID = go->GetGUID();
                        if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_SLAUGTHER_GATE:
                        _slaughterGateGUID = go->GetGUID();
                        if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_ZIGGURAT_DOORS4:
                        _zigguratDoorsGUID4 = go->GetGUID();
                        if (_slaughterProgress == 4)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_ZIGGURAT_DOORS5:
                        _zigguratDoorsGUID5 = go->GetGUID();
                        if (_slaughterProgress == 4)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                    case GO_SLAUGHTER_GATE_SIDE:
                        if (_slaughterProgress >= 2)
                            go->SetGoState(GO_STATE_ACTIVE);
                        break;
                }
            }

            void CheckZiggurats()
            {
                if (_zigguratState1 == 2 && _zigguratState2 == 2 && _zigguratState3 == 2)
                {
                    instance->LoadGrid(4035.83f, -3336.31f);
                    if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
                        baron->AI()->Talk(SAY_BRAON_ZIGGURAT_FALL_YELL);

                    if (GameObject* gate = instance->GetGameObject(_gauntletGateGUID))
                        gate->SetGoState(GO_STATE_ACTIVE);
                    if (GameObject* gate = instance->GetGameObject(_slaughterGateGUID))
                        gate->SetGoState(GO_STATE_ACTIVE);
                }
            }

            void SetData(uint32 type, uint32 data)
            {
                switch (type)
                {
                    case TYPE_BARON_RUN:
                    {
                        if (_baronRunProgress == DATA_BARON_RUN_NONE)
                        {
                            _baronRunProgress = DATA_BARON_RUN_GATE;
                            _baronRunTime = 45;
                            DoCastSpellOnPlayers(SPELL_BARON_ULTIMATUM);
                            events.ScheduleEvent(EVENT_BARON_TIME, 60000);

                            instance->LoadGrid(4035.83f, -3336.31f);
                            if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
                                baron->AI()->Talk(SAY_BARON_INIT_YELL);
                        }
                        break;
                    }
                    case TYPE_ZIGGURAT1:
                    {
                        if (data == _zigguratState1+1)
                            ++_zigguratState1;

                        if (_zigguratState1 == 1)
                            if (GameObject* ziggurat = instance->GetGameObject(_zigguratDoorsGUID1))
                                ziggurat->SetGoState(GO_STATE_ACTIVE);

                        CheckZiggurats();
                        break;
                    }
                    case TYPE_ZIGGURAT2:
                    {
                        if (data == _zigguratState2+1)
                            ++_zigguratState2;

                        if (_zigguratState2 == 1)
                            if (GameObject* ziggurat = instance->GetGameObject(_zigguratDoorsGUID2))
                                ziggurat->SetGoState(GO_STATE_ACTIVE);

                        CheckZiggurats();
                        break;                      
                    }
                    case TYPE_ZIGGURAT3:
                    {
                        if (data == _zigguratState3+1)
                            ++_zigguratState3;

                        if (_zigguratState3 == 1)
                            if (GameObject* ziggurat = instance->GetGameObject(_zigguratDoorsGUID3))
                                ziggurat->SetGoState(GO_STATE_ACTIVE);

                        CheckZiggurats();
                        break;
                    }
                    case TYPE_BARON_FIGHT:
                    {
                        if (GameObject* gate = instance->GetGameObject(_zigguratDoorsGUID5))
                            gate->SetGoState(data == IN_PROGRESS ? GO_STATE_READY : GO_STATE_ACTIVE);
                        return;
                    }
                    case TYPE_MALLOW:
                        ++_postboxesOpened;
                        break;
                }

                SaveToDB();
            }

            std::string GetSaveData()
            {
                std::ostringstream saveStream;
                saveStream << "S T " << _baronRunProgress << ' ' << _baronRunTime << ' ' << _zigguratState1 << ' ' << _zigguratState2 << ' ' << _zigguratState3 << ' ' << _slaughterProgress << ' ' << _postboxesOpened;
                return saveStream.str();
            }

            void Load(const char* in)
            {
                if (!in)
                    return;

                char dataHead1, dataHead2;
                std::istringstream loadStream(in);
                loadStream >> dataHead1 >> dataHead2;
                if (dataHead1 == 'S' && dataHead2 == 'T')
                {
                    loadStream >> _baronRunProgress;
                    loadStream >> _baronRunTime;
                    loadStream >> _zigguratState1;
                    loadStream >> _zigguratState2;
                    loadStream >> _zigguratState3;
                    loadStream >> _slaughterProgress;
                    loadStream >> _postboxesOpened;
                }

                if (_baronRunTime > 0)
                    events.ScheduleEvent(EVENT_BARON_TIME, 60000);

                if (_slaughterProgress > 0 && _slaughterProgress < 4)
                    events.ScheduleEvent(EVENT_FORCE_SLAUGHTER_EVENT, 5000);
            }

            uint32 GetData(uint32 type) const
            {
                switch (type)
                {
                    case TYPE_ZIGGURAT1:
                        return _zigguratState1;
                    case TYPE_ZIGGURAT2:
                        return _zigguratState2;
                    case TYPE_ZIGGURAT3:
                        return _zigguratState3;
                    case TYPE_MALLOW:
                        return _postboxesOpened;
                }
                return 0;
            }

            void Update(uint32 diff)
            {
                events.Update(diff);
                switch (events.ExecuteEvent())
                {
                    case EVENT_BARON_TIME:
                    {
                        --_baronRunTime;
                        instance->LoadGrid(4035.83f, -3336.31f);
                        Creature* baron = instance->GetCreature(_baronRivendareGUID);
                        if (baron && !baron->IsInCombat())
                        {
                            switch (_baronRunTime)
                            {
                                case 10:
                                    baron->AI()->Talk(SAY_BARON_10M);
                                    break;
                                case 5:
                                    baron->AI()->Talk(SAY_BARON_5M);
                                    if (Creature* ysida = baron->FindNearestCreature(NPC_YSIDA, 50.0f))
                                        ysida->AI()->SetData(1, 1);
                                    break;
                                case 0:
                                    baron->AI()->Talk(SAY_BARON_0M);
                                    DoRemoveAurasDueToSpellOnPlayers(SPELL_BARON_ULTIMATUM);
                                    break;
                            }
                        }

                        if (_baronRunTime > 0)
                            events.ScheduleEvent(EVENT_BARON_TIME, 60000);
                        else
                            events.ScheduleEvent(EVENT_EXECUTE_PRISONER, 0);

                        SaveToDB();
                        break;
                    }
                    case EVENT_EXECUTE_PRISONER:
                    {
                        instance->LoadGrid(4035.83f, -3336.31f);
                        Creature* baron = instance->GetCreature(_baronRivendareGUID);
                        if (baron && baron->IsAlive())
                        {
                            if (!baron->IsInCombat())
                            {
                                baron->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK1H);
                                if (Creature* ysida = baron->FindNearestCreature(NPC_YSIDA, 50.0f))
                                    Unit::Kill(baron, ysida);
                            }
                            else
                                events.ScheduleEvent(EVENT_EXECUTE_PRISONER, 1000);
                        }
                        break;
                    }
                    case EVENT_SPAWN_MINDLESS:
                    {
                        Position pos = {3941.75f, -3393.06f, 119.70f, 0.0f};
                        instance->SummonCreature(NPC_MINDLESS_UNDEAD, pos);
                        break;
                    }
                    case EVENT_FORCE_SLAUGHTER_EVENT:
                    {
                        Map::PlayerList const& PlayerList = instance->GetPlayers();
                        if (!PlayerList.isEmpty())
                            for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
                                if (Player* player = i->GetSource())
                                    if (player->GetDistance2d(4034.97f, -3402.13f) < 50.0f)
                                    {
                                        ProcessSlaughterEvent();
                                        return;
                                    }
                            
                        events.ScheduleEvent(EVENT_FORCE_SLAUGHTER_EVENT, 3000);
                        break;
                    }
                    case EVENT_SPAWN_BLACK_GUARD:
                    {
                        for (uint8 i = 0; i < 5; ++i)
                            if (Creature* guard = instance->SummonCreature(NPC_BLACK_GUARD, BlackGuardPos[i]))
                            {
                                guard->SetWalk(true);
                                guard->GetMotionMaster()->MovePoint(0, BlackGuardPos[i+5]);
                                guard->SetHomePosition(BlackGuardPos[i+5]);
                                if (i == 0 && guard->AI())
                                    guard->AI()->Talk(SAY_BLACK_GUARD_INIT);
                            }

                        if (GameObject* gate = instance->GetGameObject(_zigguratDoorsGUID4))
                            gate->SetGoState(GO_STATE_ACTIVE);
                        break;
                    }
                }
            }

        private:
            uint32 _baronRunProgress;
            uint32 _baronRunTime;
            uint32 _zigguratState1;
            uint32 _zigguratState2;
            uint32 _zigguratState3;
            uint32 _slaughterProgress;
            uint32 _slaughterNPCs;
            uint32 _postboxesOpened;
            EventMap events;

            uint64 _zigguratDoorsGUID1;
            uint64 _zigguratDoorsGUID2;
            uint64 _zigguratDoorsGUID3;
            uint64 _zigguratDoorsGUID4;
            uint64 _zigguratDoorsGUID5;
            uint64 _slaughterGateGUID;
            uint64 _gauntletGateGUID;
            uint64 _baronRivendareGUID;
        };

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

void AddSC_instance_stratholme()
{
    new instance_stratholme();
}