/*
REWRITTEN BY XINEF
*/

#include "ScriptMgr.h"
#include "InstanceScript.h"
#include "serpent_shrine.h"
#include "Player.h"
#include "TemporarySummon.h"

DoorData const doorData[] =
{
    { GO_LADY_VASHJ_BRIDGE_CONSOLE, DATA_BRIDGE_EMERGED, DOOR_TYPE_PASSAGE, BOUNDARY_NONE },
    { GO_COILFANG_BRIDGE1,          DATA_BRIDGE_EMERGED, DOOR_TYPE_PASSAGE, BOUNDARY_NONE },
    { GO_COILFANG_BRIDGE2,          DATA_BRIDGE_EMERGED, DOOR_TYPE_PASSAGE, BOUNDARY_NONE },
    { GO_COILFANG_BRIDGE3,          DATA_BRIDGE_EMERGED, DOOR_TYPE_PASSAGE, BOUNDARY_NONE }
};

class instance_serpent_shrine : public InstanceMapScript
{
    public:
        instance_serpent_shrine() : InstanceMapScript("instance_serpent_shrine", 548) { }

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

            void Initialize()
            {
                SetBossNumber(MAX_ENCOUNTERS);
                LoadDoorData(doorData);

                LadyVashjGUID = 0;
                memset(&ShieldGeneratorGUID, 0, sizeof(ShieldGeneratorGUID));
                AliveKeepersCount = 0;
                LeotherasTheBlindGUID = 0;
                LurkerBelowGUID = 0;
            }

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

                if (type == DATA_LADY_VASHJ)
                    for (uint8 i = 0; i < 4; ++i)
                        if (GameObject* gobject = instance->GetGameObject(ShieldGeneratorGUID[i]))
                            gobject->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);

                return true;
            }

            void OnGameObjectCreate(GameObject* go)
            {
                switch (go->GetEntry())
                {
                    case GO_LADY_VASHJ_BRIDGE_CONSOLE:
                    case GO_COILFANG_BRIDGE1:
                    case GO_COILFANG_BRIDGE2:
                    case GO_COILFANG_BRIDGE3:
                        AddDoor(go, true);
                        break;
                    case GO_SHIELD_GENERATOR1:
                    case GO_SHIELD_GENERATOR2:
                    case GO_SHIELD_GENERATOR3:
                    case GO_SHIELD_GENERATOR4:
                        ShieldGeneratorGUID[go->GetEntry()-GO_SHIELD_GENERATOR1] = go->GetGUID();
                        break;
                }
            }

            void OnGameObjectRemove(GameObject* go)
            {
                switch (go->GetEntry())
                {
                    case GO_LADY_VASHJ_BRIDGE_CONSOLE:
                    case GO_COILFANG_BRIDGE1:
                    case GO_COILFANG_BRIDGE2:
                    case GO_COILFANG_BRIDGE3:
                        AddDoor(go, false);
                        break;
                }
            }

            void OnCreatureCreate(Creature* creature)
            {
                switch (creature->GetEntry())
                {
                    case NPC_COILFANG_SHATTERER:
                    case NPC_COILFANG_PRIESTESS:
                        if (creature->GetPositionX() > -110.0f && creature->GetPositionX() < 155.0f && creature->GetPositionY() > -610.0f && creature->GetPositionY() < -280.0f)
                            AliveKeepersCount += creature->IsAlive() ? 0 : -1; // retarded SmartAI calls JUST_RESPAWNED in AIInit...
                        break;
                    case NPC_THE_LURKER_BELOW:
                        LurkerBelowGUID = creature->GetGUID();
                        break;
                    case NPC_LEOTHERAS_THE_BLIND:
                        LeotherasTheBlindGUID = creature->GetGUID();
                        break;
                    case NPC_CYCLONE_KARATHRESS:
                        creature->GetMotionMaster()->MoveRandom(50.0f);
                        break;
                    case NPC_LADY_VASHJ:
                        LadyVashjGUID = creature->GetGUID();
                        break;
                    case NPC_ENCHANTED_ELEMENTAL:
                    case NPC_COILFANG_ELITE:
                    case NPC_COILFANG_STRIDER:
                    case NPC_TAINTED_ELEMENTAL:
                        if (Creature* vashj = instance->GetCreature(LadyVashjGUID))
                            vashj->AI()->JustSummoned(creature);
                        break;
                }
            }

            uint64 GetData64(uint32 identifier) const
            {
                switch (identifier)
                {
                    case NPC_THE_LURKER_BELOW:
                        return LurkerBelowGUID;
                    case NPC_LEOTHERAS_THE_BLIND:
                        return LeotherasTheBlindGUID;
                    case NPC_LADY_VASHJ:
                        return LadyVashjGUID;
                }
                return 0;
            }

            void SetData(uint32 type, uint32 data)
            {
                switch (type)
                {
                    case DATA_PLATFORM_KEEPER_RESPAWNED:
                        ++AliveKeepersCount;
                        break;
                    case DATA_PLATFORM_KEEPER_DIED:
                        --AliveKeepersCount;
                        break;
                    case DATA_BRIDGE_ACTIVATED:
                        SetBossState(DATA_BRIDGE_EMERGED, NOT_STARTED);
                        SetBossState(DATA_BRIDGE_EMERGED, DONE);
                        break;
                    case DATA_ACTIVATE_SHIELD:
                        if (Creature* vashj = instance->GetCreature(LadyVashjGUID))
                            for (uint8 i = 0; i < 4; ++i)
                                if (GameObject* gobject = instance->GetGameObject(ShieldGeneratorGUID[i]))
                                {
                                    gobject->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
                                    vashj->SummonTrigger(gobject->GetPositionX(), gobject->GetPositionY(), gobject->GetPositionZ(), 0.0f, 0);
                                }
                        break;
                }
            }

            uint32 GetData(uint32 type) const
            {
                if (type == DATA_ALIVE_KEEPERS)
                    return AliveKeepersCount;

                return 0;
            }

            std::string GetSaveData()
            {
                OUT_SAVE_INST_DATA;

                std::ostringstream saveStream;
                saveStream << "S C " << GetBossSaveData();

                OUT_SAVE_INST_DATA_COMPLETE;
                return saveStream.str();
            }

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

                OUT_LOAD_INST_DATA(str);

                char dataHead1, dataHead2;

                std::istringstream loadStream(str);
                loadStream >> dataHead1 >> dataHead2;

                if (dataHead1 == 'S' && dataHead2 == 'C')
                {
                    for (uint8 i = 0; i < MAX_ENCOUNTERS; ++i)
                    {
                        uint32 tmpState;
                        loadStream >> tmpState;
                        if (tmpState == IN_PROGRESS || tmpState > SPECIAL)
                            tmpState = NOT_STARTED;
                        SetBossState(i, EncounterState(tmpState));
                    }
                }
                else
                    OUT_LOAD_INST_DATA_FAIL;

                OUT_LOAD_INST_DATA_COMPLETE;
            }

        private:
            uint64 LadyVashjGUID;
            uint64 ShieldGeneratorGUID[4];
            uint64 LurkerBelowGUID;
            uint64 LeotherasTheBlindGUID;
            int32 AliveKeepersCount;
        };

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

