/*
REWRITTEN BY XINEF
*/

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Cell.h"
#include "CellImpl.h"
#include "sunwell_plateau.h"

enum Yells
{
    YELL_BIRTH                                  = 0,
    YELL_KILL                                   = 1,
    YELL_BREATH                                 = 2,
    YELL_TAKEOFF                                = 3,
    YELL_BERSERK                                = 4,
    YELL_DEATH                                  = 5,
    EMOTE_BREATH                                = 6
};

enum Spells
{
    //Aura
    SPELL_NOXIOUS_FUMES                         = 47002,

    //Land phase
    SPELL_BERSERK                               = 45078,
    SPELL_CLEAVE                                = 19983,
    SPELL_CORROSION                             = 45866,
    SPELL_GAS_NOVA                              = 45855,
    SPELL_ENCAPSULATE_CHANNEL                   = 45661,

    //Flight phase
    SPELL_SUMMON_DEMONIC_VAPOR                  = 45391,
    SPELL_DEMONIC_VAPOR_SPAWN_TRIGGER           = 45388, // Triggers visual beam
    SPELL_DEMONIC_VAPOR_PERIODIC                = 45411, // Spawns cloud and deals damage
    SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC          = 45399, // periodic of cloud
    SPELL_DEMONIC_VAPOR                         = 45402, // cloud dot
    SPELL_SUMMON_BLAZING_DEAD                   = 45400, // spawns skeletons
    SPELL_FELMYST_SPEED_BURST                   = 45495, // speed burst and breath animation
    SPELL_FOG_OF_CORRUPTION                     = 45582, // trigger cast
    SPELL_FOG_OF_CORRUPTION_CHARM               = 45717, // charm 1
    SPELL_FOG_OF_CORRUPTION_CHARM2              = 45726, // charm 2
};

enum Misc
{
    // Land
    EVENT_SPELL_BERSERK         = 1,
    EVENT_SPELL_CLEAVE          = 2,
    EVENT_SPELL_CORROSION       = 3,
    EVENT_SPELL_GAS_NOVA        = 4,
    EVENT_SPELL_ENCAPSULATE     = 5,
    EVENT_FLIGHT                = 6,
    EVENT_LAND                  = 7,
    EVENT_RESTORE_COMBAT        = 8,
    EVENT_RESTORE_COMBAT2       = 9,

    // Air
    EVENT_FLIGHT_SEQ            = 100,
    EVENT_FLIGHT_VAPOR          = 101,
    EVENT_FLIGHT_MOVE_UP        = 102,
    EVENT_LAND_FIGHT            = 103,
    EVENT_FLIGHT_EMOTE          = 104,
    EVENT_FLIGHT_BREATH1        = 105,
    EVENT_FLIGHT_BREATH2        = 106,
    EVENT_FLIGHT_FLYOVER1       = 107,
    EVENT_FLIGHT_FLYOVER2       = 108,
    EVENT_CORRUPT_TRIGGERS      = 109,

    // Intro
    EVENT_INTRO_1               = 20,
    EVENT_INTRO_2               = 21,
    EVENT_INTRO_3               = 22,
    EVENT_INTRO_4               = 23,

    // Misc
    ACTION_START_EVENT          = 1,
    POINT_GROUND                = 1,
    POINT_AIR                   = 2,
    POINT_AIR_BREATH_START1     = 3,
    POINT_AIR_BREATH_END1       = 4,
    POINT_AIR_BREATH_START2     = 5,
    POINT_AIR_BREATH_END2       = 6,
    POINT_MISC                  = 7,

    NPC_FOG_TRIGGER             = 23472
};

class CorruptTriggers : public BasicEvent
{
    public:
        CorruptTriggers(Unit* caster) : _caster(caster)
        {
        }

