/*
REWRITTEN BY XINEF
*/

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

const Position instancePositions[INSTANCE_POSITIONS_COUNT] = 
{
    {2188.18f, 228.90f, 53.025f, 1.77f},    // Orcs Gather Point 1
    {2103.23f, 93.55f, 53.096f, 3.78f},     // Orcs Gather Point 2
    {2128.43f, 71.01f, 64.42f, 1.74f}       // Lieutenant Drake Summon Position
};

const Position thrallPositions[THRALL_POSITIONS_COUNT] =
{
    {2181.37f, 119.15f, 89.45f, 5.75f},     // After wearing armor
    {2096.09f, 195.91f, 65.22f, 2.45f},     // After Fourth Ambush
    {2062.9f, 229.93f, 64.454f, 2.45f},     // After Captain Skarloc death
    {2486.91f, 626.356f, 58.0761f, 0.0f},   // Arrived at Tarren Mill
    {2660.47f, 659.223f, 62.0f, 5.78f}      // Taretha Met

};

class instance_old_hillsbrad : public InstanceMapScript
{
public:
    instance_old_hillsbrad() : InstanceMapScript("instance_old_hillsbrad", 560) { }

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

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

        void Initialize()
        {
            _encounterProgress = 0;
            _barrelCount = 0;
            _attemptsCount = 0;

            _thrallGUID = 0;
            _tarethaGUID = 0;

            _initalFlamesSet.clear();
            _finalFlamesSet.clear();
            _prisonersSet.clear();
            _events.Reset();
        }

        void OnPlayerEnter(Player* player)
        {
            if (instance->GetPlayersCountExceptGMs() == 1)
                CleanupInstance();

            EnsureGridLoaded();

            if (_encounterProgress < ENCOUNTER_PROGRESS_BARRELS)
                player->SendUpdateWorldState(WORLD_STATE_BARRELS_PLANTED, _barrelCount);
        }

        void CleanupInstance()
        {
            if (_encounterProgress == ENCOUNTER_PROGRESS_NONE)
                return;

            _events.ScheduleEvent(EVENT_INITIAL_BARRELS_FLAME, 0);
            _events.ScheduleEvent(EVENT_FINAL_BARRELS_FLAME, 0);

            if (_encounterProgress == ENCOUNTER_PROGRESS_BARRELS)
                _events.ScheduleEvent(EVENT_SUMMON_LIEUTENANT, 0);
            else
                SetData(DATA_THRALL_REPOSITION, 2);
        }

        void OnCreatureCreate(Creature* creature)
        {
            switch (creature->GetEntry())
            {
                case NPC_THRALL:
                    _thrallGUID = creature->GetGUID();
                    if (_encounterProgress == ENCOUNTER_PROGRESS_FINISHED)
                        creature->SetVisible(false);
                    else
                        Reposition(creature);
                    break;
                case NPC_ORC_PRISONER:
                    _prisonersSet.insert(creature->GetGUID());
                    break;
                case NPC_TARETHA:
                    if (_encounterProgress == ENCOUNTER_PROGRESS_FINISHED)
                        creature->SetVisible(false);
                    _tarethaGUID = creature->GetGUID();
                    break;
            }
        }

        void OnGameObjectCreate(GameObject* gameobject)
        {
            switch (gameobject->GetEntry())
            {
                case GO_BARREL:
                    if (_encounterProgress >= ENCOUNTER_PROGRESS_BARRELS)
                        gameobject->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
                    break;
                case GO_PRISON_DOOR:
                    if (_encounterProgress >= ENCOUNTER_PROGRESS_THRALL_ARMORED)
                        gameobject->SetGoState(GO_STATE_ACTIVE);
                    break;
                case GO_ROARING_FLAME:
                    // Xinef: hack in DB to distinguish final / initial flames
                    if (gameobject->GetPhaseMask() & 0x2)
                        _finalFlamesSet.insert(gameobject->GetGUID());
                    else
                        _initalFlamesSet.insert(gameobject->GetGUID());
                    break;
            }
        }