class spell_serpentshrine_cavern_serpentshrine_parasite : public SpellScriptLoader
{
    public:
        spell_serpentshrine_cavern_serpentshrine_parasite() : SpellScriptLoader("spell_serpentshrine_cavern_serpentshrine_parasite") { }

        class spell_serpentshrine_cavern_serpentshrine_parasite_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_serpentshrine_cavern_serpentshrine_parasite_AuraScript)

            void HandleEffectRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                if (GetTarget()->GetInstanceScript() && GetTarget()->GetInstanceScript()->IsEncounterInProgress())
                    GetTarget()->CastSpell(GetTarget(), SPELL_SUMMON_SERPENTSHRINE_PARASITE, true);
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_serpentshrine_cavern_serpentshrine_parasite_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_serpentshrine_cavern_serpentshrine_parasite_AuraScript();
        }
};

class spell_serpentshrine_cavern_serpentshrine_parasite_trigger : public SpellScriptLoader
{
    public:
        spell_serpentshrine_cavern_serpentshrine_parasite_trigger() : SpellScriptLoader("spell_serpentshrine_cavern_serpentshrine_parasite_trigger") { }

        class spell_serpentshrine_cavern_serpentshrine_parasite_trigger_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_serpentshrine_cavern_serpentshrine_parasite_trigger_AuraScript)

            void HandleEffectRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                if (GetTarget()->GetInstanceScript() && GetTarget()->GetInstanceScript()->IsEncounterInProgress())
                    GetTarget()->CastSpell(GetTarget(), SPELL_SUMMON_SERPENTSHRINE_PARASITE, true);
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_serpentshrine_cavern_serpentshrine_parasite_trigger_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_serpentshrine_cavern_serpentshrine_parasite_trigger_AuraScript();
        }

        class spell_serpentshrine_cavern_serpentshrine_parasite_trigger_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_serpentshrine_cavern_serpentshrine_parasite_trigger_SpellScript);

            void HandleApplyAura(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (Creature* target = GetHitCreature())
                    target->DespawnOrUnsummon(1);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_serpentshrine_cavern_serpentshrine_parasite_trigger_SpellScript::HandleApplyAura, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_serpentshrine_cavern_serpentshrine_parasite_trigger_SpellScript();
        }
};