        bool Execute(uint64 /*execTime*/, uint32 /*diff*/)
        {
            std::list<Creature*> cList;
            _caster->GetCreaturesWithEntryInRange(cList, 70.0f, NPC_FOG_TRIGGER);
            for (std::list<Creature*>::const_iterator itr = cList.begin(); itr != cList.end(); ++itr)
                if (_caster->GetExactDist2d(*itr) <= 11.0f)
                    (*itr)->CastSpell(*itr, SPELL_FOG_OF_CORRUPTION, true);
            return true;
        }

    private:
        Unit* _caster;
};

class boss_felmyst : public CreatureScript
{
public:
    boss_felmyst() : CreatureScript("boss_felmyst") { }

    struct boss_felmystAI : public BossAI
    {
        boss_felmystAI(Creature* creature) : BossAI(creature, DATA_FELMYST)
        {
            bool appear = instance->GetBossState(DATA_BRUTALLUS) == DONE;
            creature->SetVisible(appear);
            creature->SetStandState(UNIT_STAND_STATE_SLEEP);
            creature->SetReactState(REACT_PASSIVE);
        }

        EventMap events2;

        void DoAction(int32 param)
        {
            if (param == ACTION_START_EVENT)
            {
                me->SetVisible(true);
                me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
                events2.ScheduleEvent(EVENT_INTRO_1, 3000);
            }
        }

        void Reset()
        {
            BossAI::Reset();
            me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
            me->SetReactState(REACT_PASSIVE);
            me->SetDisableGravity(false);
            events2.Reset();
            instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FOG_OF_CORRUPTION_CHARM);
        }

        void EnterCombat(Unit* who)
        {
            BossAI::EnterCombat(who);
            me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
            if (events.Empty() && events2.Empty())
                events2.ScheduleEvent(EVENT_INTRO_2, 3000);
        }


        void KilledUnit(Unit* victim)
        {
            if (victim->GetTypeId() == TYPEID_PLAYER && roll_chance_i(50))
                Talk(YELL_KILL);
        }

        void JustDied(Unit* killer)
        {
            BossAI::JustDied(killer);
            Talk(YELL_DEATH);
            instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FOG_OF_CORRUPTION_CHARM);