        void SetData(uint32 type, uint32 data)
        {
            switch (type)
            {
                case DATA_THRALL_REPOSITION:
                    if (data > 1)
                        _events.ScheduleEvent(EVENT_THRALL_REPOSITION, data == 2 ? 0 : 10000);
                    else if (Creature* thrall = instance->GetCreature(_thrallGUID))
                        Reposition(thrall);
                    return;
                case DATA_ESCORT_PROGRESS:
                    _encounterProgress = data;
                    SaveToDB();
                    break;
                case DATA_BOMBS_PLACED:
                {
                    if (_barrelCount >= 5 || _encounterProgress > ENCOUNTER_PROGRESS_NONE)
                        return;

                    DoUpdateWorldState(WORLD_STATE_BARRELS_PLANTED, ++_barrelCount);
                    if (_barrelCount == 5)
                    {
                        _events.ScheduleEvent(EVENT_INITIAL_BARRELS_FLAME, 4000);
                        _events.ScheduleEvent(EVENT_FINAL_BARRELS_FLAME, 12000);
                        _events.ScheduleEvent(EVENT_SUMMON_LIEUTENANT, 18000);
                    }
                    break;
                }
                case DATA_THRALL_ADD_FLAG:
                    if (Creature* thrall = instance->GetCreature(_thrallGUID))
                        thrall->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
                    break;
            }
        }

        uint32 GetData(uint32 data) const
        {
            if (data == DATA_ESCORT_PROGRESS)
                return _encounterProgress;
            else if (data == DATA_ATTEMPTS_COUNT)
                return _attemptsCount;
            return 0;
        }

        uint64 GetData64(uint32 data) const
        {
            if (data == DATA_THRALL_GUID)
                return _thrallGUID;
            else if (data == DATA_TARETHA_GUID)
                return _tarethaGUID;
            return 0;
        }

        void Update(uint32 diff)
        {
            _events.Update(diff);
            switch (_events.ExecuteEvent())
            {
                case EVENT_INITIAL_BARRELS_FLAME:
                {
                    instance->LoadGrid(instancePositions[0].GetPositionX(), instancePositions[0].GetPositionY());
                    instance->LoadGrid(instancePositions[1].GetPositionX(), instancePositions[1].GetPositionY());

                    for (std::set<uint64>::const_iterator itr = _prisonersSet.begin(); itr != _prisonersSet.end(); ++itr)
                        if (Creature* orc = instance->GetCreature(*itr))
                        {
                            uint8 index = orc->GetDistance(instancePositions[0]) < 80.0f ? 0 : 1;
                            Position pos(instancePositions[index]);
                            orc->MovePosition(pos, frand(1.0f, 3.0f) + 15.0f * (float)rand_norm(), (float)rand_norm() * static_cast<float>(2 * M_PI));
                            orc->GetMotionMaster()->MovePoint(1, pos);
                            orc->SetStandState(UNIT_STAND_STATE_STAND);
                        }

                    for (std::set<uint64>::const_iterator itr = _initalFlamesSet.begin(); itr != _initalFlamesSet.end(); ++itr)
                        if (GameObject* gobject = instance->GetGameObject(*itr))
                        {
                            gobject->SetRespawnTime(0);
                            gobject->UpdateObjectVisibility(true);
                        }
                    break;
                }
                case EVENT_FINAL_BARRELS_FLAME:
                {
                    instance->LoadGrid(instancePositions[0].GetPositionX(), instancePositions[0].GetPositionY());
                    instance->LoadGrid(instancePositions[1].GetPositionX(), instancePositions[1].GetPositionY());

                    if (_encounterProgress == ENCOUNTER_PROGRESS_NONE)
                    {
                        Map::PlayerList const& players = instance->GetPlayers();
                        if (!players.isEmpty())
                            for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
                                if (Player* player = itr->GetSource())
                                    player->KilledMonsterCredit(NPC_LODGE_QUEST_TRIGGER, 0);
                    }

                    for (std::set<uint64>::const_iterator itr = _finalFlamesSet.begin(); itr != _finalFlamesSet.end(); ++itr)
                        if (GameObject* gobject = instance->GetGameObject(*itr))
                        {
                            gobject->SetRespawnTime(0);
                            gobject->UpdateObjectVisibility(true);
                        }

                    for (std::set<uint64>::const_iterator itr = _prisonersSet.begin(); itr != _prisonersSet.end(); ++itr)
                        if (Creature* orc = instance->GetCreature(*itr))
                            if (roll_chance_i(25))
                                orc->HandleEmoteCommand(EMOTE_ONESHOT_CHEER);
                    
                    SetData(DATA_ESCORT_PROGRESS, ENCOUNTER_PROGRESS_BARRELS);
                    DoUpdateWorldState(WORLD_STATE_BARRELS_PLANTED, 0);
                    break;
                }
                case EVENT_SUMMON_LIEUTENANT:
                {
                    instance->LoadGrid(instancePositions[2].GetPositionX(), instancePositions[2].GetPositionY());
                    if (Creature* drake = instance->SummonCreature(NPC_LIEUTENANT_DRAKE, instancePositions[2]))
                        drake->AI()->Talk(0);
                }
                case EVENT_THRALL_REPOSITION:
                {
                    if (Creature* thrall = instance->GetCreature(_thrallGUID))
                    {
                        if (!thrall->IsAlive())
                        {
                            ++_attemptsCount;
                            EnsureGridLoaded();
                            thrall->SetVisible(false);
                            Reposition(thrall);
                            thrall->setDeathState(DEAD);
                            thrall->Respawn();
                            thrall->SetVisible(true);
                            SaveToDB();
                        }
                        else
                            thrall->AI()->Reset();
                    }
                    break;
                }
            }
        }