class spell_serpentshrine_cavern_infection : public SpellScriptLoader
{
    public:
        spell_serpentshrine_cavern_infection() : SpellScriptLoader("spell_serpentshrine_cavern_infection") { }

        class spell_serpentshrine_cavern_infection_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_serpentshrine_cavern_infection_AuraScript)

            void HandleEffectRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE && GetTarget()->GetInstanceScript())
                {
                    CustomSpellValues values;
                    values.AddSpellMod(SPELLVALUE_MAX_TARGETS, 1);
                    values.AddSpellMod(SPELLVALUE_BASE_POINT0, aurEff->GetAmount()+500);
                    values.AddSpellMod(SPELLVALUE_BASE_POINT1, aurEff->GetAmount()+500);
                    GetTarget()->CastCustomSpell(SPELL_RAMPART_INFECTION, values, GetTarget(), TRIGGERED_FULL_MASK, NULL);
                }
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_serpentshrine_cavern_infection_AuraScript::HandleEffectRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_serpentshrine_cavern_infection_AuraScript();
        }
};

class spell_serpentshrine_cavern_coilfang_water : public SpellScriptLoader
{
    public:
        spell_serpentshrine_cavern_coilfang_water() : SpellScriptLoader("spell_serpentshrine_cavern_coilfang_water") { }

        class spell_serpentshrine_cavern_coilfang_water_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_serpentshrine_cavern_coilfang_water_AuraScript)

            void HandleEffectApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                if (InstanceScript* instance = GetUnitOwner()->GetInstanceScript())
                    if (instance->GetBossState(DATA_THE_LURKER_BELOW) != DONE)
                        if (instance->GetData(DATA_ALIVE_KEEPERS) == 0)
                            GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SCALDING_WATER, true);
            }

            void HandleEffectRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                GetUnitOwner()->RemoveAurasDueToSpell(SPELL_SCALDING_WATER);
            }

            void CalcPeriodic(AuraEffect const* /*aurEff*/, bool& isPeriodic, int32& amplitude)
            {
                InstanceScript* instance = GetUnitOwner()->GetInstanceScript();
                if (!instance || instance->GetBossState(DATA_THE_LURKER_BELOW) == DONE)
                    return;

                isPeriodic = true;
                amplitude = 8*IN_MILLISECONDS;
            }
            

            void HandlePeriodic(AuraEffect const* aurEff)
            {
                PreventDefaultAction();
                InstanceScript* instance = GetUnitOwner()->GetInstanceScript();
                if (!instance || GetUnitOwner()->GetMapId() != 548)
                {
                    SetDuration(0);
                    return;
                }

                if (instance->GetBossState(DATA_THE_LURKER_BELOW) == DONE || instance->GetData(DATA_ALIVE_KEEPERS) == 0 || GetUnitOwner()->GetPositionZ() > -20.5f || !GetUnitOwner()->IsInWater())
                    return;

                for (uint8 i = 0; i < 3; ++i)
                    GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_FRENZY_WATER, true);
            }

            void Register()
            {
                AfterEffectApply += AuraEffectApplyFn(spell_serpentshrine_cavern_coilfang_water_AuraScript::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
                AfterEffectRemove += AuraEffectRemoveFn(spell_serpentshrine_cavern_coilfang_water_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);

                DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(spell_serpentshrine_cavern_coilfang_water_AuraScript::CalcPeriodic, EFFECT_0, SPELL_AURA_DUMMY);
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_serpentshrine_cavern_coilfang_water_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_DUMMY);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_serpentshrine_cavern_coilfang_water_AuraScript();
        }
};

void AddSC_instance_serpentshrine_cavern()
{
    new instance_serpent_shrine();
    new spell_serpentshrine_cavern_serpentshrine_parasite();
    new spell_serpentshrine_cavern_serpentshrine_parasite_trigger();
    new spell_serpentshrine_cavern_infection();
    new spell_serpentshrine_cavern_coilfang_water();
}