            // Summon Kalecgos (human form of kalecgos fight)
            me->SummonCreature(NPC_KALEC, 1526.28f, 700.10f, 60.0f, 4.33f);
        }

        void MovementInform(uint32 type, uint32 point)
        {
            if (type != POINT_MOTION_TYPE)
                return;

            if (point == POINT_GROUND)
            {
                me->HandleEmoteCommand(EMOTE_ONESHOT_LAND);
                me->SetDisableGravity(false);
                me->SendMovementFlagUpdate();

                events.ScheduleEvent(EVENT_RESTORE_COMBAT, 0);
                events.ScheduleEvent(EVENT_RESTORE_COMBAT2, 1);
                events.ScheduleEvent(EVENT_SPELL_CLEAVE, 7500, 1);
                events.ScheduleEvent(EVENT_SPELL_CORROSION, 12000, 1);
                events.ScheduleEvent(EVENT_SPELL_GAS_NOVA, 18000, 1);
                events.ScheduleEvent(EVENT_SPELL_ENCAPSULATE, 25000, 1);
                events.ScheduleEvent(EVENT_FLIGHT, 60000, 1);
            }
            else if (point == POINT_AIR_BREATH_START1)
            {
                me->SetTarget(0);
                me->SetFacingTo(4.71f);
                events.ScheduleEvent(EVENT_FLIGHT_EMOTE, 2000);
                events.ScheduleEvent(EVENT_CORRUPT_TRIGGERS, 5000);
                events.ScheduleEvent(EVENT_FLIGHT_FLYOVER1, 5000);
            }
            else if (point == POINT_AIR_BREATH_END1)
            {
                me->RemoveAurasDueToSpell(SPELL_FELMYST_SPEED_BURST);
                me->SetFacingTo(1.57f);
                if (events.GetNextEventTime(EVENT_FLIGHT_BREATH1) != 0)
                    events.ScheduleEvent(EVENT_FLIGHT_BREATH2, 2000);
            }
            else if (point == POINT_AIR_BREATH_START2)
            {
                me->SetTarget(0);
                me->SetFacingTo(1.57f);
                events.ScheduleEvent(EVENT_FLIGHT_EMOTE, 2000);
                events.ScheduleEvent(EVENT_CORRUPT_TRIGGERS, 5000);
                events.ScheduleEvent(EVENT_FLIGHT_FLYOVER2, 5000);
            }
            else if (point == POINT_AIR_BREATH_END2)
            {
                me->RemoveAurasDueToSpell(SPELL_FELMYST_SPEED_BURST);
                me->SetFacingTo(4.71f);
            }
        }

        void JustSummoned(Creature* summon)
        {
            summons.Summon(summon);
        }

        void UpdateAI(uint32 diff)
        {
            events2.Update(diff);
            switch (events2.ExecuteEvent())
            {
                case EVENT_INTRO_1:
                    me->SetStandState(UNIT_STAND_STATE_STAND);
                    events2.ScheduleEvent(EVENT_INTRO_2, 4000);
                    break;
                case EVENT_INTRO_2:
                    Talk(YELL_BIRTH);
                    me->SetDisableGravity(true);
                    me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF);
                    me->SendMovementFlagUpdate();
                    events2.ScheduleEvent(EVENT_INTRO_3, 1500);
                    break;
                case EVENT_INTRO_3:
                    me->GetMotionMaster()->MovePoint(POINT_AIR, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()+10.0f, false, true);
                    events2.ScheduleEvent(EVENT_INTRO_4, 2000);
                    break;
                case EVENT_INTRO_4:
                    events.ScheduleEvent(EVENT_LAND, 3000, 1);
                    events.ScheduleEvent(EVENT_SPELL_BERSERK, 600000);
                    me->SetInCombatWithZone();
                    me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
                    me->CastSpell(me, SPELL_NOXIOUS_FUMES, true);
                    me->GetMotionMaster()->MovePoint(POINT_MISC, 1472.18f, 603.38f, 34.0f, false, true);
                    break;
            }

            if (!events2.Empty())
                return;

            if (!UpdateVictim())
                return;

            events.Update(diff);
            if (me->HasUnitState(UNIT_STATE_CASTING))
                return;

            switch(events.ExecuteEvent())
            {
                case EVENT_RESTORE_COMBAT:
                    me->SetReactState(REACT_AGGRESSIVE);
                    break;
                case EVENT_RESTORE_COMBAT2:
                    me->SetTarget(me->GetVictim()->GetGUID());
                    me->GetMotionMaster()->MoveChase(me->GetVictim());
                    break;
                case EVENT_LAND:
                    me->GetMotionMaster()->MovePoint(POINT_GROUND, me->GetPositionX(), me->GetPositionY(), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), false, true);
                    break;
                case EVENT_SPELL_BERSERK:
                    Talk(YELL_BERSERK);
                    me->CastSpell(me, SPELL_BERSERK, true);
                    break;
                case EVENT_SPELL_CLEAVE:
                    me->CastSpell(me->GetVictim(), SPELL_CLEAVE, false);
                    events.ScheduleEvent(EVENT_SPELL_CLEAVE, 7500, 1);
                    break;
                case EVENT_SPELL_CORROSION:
                    me->CastSpell(me->GetVictim(), SPELL_CORROSION, false);
                    events.ScheduleEvent(EVENT_SPELL_CORROSION, 20000, 1);
                    break;
                case EVENT_SPELL_GAS_NOVA:
                    DoCast(me, SPELL_GAS_NOVA, false);
                    events.ScheduleEvent(EVENT_SPELL_GAS_NOVA, 20000, 1);
                    break;
                case EVENT_SPELL_ENCAPSULATE:
                    if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 50.0f, true))
                        me->CastSpell(target, SPELL_ENCAPSULATE_CHANNEL, false);
                    events.ScheduleEvent(EVENT_SPELL_ENCAPSULATE, 25000, 1);
                    break;
                case EVENT_FLIGHT:
                    events.CancelEventGroup(1);
                    events.ScheduleEvent(EVENT_FLIGHT_SEQ, 1000);
                    me->SetReactState(REACT_PASSIVE);
                    me->StopMoving();
                    me->GetMotionMaster()->Clear();
                    break;
                case EVENT_FLIGHT_SEQ:
                    Talk(YELL_TAKEOFF);
                    me->SetTarget(0);
                    me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF);
                    me->SetDisableGravity(true);
                    me->SendMovementFlagUpdate();

                    events.ScheduleEvent(EVENT_FLIGHT_MOVE_UP, 2000);
                    events.ScheduleEvent(EVENT_FLIGHT_VAPOR, 8000);
                    events.ScheduleEvent(EVENT_FLIGHT_VAPOR, 21000);
                    events.ScheduleEvent(EVENT_FLIGHT_BREATH1, 35000);
                    events.ScheduleEvent(EVENT_FLIGHT_BREATH1, 72000);
                    events.ScheduleEvent(EVENT_LAND_FIGHT, 86000);
                    break;
                case EVENT_FLIGHT_MOVE_UP:
                    me->GetMotionMaster()->MovePoint(POINT_AIR, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()+15.0f, false, true);
                    break;
                case EVENT_FLIGHT_VAPOR:
                    me->CastSpell(me, SPELL_SUMMON_DEMONIC_VAPOR, true);
                    break;
                case EVENT_FLIGHT_BREATH1:
                {
                    Position pos = {1447.0f + urand(0, 2)*25.0f, 705.0f, 50.0f, 4.71f};
                    me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_START1, pos, false, true);
                    break;
                }
                case EVENT_FLIGHT_BREATH2:
                {
                    Position pos = {1447.0f + urand(0, 2)*25.0f, 515.0f, 50.0f, 1.57f};
                    me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_START2, pos, false, true);
                    break;
                }
                case EVENT_FLIGHT_EMOTE:
                    Talk(EMOTE_BREATH);
                    break;
                case EVENT_CORRUPT_TRIGGERS:
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(0));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(500));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(1000));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(1500));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(2000));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(2500));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(3000));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(3500));
                    me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(4000));
                    break;
                case EVENT_FLIGHT_FLYOVER1:
                    me->CastSpell(me, SPELL_FELMYST_SPEED_BURST, true);
                    me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_END1, me->GetPositionX(), me->GetPositionY()-200.0f, me->GetPositionZ()+5.0f, false, true);
                    break;
                case EVENT_FLIGHT_FLYOVER2:
                    me->CastSpell(me, SPELL_FELMYST_SPEED_BURST, true);
                    me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_END2, me->GetPositionX(), me->GetPositionY()+200.0f, me->GetPositionZ()+5.0f, false, true);
                    break;
                case EVENT_LAND_FIGHT:
                    me->GetMotionMaster()->MovePoint(POINT_GROUND, 1500.0f, 552.8f, 26.52f, false, true);
                    break;

            }

            if (!me->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
                DoMeleeAttackIfReady();
        }
    };

    CreatureAI* GetAI(Creature* creature) const
    {
        return GetInstanceAI<boss_felmystAI>(creature);
    }
};

