/*
 * 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 "the_eye.h"
#include "WaypointManager.h"
#include "MoveSplineInit.h"

enum Spells
{
    SPELL_BERSERK                   = 45078,
    SPELL_FLAME_QUILLS              = 34229,
    SPELL_QUILL_MISSILE_1           = 34269, // 21
    SPELL_QUILL_MISSILE_2           = 34314, // 3
    SPELL_FLAME_BUFFET              = 34121,
    SPELL_EMBER_BLAST               = 34341,
    SPELL_REBIRTH_PHASE2            = 34342,
    SPELL_MELT_ARMOR                = 35410,
    SPELL_CHARGE                    = 35412,
    SPELL_REBIRTH_DIVE              = 35369,
    SPELL_DIVE_BOMB_VISUAL          = 35367,
    SPELL_DIVE_BOMB                 = 35181
};

const Position alarPoints[7] =
{
    {340.15f, 58.65f, 17.71f, 4.60f},
    {388.09f, 31.54f, 20.18f, 1.61f},
    {388.18f, -32.85f, 20.18f, 0.52f},
    {340.29f, -60.19f, 17.72f, 5.71f},
    {332.0f, 0.01f, 43.0f, 0.0f},
    {331.0f, 0.01f, -2.38f, 0.0f},
    {332.0f, 0.01f, 43.0f, 0.0f}
};

enum Misc
{
    DISPLAYID_INVISIBLE         = 23377,
    NPC_EMBER_OF_ALAR           = 19551,
    NPC_FLAME_PATCH             = 20602,
    
    POINT_PLATFORM              = 0,
    POINT_QUILL                 = 4,
    POINT_MIDDLE                = 5,
    POINT_DIVE                  = 6,

    EVENT_SWITCH_PLATFORM       = 1,
    EVENT_START_QUILLS          = 2,
    EVENT_RELOCATE_MIDDLE       = 3,
    EVENT_REBIRTH               = 4,
    EVENT_SPELL_MELT_ARMOR      = 5,
    EVENT_SPELL_FLAME_PATCH     = 6,
    EVENT_SPELL_CHARGE          = 7,
    EVENT_SPELL_DIVE_BOMB       = 8,
    EVENT_START_DIVE            = 9,
    EVENT_CAST_DIVE_BOMB        = 10,
    EVENT_SUMMON_DIVE_PHOENIX   = 11,
    EVENT_REBIRTH_DIVE          = 12,
    EVENT_SPELL_BERSERK         = 13,

    EVENT_MOVE_TO_PHASE_2       = 20,
    EVENT_FINISH_DIVE           = 21

};

// Xinef: Ruse of the Ashtongue (10946)
enum qruseoftheAshtongue
{
    SPELL_ASHTONGUE_RUSE        = 42090,
    QUEST_RUSE_OF_THE_ASHTONGUE = 10946,
};

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

        struct boss_alarAI : public BossAI
        {
            boss_alarAI(Creature* creature) : BossAI(creature, DATA_ALAR)
            {
                startPath = true;
                SetCombatMovement(false);
            }

            uint8 platform;
            uint8 noQuillTimes;
            bool startPath;

            void JustReachedHome()
            {
                BossAI::JustReachedHome();
                startPath = true;
            }

            void Reset()
            {
                BossAI::Reset();
                platform = 0;
                noQuillTimes = 0;
                me->SetModelVisible(true);
                me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_FIRE, true);
                me->SetReactState(REACT_AGGRESSIVE);
            }

            void EnterCombat(Unit* who)
            {
                BossAI::EnterCombat(who);
                events.ScheduleEvent(EVENT_SWITCH_PLATFORM, 0);
            }

            void JustDied(Unit* killer)
            {
                me->SetModelVisible(true);
                BossAI::JustDied(killer);

                // Xinef: Ruse of the Ashtongue (10946)
                Map::PlayerList const& pl = me->GetMap()->GetPlayers();
                for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
                {
                    Player* player = itr->GetSource();
                    if (player->GetQuestStatus(QUEST_RUSE_OF_THE_ASHTONGUE) == QUEST_STATUS_INCOMPLETE)
                        if (player->HasAura(SPELL_ASHTONGUE_RUSE))
                            player->AreaExploredOrEventHappens(QUEST_RUSE_OF_THE_ASHTONGUE);
                }
            }

            void JustSummoned(Creature* summon)
            {
                summons.Summon(summon);
                if (summon->GetEntry() == NPC_EMBER_OF_ALAR)    
                    summon->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_FIRE, true);
            }

            void MoveInLineOfSight(Unit* /*who*/) { }

            void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask)
            {
                if (damage >= me->GetHealth() && platform < POINT_MIDDLE)
                {
                    damage = 0;
                    if (events.GetNextEventTime(EVENT_REBIRTH) == 0)
                    {
                        me->InterruptNonMeleeSpells(false);
                        me->SetHealth(me->GetMaxHealth());
                        me->SetReactState(REACT_PASSIVE);
                        me->CastSpell(me, SPELL_EMBER_BLAST, true);

                        me->setAttackTimer(BASE_ATTACK, 16000);
                        events.Reset();
                        events.ScheduleEvent(EVENT_RELOCATE_MIDDLE, 8000);
                        events.ScheduleEvent(EVENT_MOVE_TO_PHASE_2, 12000);
                        events.ScheduleEvent(EVENT_REBIRTH, 16001);
                    }
                }
            }

            void MovementInform(uint32 type, uint32 id)
            {
                if (type != POINT_MOTION_TYPE)
                {
                    if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized() && !me->IsInCombat())
                        startPath = true;
                    return;
                }

                if (id == POINT_PLATFORM)
                    me->setAttackTimer(BASE_ATTACK, 1000);
                else if (id == POINT_QUILL)
                    events.ScheduleEvent(EVENT_START_QUILLS, 1000);
                else if (id == POINT_DIVE)
                {
                    events.ScheduleEvent(EVENT_START_DIVE, 1000);
                    events.ScheduleEvent(EVENT_CAST_DIVE_BOMB, 5000);
                }
            }

            void UpdateAI(uint32 diff)
            {
                if (startPath)
                {
                    me->StopMoving();
                    startPath = false;
                    if (WaypointPath const* i_path = sWaypointMgr->GetPath(me->GetWaypointPath()))
                    {
                        Movement::PointsArray pathPoints;
                        pathPoints.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
                        for (uint8 i = 0; i < i_path->size(); ++i)
                        {
                            WaypointData const* node = i_path->at(i);
                            pathPoints.push_back(G3D::Vector3(node->x, node->y, node->z));
                        }
                        me->GetMotionMaster()->MoveSplinePath(&pathPoints);
                    }
                }

                if (!UpdateVictim())
                    return;

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

                switch (events.ExecuteEvent())
                {
                    case EVENT_SWITCH_PLATFORM:
                        if (roll_chance_i(20*noQuillTimes))
                        {
                            noQuillTimes = 0;
                            platform = RAND(0, 3);
                            me->GetMotionMaster()->MovePoint(POINT_QUILL, alarPoints[POINT_QUILL], false, true);
                            events.ScheduleEvent(EVENT_SWITCH_PLATFORM, 16000);
                        }
                        else
                        {
                            if (noQuillTimes++ > 0)
                            {
                                me->SetOrientation(alarPoints[platform].GetOrientation());
                                me->SummonCreature(NPC_EMBER_OF_ALAR, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 6000);
                            }
                            me->GetMotionMaster()->MovePoint(POINT_PLATFORM, alarPoints[platform], false, true);
                            platform = (++platform)%4;
                            events.ScheduleEvent(EVENT_SWITCH_PLATFORM, 30000);
                        }
                        me->setAttackTimer(BASE_ATTACK, 20000);
                        break;
                    case EVENT_START_QUILLS:
                        me->CastSpell(me, SPELL_FLAME_QUILLS, false);
                        break;
                    case EVENT_RELOCATE_MIDDLE:
                        me->SetPosition(alarPoints[POINT_MIDDLE]);
                        break;
                    case EVENT_MOVE_TO_PHASE_2:
                        me->RemoveAurasDueToSpell(SPELL_EMBER_BLAST);
                        me->CastSpell(me, SPELL_REBIRTH_PHASE2, false);
                        break;
                    case EVENT_REBIRTH:
                        me->SetReactState(REACT_AGGRESSIVE);
                        platform = POINT_MIDDLE;
                        me->GetMotionMaster()->MoveChase(me->GetVictim());

                        events.ScheduleEvent(EVENT_SPELL_MELT_ARMOR, 67000);
                        events.ScheduleEvent(EVENT_SPELL_CHARGE, 10000);
                        events.ScheduleEvent(EVENT_SPELL_FLAME_PATCH, 20000);
                        events.ScheduleEvent(EVENT_SPELL_DIVE_BOMB, 30000);
                        break;
                    case EVENT_SPELL_MELT_ARMOR:
                        me->CastSpell(me->GetVictim(), SPELL_MELT_ARMOR, false);
                        events.ScheduleEvent(EVENT_SPELL_MELT_ARMOR, 60000);
                        break;
                    case EVENT_SPELL_CHARGE:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 50.0f, true))
                            me->CastSpell(target, SPELL_CHARGE, false);
                        events.ScheduleEvent(EVENT_SPELL_CHARGE, 30000);
                        break;
                    case EVENT_SPELL_FLAME_PATCH:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 50.0f, true))
                            me->SummonCreature(NPC_FLAME_PATCH, *target, TEMPSUMMON_TIMED_DESPAWN, 2*MINUTE*IN_MILLISECONDS);
                        events.ScheduleEvent(EVENT_SPELL_FLAME_PATCH, 30000);
                        break;
                    case EVENT_SPELL_DIVE_BOMB:
                        me->GetMotionMaster()->MovePoint(POINT_DIVE, alarPoints[POINT_DIVE], false, true);
                        events.ScheduleEvent(EVENT_SPELL_DIVE_BOMB, 30000);
                        events.DelayEvents(15000);
                        me->setAttackTimer(BASE_ATTACK, 20000);
                        break;
                    case EVENT_START_DIVE:
                        me->CastSpell(me, SPELL_DIVE_BOMB_VISUAL, false);
                        break;
                    case EVENT_CAST_DIVE_BOMB:
                        events.ScheduleEvent(EVENT_SUMMON_DIVE_PHOENIX, 2000);
                        events.ScheduleEvent(EVENT_REBIRTH_DIVE, 6000);
                        events.ScheduleEvent(EVENT_FINISH_DIVE, 10000);
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 90.0f, true))
                        {
                            me->CastSpell(target, SPELL_DIVE_BOMB, false);
                            me->SetPosition(*target);
                            me->StopMovingOnCurrentPos();
                        }

                        me->RemoveAurasDueToSpell(SPELL_DIVE_BOMB_VISUAL);
                        break;
                    case EVENT_SUMMON_DIVE_PHOENIX:
                    {
                        Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 10.0f, true);
                        me->SummonCreature(NPC_EMBER_OF_ALAR, target ? *target : *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 6000);
                        me->SummonCreature(NPC_EMBER_OF_ALAR, target ? *target : *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 6000);
                        break;
                    }
                    case EVENT_REBIRTH_DIVE:
                        me->SetModelVisible(true);
                        me->CastSpell(me, SPELL_REBIRTH_DIVE, false);
                        break;
                    case EVENT_FINISH_DIVE:
                        me->GetMotionMaster()->MoveChase(me->GetVictim());
                        break;
                    case EVENT_SPELL_BERSERK:
                        me->CastSpell(me, SPELL_BERSERK, true);
                        break;
                }

                if (me->isAttackReady())
                {
                    if (me->IsWithinMeleeRange(me->GetVictim()))
                    {
                        me->AttackerStateUpdate(me->GetVictim());
                        me->resetAttackTimer();
                    }
                    else
                    {
                        me->resetAttackTimer();
                        ThreatContainer::StorageType const &threatList = me->getThreatManager().getThreatList();
                        for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
                            if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
                                if (me->IsWithinMeleeRange(unit))
                                {
                                    me->AttackerStateUpdate(unit);
                                    return;
                                }

                        me->CastSpell(me, SPELL_FLAME_BUFFET, false);
                    }
                }
            }
        };

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

