/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Player.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellInfo.h"
#include "onyxias_lair.h"

enum Spells
{
    SPELL_WINGBUFFET                = 18500,
    SPELL_FLAMEBREATH               = 18435,
    SPELL_CLEAVE                    = 68868,
    //SPELL_TAILSWEEP                 = 68867,
    SPELL_TAILSWEEP                 = 15847,
    SPELL_FIREBALL                  = 18392,
    SPELL_BELLOWINGROAR             = 18431,

    SPELL_SUMMON_WHELP              = 91003,
    SPELL_SUMMON_LAIR_GUARD         = 68968,
    SPELL_ERUPTION                  = 17731,

    SPELL_OLG_BLASTNOVA             = 68958,
    SPELL_OLG_IGNITEWEAPON          = 68959,

    SPELL_BREATH_N_TO_S             = 17086,
    SPELL_BREATH_S_TO_N             = 18351,
    SPELL_BREATH_E_TO_W             = 18576,
    SPELL_BREATH_W_TO_E             = 18609,
    SPELL_BREATH_SE_TO_NW           = 18564,
    SPELL_BREATH_NW_TO_SE           = 18584,
    SPELL_BREATH_SW_TO_NE           = 18596,
    SPELL_BREATH_NE_TO_SW           = 18617,
};

enum Events
{
    EVENT_SPELL_WINGBUFFET          = 1,
    EVENT_SPELL_FLAMEBREATH         = 2,
    EVENT_SPELL_TAILSWEEP           = 3,
    EVENT_SPELL_CLEAVE              = 4,
    EVENT_START_PHASE_2             = 5,
    EVENT_SPELL_FIREBALL_FIRST      = 6,
    EVENT_SPELL_FIREBALL_SECOND     = 7,
    EVENT_PHASE_2_STEP_CW           = 8,
    EVENT_PHASE_2_STEP_ACW          = 9,
    EVENT_PHASE_2_STEP_ACROSS       = 10,
    EVENT_SPELL_BREATH              = 11,
    EVENT_START_PHASE_3             = 12,
    EVENT_PHASE_3_ATTACK            = 13,
    EVENT_SPELL_BELLOWINGROAR       = 14,
    EVENT_WHELP_SPAM                = 15,
    EVENT_SUMMON_LAIR_GUARD         = 16,
    EVENT_SUMMON_WHELP              = 17,
    EVENT_OLG_SPELL_BLASTNOVA       = 18,
    EVENT_OLG_SPELL_IGNITEWEAPON    = 19,
    EVENT_ERUPTION                  = 20,

    EVENT_LIFTOFF                   = 31,
    EVENT_FLY_S_TO_N                = 32,
    EVENT_LAND                      = 33,
    EVENT_END_MANY_WHELPS_TIME
};

enum Phases
{
    PHASE_NONE,
    PHASE_GROUNDED, // Phase 1
    PHASE_AIRPHASE, // Phase 2 - Airphase - 60% health
    PHASE_LANDED    // Phase 3 - Landed after Airphase - 40% health
};

struct sOnyxMove
{
    uint8 CurrId, DestId;
    uint32 spellId;
    float x, y, z, o;
};

static sOnyxMove OnyxiaMoveData[] =
        {
                {0, 0, 0, -64.496f, -214.906f, -84.4f, 0.0f}, // south ground
                {1, 5, SPELL_BREATH_S_TO_N, -64.496f, -214.906f, -60.0f, 0.0f}, // south
                {2, 6, SPELL_BREATH_SW_TO_NE, -59.809f, -190.758f, -60.0f, 7 * M_PI / 4}, // south-west
                {3, 7, SPELL_BREATH_W_TO_E, -29.450f, -180.600f, -60.0f, M_PI + M_PI / 2}, // west
                {4, 8, SPELL_BREATH_NW_TO_SE, 6.895f, -180.246f, -60.0f, M_PI + M_PI / 4}, // north-west
                {5, 1, SPELL_BREATH_N_TO_S,  22.876f, -217.152f, -60.0f, M_PI}, // north
                {6, 2, SPELL_BREATH_NE_TO_SW, 10.2191f, -247.912f, -60.0f, 3 * M_PI / 4}, // north-east
                {7, 3, SPELL_BREATH_E_TO_W, -31.496f, -250.123f, -60.0f, M_PI / 2}, // east
                {8, 4, SPELL_BREATH_SE_TO_NW, -63.5156f, -240.096f, -60.0f, M_PI / 4}, // south-east
        };