class npc_demonic_vapor : public CreatureScript
{
public:
    npc_demonic_vapor() : CreatureScript("npc_demonic_vapor") { }

    CreatureAI* GetAI(Creature* creature) const
    {
        return new npc_demonic_vaporAI(creature);
    }

    struct npc_demonic_vaporAI : public NullCreatureAI
    {
        npc_demonic_vaporAI(Creature* creature) : NullCreatureAI(creature) { }

        void Reset()
        {
            me->CastSpell(me, SPELL_DEMONIC_VAPOR_SPAWN_TRIGGER, true);
            me->CastSpell(me, SPELL_DEMONIC_VAPOR_PERIODIC, true);
        }

        void UpdateAI(uint32 diff)
        {
            if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
            {
                Map::PlayerList const& players = me->GetMap()->GetPlayers();
                for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
                    if (me->GetDistance2d(itr->GetSource()) < 20.0f && itr->GetSource()->IsAlive())
                    {
                        me->GetMotionMaster()->MoveFollow(itr->GetSource(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED);
                        break;
                    }
            }
        }
    };
};

class npc_demonic_vapor_trail : public CreatureScript
{
public:
    npc_demonic_vapor_trail() : CreatureScript("npc_demonic_vapor_trail") { }

    CreatureAI* GetAI(Creature* creature) const
    {
        return new npc_demonic_vapor_trailAI(creature);
    }