class CastQuill : public BasicEvent
{
    public:
        CastQuill(Unit* caster, uint32 spellId) : _caster(caster), _spellId(spellId)
        {
        }

        bool Execute(uint64 /*execTime*/, uint32 /*diff*/)
        {
            _caster->CastSpell(_caster, _spellId, true);
            return true;
        }

    private:
        Unit* _caster;
        uint32 _spellId;
};

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

        class spell_alar_flame_quills_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_alar_flame_quills_AuraScript);

            void HandlePeriodic(AuraEffect const* aurEff)
            {
                PreventDefaultAction();

                // 24 spells in total
                for (uint8 i = 0; i < 21; ++i)
                    GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_1+i), GetUnitOwner()->m_Events.CalculateTime(i*40));
                GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2+0), GetUnitOwner()->m_Events.CalculateTime(22*40));
                GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2+1), GetUnitOwner()->m_Events.CalculateTime(23*40));
                GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2+2), GetUnitOwner()->m_Events.CalculateTime(24*40));
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_alar_flame_quills_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
            }
        };

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

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

        class spell_alar_ember_blast_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_alar_ember_blast_SpellScript);

            void HandleForceCast(SpellEffIndex effIndex)
            {
                PreventHitEffect(effIndex);
                if (InstanceScript* instance = GetCaster()->GetInstanceScript())
                    if (Creature* alar = ObjectAccessor::GetCreature(*GetCaster(), instance->GetData64(NPC_ALAR)))
                        Unit::DealDamage(GetCaster(), alar, alar->CountPctFromMaxHealth(2));
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_alar_ember_blast_SpellScript::HandleForceCast, EFFECT_2, SPELL_EFFECT_FORCE_CAST);
            }
        };

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

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

        class spell_alar_ember_blast_death_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_alar_ember_blast_death_AuraScript);

            void OnApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                PreventDefaultAction(); // xinef: prevent default action after change that invisibility in instances is executed instantly even for creatures
                Unit* target = GetTarget();
                InvisibilityType type = InvisibilityType(aurEff->GetMiscValue());
                target->m_invisibility.AddFlag(type);
                target->m_invisibility.AddValue(type, aurEff->GetAmount());

                GetUnitOwner()->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                GetUnitOwner()->SetStandState(UNIT_STAND_STATE_DEAD);
                GetUnitOwner()->m_last_notify_position.Relocate(0.0f, 0.0f, 0.0f);
                GetUnitOwner()->m_delayed_unit_relocation_timer = 1000;
            }

            void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                GetUnitOwner()->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                GetUnitOwner()->SetStandState(UNIT_STAND_STATE_STAND);
            }

            void Register()
            {
                OnEffectApply += AuraEffectApplyFn(spell_alar_ember_blast_death_AuraScript::OnApply, EFFECT_2, SPELL_AURA_MOD_INVISIBILITY, AURA_EFFECT_HANDLE_REAL);
                OnEffectRemove += AuraEffectRemoveFn(spell_alar_ember_blast_death_AuraScript::OnRemove, EFFECT_2, SPELL_AURA_MOD_INVISIBILITY, AURA_EFFECT_HANDLE_REAL);
            }
        };

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

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

        class spell_alar_dive_bomb_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_alar_dive_bomb_AuraScript);

            void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                GetUnitOwner()->SetModelVisible(false);
                GetUnitOwner()->SetDisplayId(DISPLAYID_INVISIBLE);
            }

            void Register()
            {
                OnEffectApply += AuraEffectApplyFn(spell_alar_dive_bomb_AuraScript::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
            }
        };

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

void AddSC_boss_alar()
{
    new boss_alar();
    new spell_alar_flame_quills();
    new spell_alar_ember_blast();
    new spell_alar_ember_blast_death();
    new spell_alar_dive_bomb();
}