enum Yells
{
    // Say
    SAY_AGGRO                   = 0,
    SAY_KILL                    = 1,
    SAY_PHASE_2_TRANS           = 2,
    SAY_PHASE_3_TRANS           = 3,
    EMOTE_BREATH                = 4,
    SAY_EVADE                   = 5
};


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

    struct boss_onyxiaAI_40 : public BossAI
    {
    public:
        boss_onyxiaAI_40(Creature* pCreature) : BossAI(pCreature, DATA_ONYXIA)
        {
            Initialize();
        }

        void Initialize()
        {
            CurrentWP            = 0;
            whelpSpam            = false;
            whelpCount           = 0;
            whelpSpamTimer       = 0;
            bManyWhelpsAvailable = false;

            // Immune to taunt in vanilla
            me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true);
            me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
        }

        void SetPhase(uint8 ph)
        {
            events.Reset();
            Phase = ph;
            switch (ph)
            {
                case PHASE_GROUNDED:
                    events.ScheduleEvent(EVENT_SPELL_WINGBUFFET, Milliseconds(urand(10000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_FLAMEBREATH, Milliseconds(urand(10000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_TAILSWEEP, Milliseconds(urand(15000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_CLEAVE, Milliseconds(urand(2000, 5000)));
                    break;
                case PHASE_AIRPHASE:
                    events.ScheduleEvent(EVENT_START_PHASE_2, Milliseconds(0));
                    break;
                case PHASE_LANDED:
                    events.ScheduleEvent(EVENT_START_PHASE_3, Milliseconds(5000));
                    break;
            }
        }

        void Reset() override
        {
            Initialize();
            SetPhase(PHASE_NONE);
            me->SetReactState(REACT_AGGRESSIVE);
            me->SetCanFly(false);
            me->SetDisableGravity(false);
            me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run, false);
            BossAI::Reset();
        }

        void DoAction(int32 param) override
        {
            switch (param)
            {
                case -1:
                    if (bManyWhelpsAvailable)
                    {
                        instance->SetData(DATA_WHELP_SUMMONED, 1);
                    }
                    break;
            }
        }

        void JustEngagedWith(Unit* who) override
        {
            Talk(SAY_AGGRO);
            SetPhase(PHASE_GROUNDED);

            BossAI::JustEngagedWith(who);
        }

        void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
        {
            if (me->HealthBelowPctDamaged(65, damage) && Phase == PHASE_GROUNDED)
            {
                SetPhase(PHASE_AIRPHASE);
            }
            else if (me->HealthBelowPctDamaged(40, damage) && Phase == PHASE_AIRPHASE)
            {
                SetPhase(PHASE_LANDED);
            }
        }

        void JustSummoned(Creature* summon) override
        {
            if (summon->GetEntry() == NPC_ONYXIAN_WHELP || summon->GetEntry() == NPC_ONYXIAN_LAIR_GUARD)
            {
                if (Unit* target = summon->SelectNearestTarget(300.0f))
                {
                    summon->AI()->AttackStart(target);
                    DoZoneInCombat(summon);
                }

                summons.Summon(summon);
            }
        }

        void MovementInform(uint32 type, uint32 id) override
        {
            if (type != POINT_MOTION_TYPE && type != EFFECT_MOTION_TYPE)
            {
                return;
            }

            if (id < 9)
            {
                if (id > 0 && Phase == PHASE_AIRPHASE)
                {
                    me->SetFacingTo(OnyxiaMoveData[id].o);
                    me->SetSpeed(MOVE_RUN, 1.6f, false);
                    CurrentWP = id;
                    events.ScheduleEvent(EVENT_SPELL_FIREBALL_FIRST, Milliseconds(1000));
                }
            }
            else
            {
                switch (id)
                {
                    case 10:
                        me->SetFacingTo(OnyxiaMoveData[0].o);
                        events.ScheduleEvent(EVENT_LIFTOFF, Milliseconds(0));
                        break;
                    case 11:
                        me->SetFacingTo(OnyxiaMoveData[1].o);
                        events.ScheduleEvent(EVENT_FLY_S_TO_N, Milliseconds(0));
                        break;
                    case 12:
                        me->SetFacingTo(OnyxiaMoveData[1].o);
                        events.ScheduleEvent(EVENT_LAND, Milliseconds(0));
                        break;
                    case 13:
                        me->SetCanFly(false);
                        me->SetDisableGravity(false);
                        me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run, false);
                        events.ScheduleEvent(EVENT_PHASE_3_ATTACK, Milliseconds(0));
                        break;
                }
            }
        }

        void HandleWhelpSpam(const uint32 diff)
        {
            if (whelpSpam)
            {
                if (whelpCount < 40)
                {
                    whelpSpamTimer -= diff;
                    if (whelpSpamTimer <= 0)
                    {
                        // Coordinates for the cave mouths
                        float caveMouths[2][3] = {
                            {-31.71, -170.55, -89.72},
                            {-32.086, -258.55, -89.72},
                        };

                        // Randomly select one of the cave mouths for spawning
                        uint8 selectedCave = urand(0, 1);

                        // Summon a whelp at the selected cave mouth
                        Creature* whelp = me->SummonCreature(NPC_ONYXIAN_WHELP_40, caveMouths[selectedCave][0], caveMouths[selectedCave][1], caveMouths[selectedCave][2], 0);

                        if (whelp)
                        {
                            // Find the nearest player and attack
                            if (Unit* target = whelp->SelectNearestTarget(300.0f))
                            {
                                whelp->AI()->AttackStart(target);
                            }
                        }

                        // Increment the whelp count and reset the timer
                        whelpCount += 1;
                        whelpSpamTimer += 600;
                    }
                }
                else
                {
                    whelpSpam = false;
                    whelpCount = 0;
                    whelpSpamTimer = 0;
                }
            }
        }


    bool CheckInRoom() override
    {
        if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 95.0f)
        {
            Talk(SAY_EVADE);
            EnterEvadeMode();
            return false;
        }

        return true;
    }

        void UpdateAI(uint32 diff) override
        {
        if (!UpdateVictim() || !CheckInRoom())
            {
                return;
            }

            events.Update(diff);
            HandleWhelpSpam(diff);

            if (me->HasUnitState(UNIT_STATE_CASTING))
            {
                return;
            }

            DoMeleeAttackIfReady();

            switch (events.ExecuteEvent())
            {
                case EVENT_SPELL_WINGBUFFET:
                {
                    DoCast(SPELL_WINGBUFFET);
                    events.Repeat(Milliseconds(urand(15000, 30000)));
                    break;
                }
                case EVENT_SPELL_FLAMEBREATH:
                {
                    DoCast(SPELL_FLAMEBREATH);
                    events.Repeat(Milliseconds(urand(10000, 20000)));
                    break;
                }
                case EVENT_SPELL_TAILSWEEP:
                {
                    DoCastAOE(SPELL_TAILSWEEP);
                    events.Repeat(Milliseconds(urand(15000, 20000)));
                    break;
                }
                case EVENT_SPELL_CLEAVE:
                {
                    DoCastVictim(SPELL_CLEAVE);
                    events.Repeat(Milliseconds(urand(2000, 5000)));
                    break;
                }
                case EVENT_START_PHASE_2:
                {
                    me->AttackStop();
                    me->SetReactState(REACT_PASSIVE);
                    me->StopMoving();
                    DoResetThreatList();
                    me->GetMotionMaster()->MovePoint(10, OnyxiaMoveData[0].x, OnyxiaMoveData[0].y, OnyxiaMoveData[0].z);
                    break;
                }
                case EVENT_LIFTOFF:
                {
                    Talk(SAY_PHASE_2_TRANS);
                    me->SendMeleeAttackStop(me->GetVictim());
                    me->GetMotionMaster()->MoveIdle();
                    me->DisableSpline();
                    me->SetCanFly(true);
                    me->SetDisableGravity(true);
                    me->SetOrientation(OnyxiaMoveData[0].o);
                    me->SendMovementFlagUpdate();
                    me->GetMotionMaster()->MoveTakeoff(11, OnyxiaMoveData[1].x + 1.0f, OnyxiaMoveData[1].y, OnyxiaMoveData[1].z, 12.0f);
                    bManyWhelpsAvailable = true;

                    events.RescheduleEvent(EVENT_END_MANY_WHELPS_TIME, Milliseconds(10000));
                    break;
                }
                case EVENT_END_MANY_WHELPS_TIME:
                    bManyWhelpsAvailable = false;
                    break;
                case EVENT_FLY_S_TO_N:
                {
                    me->SetSpeed(MOVE_RUN, 2.95f, false);
                    me->GetMotionMaster()->MovePoint(5, OnyxiaMoveData[5].x, OnyxiaMoveData[5].y, OnyxiaMoveData[5].z);

                    whelpSpam = true;
                    events.ScheduleEvent(EVENT_WHELP_SPAM, Milliseconds(90000));
                    //events.ScheduleEvent(EVENT_SUMMON_LAIR_GUARD, Milliseconds(30000));
                    break;
                }
                case EVENT_SUMMON_LAIR_GUARD:
                {
                    me->CastSpell(-101.654f, -214.491f, -80.70f, SPELL_SUMMON_LAIR_GUARD, true);
                    events.Repeat(Milliseconds(30000));
                    break;
                }
                case EVENT_WHELP_SPAM:
                {
                    whelpSpam = true;
                    events.Repeat(Milliseconds(90000));
                    break;
                }
                case EVENT_LAND:
                {
                    Talk(SAY_PHASE_3_TRANS);
                    me->SendMeleeAttackStop(me->GetVictim());
                    me->GetMotionMaster()->MoveLand(13, OnyxiaMoveData[0].x + 1.0f, OnyxiaMoveData[0].y, OnyxiaMoveData[0].z, 12.0f);
                    DoResetThreatList();
                    break;
                }
                case EVENT_SPELL_FIREBALL_FIRST:
                {
                    if (Unit* v = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
                    {
                        me->SetFacingToObject(v);
                        DoCast(v, SPELL_FIREBALL);
                    }

                    events.ScheduleEvent(EVENT_SPELL_FIREBALL_SECOND, Milliseconds(4000));
                    break;
                }
                case EVENT_SPELL_FIREBALL_SECOND:
                {
                    if (Unit* v = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
                    {
                        me->SetFacingToObject(v);
                        DoCast(v, SPELL_FIREBALL);
                    }

                    uint8 rand = urand(0, 99);
                    if (rand < 33)
                    {
                        events.ScheduleEvent(EVENT_PHASE_2_STEP_CW, Milliseconds(4000));
                    }
                    else if (rand < 66)
                    {
                        events.ScheduleEvent(EVENT_PHASE_2_STEP_ACW, Milliseconds(4000));
                    }
                    else
                    {
                        events.ScheduleEvent(EVENT_PHASE_2_STEP_ACROSS, Milliseconds(4000));
                    }
                    break;
                }
                case EVENT_PHASE_2_STEP_CW:
                {
                    uint8 newWP = CurrentWP + 1;
                    if (newWP > 8)
                    {
                        newWP = 1;
                    }
                    me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
                    break;
                }
                case EVENT_PHASE_2_STEP_ACW:
                {
                    uint8 newWP = CurrentWP - 1;
                    if (newWP < 1)
                    {
                        newWP = 8;
                    }
                    me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
                    break;
                }
                case EVENT_PHASE_2_STEP_ACROSS:
                {
                    Talk(EMOTE_BREATH);
                    me->SetFacingTo(OnyxiaMoveData[CurrentWP].o);
                    DoCastAOE(OnyxiaMoveData[CurrentWP].spellId);
                    events.ScheduleEvent(EVENT_SPELL_BREATH, Milliseconds(8250));
                    break;
                }
                case EVENT_SPELL_BREATH:
                {
                    uint8 newWP = OnyxiaMoveData[CurrentWP].DestId;
                    me->SetSpeed(MOVE_RUN, 2.95f, false);
                    me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
                    break;
                }
                case EVENT_START_PHASE_3:
                {
                    me->SetSpeed(MOVE_RUN, 2.95f, false);
                    me->GetMotionMaster()->MovePoint(12, OnyxiaMoveData[1].x, OnyxiaMoveData[1].y, OnyxiaMoveData[1].z);
                    break;
                }
                case EVENT_PHASE_3_ATTACK:
                {
                    me->SetReactState(REACT_AGGRESSIVE);

                    if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat, 0, 0, false))
                    {
                        AttackStart(target);
                    }

                    DoCastAOE(SPELL_BELLOWINGROAR);

                    events.ScheduleEvent(EVENT_ERUPTION, Milliseconds(0));
                    events.ScheduleEvent(EVENT_SPELL_WINGBUFFET, Milliseconds(urand(10000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_FLAMEBREATH, Milliseconds(urand(10000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_TAILSWEEP, Milliseconds(urand(15000, 20000)));
                    events.ScheduleEvent(EVENT_SPELL_CLEAVE, Milliseconds(urand(2000, 5000)));
                    events.ScheduleEvent(EVENT_SPELL_BELLOWINGROAR, Milliseconds(15000));
                    events.ScheduleEvent(EVENT_SUMMON_WHELP, Milliseconds(10000));
                    break;
                }
                case EVENT_SPELL_BELLOWINGROAR:
                {
                    DoCastAOE(SPELL_BELLOWINGROAR);
                    events.Repeat(Milliseconds(22000));
                    events.ScheduleEvent(EVENT_ERUPTION, Milliseconds(0));
                    break;
                }
                case EVENT_ERUPTION:
                {
                    if (Creature* trigger = me->SummonCreature(12758, *me, TEMPSUMMON_TIMED_DESPAWN, 1000))
                    {
                        trigger->CastSpell(trigger, 17731, false);
                    }
                    break;
                }
                case EVENT_SUMMON_WHELP:
                {
                    float angle = rand_norm() * 2 * M_PI;
                    float dist  = rand_norm() * 4.0f;
                    me->CastSpell(-33.18f + cos(angle) * dist, -258.80f + sin(angle) * dist, -89.0f, SPELL_SUMMON_WHELP, true);
                    me->CastSpell(-32.535f + cos(angle) * dist, -170.190f + sin(angle) * dist, -89.0f, SPELL_SUMMON_WHELP, true);
                    events.Repeat(Milliseconds(30000));
                    break;
                }
            }
        }

        void SpellHitTarget(Unit* target, const SpellInfo* spell) override
        {
            if (target->IsPlayer() && spell->DurationEntry && spell->DurationEntry->ID == 328 && spell->Effects[EFFECT_1].TargetA.GetTarget() == 1 && (spell->Effects[EFFECT_1].Amplitude == 50 || spell->Effects[EFFECT_1].Amplitude == 215)) // Deep Breath
            {
                instance->SetData(DATA_DEEP_BREATH_FAILED, 1);
            }
        }

    private:
        uint8 Phase;
        int8  CurrentWP;

        bool  whelpSpam;
        uint8 whelpCount;
        int32 whelpSpamTimer;
        bool  bManyWhelpsAvailable;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return GetOnyxiasLairAI<boss_onyxiaAI_40>(creature);
    };
};

void AddSC_boss_onyxia_40()
{
    new boss_onyxia_40();
}