        void Reposition(Creature* thrall)
        {
            switch (uint32 data = GetData(DATA_ESCORT_PROGRESS))
            {
                case ENCOUNTER_PROGRESS_THRALL_ARMORED:
                case ENCOUNTER_PROGRESS_AMBUSHES_1:
                case ENCOUNTER_PROGRESS_SKARLOC_KILLED:
                case ENCOUNTER_PROGRESS_TARREN_MILL:
                case ENCOUNTER_PROGRESS_TARETHA_MEET:
                    thrall->UpdatePosition(thrallPositions[data - ENCOUNTER_PROGRESS_THRALL_ARMORED], true);
                    thrall->SetHomePosition(thrallPositions[data - ENCOUNTER_PROGRESS_THRALL_ARMORED]);
                    thrall->SetFacingTo(thrallPositions[data - ENCOUNTER_PROGRESS_THRALL_ARMORED].GetOrientation());
                    break;
            }
        }

        void EnsureGridLoaded()
        {
            for (uint8 i = 0; i < THRALL_POSITIONS_COUNT; ++i)
                instance->LoadGrid(thrallPositions[i].GetPositionX(), thrallPositions[i].GetPositionY());
        }

        std::string GetSaveData()
        {
            OUT_SAVE_INST_DATA;

            std::ostringstream saveStream;
                saveStream << "O H " << _encounterProgress << ' ' << _attemptsCount;

            OUT_SAVE_INST_DATA_COMPLETE;
            return saveStream.str();
        }

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

            OUT_LOAD_INST_DATA(in);

            char dataHead1, dataHead2;
            uint32 data0, data1;

            std::istringstream loadStream(in);
            loadStream >> dataHead1 >> dataHead2 >> data0 >> data1;

            if (dataHead1 == 'O' && dataHead2 == 'H')
            {
                _encounterProgress = data0;
                _attemptsCount = data1;
            }
            else
                OUT_LOAD_INST_DATA_FAIL;

            OUT_LOAD_INST_DATA_COMPLETE;
        }

        private:
            uint32 _encounterProgress;
            uint32 _barrelCount;
            uint32 _attemptsCount;

            uint64 _thrallGUID;
            uint64 _tarethaGUID;
            std::set<uint64> _initalFlamesSet;
            std::set<uint64> _finalFlamesSet;
            std::set<uint64> _prisonersSet;

            EventMap _events;
    };

};

void AddSC_instance_old_hillsbrad()
{
    new instance_old_hillsbrad();
}