    struct npc_demonic_vapor_trailAI : public NullCreatureAI
    {
        npc_demonic_vapor_trailAI(Creature* creature) : NullCreatureAI(creature)
        {
            timer = 1;
        }

        uint32 timer;
        void Reset()
        {
            me->CastSpell(me, SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC, true);
        }

        void SpellHitTarget(Unit* , const SpellInfo* spellInfo)
        {
            if (spellInfo->Id == SPELL_DEMONIC_VAPOR)
                me->CastSpell(me, SPELL_SUMMON_BLAZING_DEAD, true);
        }

        void UpdateAI(uint32 diff)
        {
            if (timer)
            {
                timer += diff;
                if (timer >= 6000)
                {
                    timer = 0;
                    me->CastSpell(me, SPELL_SUMMON_BLAZING_DEAD, true);
                }
            }
        }

        void JustSummoned(Creature* summon)
        {
            summon->SetInCombatWithZone();
            summon->AI()->AttackStart(summon->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f));
        }
    };
};

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

        class spell_felmyst_fog_of_corruption_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_felmyst_fog_of_corruption_SpellScript);

            void HandleScriptEffect(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (Unit* target = GetHitUnit())
                    target->CastSpell(GetCaster(), SPELL_FOG_OF_CORRUPTION_CHARM, true);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_felmyst_fog_of_corruption_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

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

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

        class spell_felmyst_fog_of_corruption_charm_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_felmyst_fog_of_corruption_charm_AuraScript);

            void HandleApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                GetTarget()->CastSpell(GetTarget(), SPELL_FOG_OF_CORRUPTION_CHARM2, true);
            }

            void HandleRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                GetTarget()->RemoveAurasDueToSpell(SPELL_FOG_OF_CORRUPTION_CHARM2);
                Unit::Kill(GetCaster(), GetTarget(), false);
            }

            void Register()
            {
                OnEffectApply += AuraEffectApplyFn(spell_felmyst_fog_of_corruption_charm_AuraScript::HandleApply, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL);
                OnEffectRemove += AuraEffectRemoveFn(spell_felmyst_fog_of_corruption_charm_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL);
            }
        };

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

class DoorsGuidCheck
{
    public:
        bool operator()(WorldObject* object) const
        {
            if (object->GetTypeId() != TYPEID_UNIT)
                return true;

            Creature* cr = object->ToCreature();
            return cr->GetDBTableGUIDLow() != 54780 && cr->GetDBTableGUIDLow() != 54787 && cr->GetDBTableGUIDLow() != 54801;
        }
};

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

        class spell_felmyst_open_brutallus_back_doors_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_felmyst_open_brutallus_back_doors_SpellScript);

            void FilterTargets(std::list<WorldObject*>& unitList)
            {
                unitList.remove_if(DoorsGuidCheck());
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_felmyst_open_brutallus_back_doors_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
            }
        };

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

void AddSC_boss_felmyst()
{
    new boss_felmyst();
    new npc_demonic_vapor();
    new npc_demonic_vapor_trail();
    new spell_felmyst_fog_of_corruption();
    new spell_felmyst_fog_of_corruption_charm();
    new spell_felmyst_open_brutallus_back_doors();
}
