/*
 * Originally written by Pussywizard - 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 "ScriptedGossip.h"
#include "ScriptedEscortAI.h"
#include "violet_hold.h"
#include "PassiveAI.h"
#include "SpellScript.h"
#include "Player.h"

/***********
** DEFENSE SYSTEM CRYSTAL
***********/

class go_vh_activation_crystal : public GameObjectScript
{
public:
    go_vh_activation_crystal() : GameObjectScript("go_vh_activation_crystal") { }

    bool OnGossipHello(Player* pPlayer, GameObject* pGo)
    {
        if (InstanceScript* pInstance = pGo->GetInstanceScript())
            pInstance->SetData(DATA_ACTIVATE_DEFENSE_SYSTEM, 1);
        return true;
    }
};

/***********
** SINCLARI
***********/

#define GOSSIP_START_EVENT  "Get your people to safety, we'll keep the Blue Dragonflight's forces at bay."
#define GOSSIP_ITEM_1      "Activate the crystals when we get in trouble, right."
#define GOSSIP_I_WANT_IN    "Sorry, I'm late! Can I get in to help my friends?"

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

    bool OnGossipHello(Player* pPlayer, Creature* pCreature)
    {
        if (InstanceScript* pInstance = pCreature->GetInstanceScript())
            switch (pInstance->GetData(DATA_ENCOUNTER_STATUS))
            {
                case NOT_STARTED:
                    pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_ITEM_1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2);
                    pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_START_EVENT, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
                    pPlayer->SEND_GOSSIP_MENU(13853, pCreature->GetGUID());
                    break;
                case IN_PROGRESS:
                    pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_I_WANT_IN, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+3);
                    pPlayer->SEND_GOSSIP_MENU(13853, pCreature->GetGUID());
                    break;
                default: // DONE or invalid
                    pPlayer->SEND_GOSSIP_MENU(13910, pCreature->GetGUID());
            }
        return true;
    }

    bool OnGossipSelect(Player* pPlayer, Creature* pCreature, uint32 /*uiSender*/, uint32 uiAction)
    {
        pPlayer->PlayerTalkClass->ClearMenus();
        switch(uiAction)
        {
            case GOSSIP_ACTION_INFO_DEF+1:
                pPlayer->CLOSE_GOSSIP_MENU();
                if (InstanceScript *pInstance = pCreature->GetInstanceScript())
                    pInstance->SetData(DATA_START_INSTANCE, 1);
                break;
            case GOSSIP_ACTION_INFO_DEF+2:
                pPlayer->SEND_GOSSIP_MENU(13854, pCreature->GetGUID());
                break;
            case GOSSIP_ACTION_INFO_DEF+3:
                pPlayer->NearTeleportTo(playerTeleportPosition.GetPositionX(), playerTeleportPosition.GetPositionY(), playerTeleportPosition.GetPositionZ(), playerTeleportPosition.GetOrientation(), true);
                pPlayer->CLOSE_GOSSIP_MENU();
                break;
        }
        return true;
    }
};

/***********
** TELEPORTATION PORTAL
***********/

enum PortalEvents
{
    EVENT_SUMMON_KEEPER_OR_GUARDIAN = 1,
    EVENT_SUMMON_KEEPER_TRASH,
    EVENT_SUMMON_ELITES,
    EVENT_SUMMON_SABOTEOUR,
    EVENT_CHECK_DEATHS,
};

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

    CreatureAI* GetAI(Creature *pCreature) const
    {
        return new npc_vh_teleportation_portalAI(pCreature);
    }

    struct npc_vh_teleportation_portalAI : public NullCreatureAI
    {
        npc_vh_teleportation_portalAI(Creature *c) : NullCreatureAI(c), listOfMobs(me)
        {
            pInstance = c->GetInstanceScript();
            events.Reset();
            if (pInstance)
            {
                wave = pInstance->GetData(DATA_WAVE_COUNT);
                bKorG = false;
                spawned = false;

                if (wave < 12)
                    addValue = 0;
                else
                    addValue = 1;

                if (wave%6 != 0)
                    events.RescheduleEvent(RAND(EVENT_SUMMON_KEEPER_OR_GUARDIAN,EVENT_SUMMON_ELITES), 10000);
                else
                    events.RescheduleEvent(EVENT_SUMMON_SABOTEOUR, 3000);
            }
        }

        InstanceScript *pInstance;
        SummonList listOfMobs;
        EventMap events;
        uint8 wave;
        uint8 addValue;
        bool bKorG;
        bool spawned;

        void UpdateAI(uint32 diff)
        {
            if (!pInstance)
                return;

            events.Update(diff);

            switch(events.GetEvent())
            {
                case 0:
                    break;
                case EVENT_SUMMON_KEEPER_OR_GUARDIAN:
                    bKorG = true;
                    spawned = true;
                    if (Creature *c = DoSummon(RAND(NPC_PORTAL_GUARDIAN, NPC_PORTAL_KEEPER), me, 2.0f, 0, TEMPSUMMON_DEAD_DESPAWN))
                        me->CastSpell(c, SPELL_PORTAL_CHANNEL, false);
                    events.PopEvent();
                    events.RescheduleEvent(EVENT_SUMMON_KEEPER_TRASH, 20000);
                    break;
                case EVENT_SUMMON_KEEPER_TRASH:
                    for (uint8 i=0; i<3+addValue; ++i)
                    {
                        uint32 entry = RAND(NPC_AZURE_INVADER_1, NPC_AZURE_INVADER_2, NPC_AZURE_SPELLBREAKER_1, NPC_AZURE_SPELLBREAKER_2, NPC_AZURE_MAGE_SLAYER_1, NPC_AZURE_MAGE_SLAYER_2, NPC_AZURE_BINDER_1, NPC_AZURE_BINDER_2);
                        DoSummon(entry, me, 2.0f, 20000, TEMPSUMMON_DEAD_DESPAWN);
                    }
                    events.RepeatEvent(20000);
                    break;
                case EVENT_SUMMON_ELITES:
                    spawned = true;
                    for (uint8 i=0; i<2+addValue; ++i)
                    {
                        uint32 entry = RAND(NPC_AZURE_CAPTAIN, NPC_AZURE_RAIDER, NPC_AZURE_STALKER, NPC_AZURE_SORCEROR);
                        DoSummon(entry, me, 2.0f, 20000, TEMPSUMMON_DEAD_DESPAWN);
                    }
                    me->SetVisible(false);
                    events.PopEvent();
                    break;
                case EVENT_SUMMON_SABOTEOUR:
                    DoSummon(NPC_SABOTEOUR, me, 2.0f, 0, TEMPSUMMON_CORPSE_DESPAWN);
                    me->DespawnOrUnsummon(3000);
                    events.PopEvent();
                    break;
            }

            if (!spawned)
                return;

            if (bKorG)
            {
                if (!me->IsNonMeleeSpellCast(false)) // keeper/guardian died => channeled spell interrupted
                {
                    // if keeper/guard lost all victims, in enterevademode linking aura is removed, restore it:
                    if (pInstance)
                        for (SummonList::iterator itr = listOfMobs.begin(); itr != listOfMobs.end(); ++itr)
                            if (Creature* c = pInstance->instance->GetCreature(*itr))
                                if (c->IsAlive() && (c->GetEntry() == NPC_PORTAL_GUARDIAN || c->GetEntry() == NPC_PORTAL_KEEPER))
                                {
                                    me->CastSpell(c, SPELL_PORTAL_CHANNEL, false);
                                    return;
                                }
                    Unit::Kill(me, me, false);
                }
            }
            else
            {
                if (listOfMobs.empty())
                    Unit::Kill(me, me, false);
            }
        }

        void JustDied(Unit* /*killer*/)
        {
            events.Reset();
            if (wave%6 == 0) // just to be sure, shouln't occur
                return;
            if (pInstance)
                pInstance->SetData(DATA_PORTAL_DEFEATED, 0);
        }

        void JustSummoned(Creature *pSummoned)
        {
            listOfMobs.Summon(pSummoned);
            if (pSummoned)
                pInstance->SetData64(DATA_ADD_TRASH_MOB, pSummoned->GetGUID());
        }

        void SummonedMobDied(Creature *pSummoned)
        {
            listOfMobs.Despawn(pSummoned);
            if (pSummoned)
                pInstance->SetData64(DATA_DELETE_TRASH_MOB, pSummoned->GetGUID());
        }
    };
};

/***********
** GENERAL TRASH AI
***********/

struct violet_hold_trashAI : public npc_escortAI
{
    violet_hold_trashAI(Creature *c) : npc_escortAI(c)
    {
        pInstance = c->GetInstanceScript();
        if (pInstance)
            PLoc = pInstance->GetData(DATA_PORTAL_LOCATION);
        bAddedWP = false;
        bAlt = false;
    }

    InstanceScript* pInstance;
    bool bAddedWP;
    uint32 PLoc;
    bool bAlt;

    void ClearDoorSealAura()
    {
        if (pInstance)
            if (Creature* c = pInstance->instance->GetCreature(pInstance->GetData64(DATA_DOOR_SEAL_GUID)))
                c->RemoveAura(SPELL_DESTROY_DOOR_SEAL, me->GetGUID());
    }

    void EnterCombat(Unit* who)
    {
        if (!who->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))
        {
            me->InterruptNonMeleeSpells(false);
            me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
        }
    }

    void AttackStart(Unit* who)
    {
        if (!who->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))
            ScriptedAI::AttackStart(who);
    }

    void JustReachedHome()
    {
        CreatureStartAttackDoor();
    }

    void WaypointReached(uint32 id)
    {
        if (PLoc<6)
            if (id == PLocWPCount[PLoc] -1 -(bAlt ? 1 : 0))
                CreatureStartAttackDoor();
    }

    void MoveInLineOfSight(Unit* who)
    {
        ScriptedAI::MoveInLineOfSight(who);
    }

    void UpdateAI(uint32 diff)
    {
        if (!bAddedWP)
        {
            bAddedWP = true;
            switch(PLoc)
            {
                case 0:
                    for(int i=0;i<6;i++)
                        AddWaypoint(i, FirstPortalTrashWPs[i][0]+irand(-1, 1), FirstPortalTrashWPs[i][1]+irand(-1, 1), FirstPortalTrashWPs[i][2]+irand(-1, 1), 0);
                    me->SetHomePosition(FirstPortalTrashWPs[5][0], FirstPortalTrashWPs[5][1], FirstPortalTrashWPs[5][2], 3.149439f);
                    break;
                case 1:
                    bAlt = (bool)urand(0, 1);
                    if (!bAlt)
                    {
                        for(int i=0;i<9;i++)
                            AddWaypoint(i, SecondPortalTrashWPs1[i][0]+irand(-1, 1), SecondPortalTrashWPs1[i][1]+irand(-1, 1), SecondPortalTrashWPs1[i][2], 0);
                        me->SetHomePosition(SecondPortalTrashWPs1[8][0]+irand(-1, 1), SecondPortalTrashWPs1[8][1]+irand(-1, 1), SecondPortalTrashWPs1[8][2]+irand(-1, 1), 3.149439f);
                    }
                    else
                    {
                        for(int i=0;i<8;i++)
                            AddWaypoint(i, SecondPortalTrashWPs2[i][0]+irand(-1, 1), SecondPortalTrashWPs2[i][1]+irand(-1, 1), SecondPortalTrashWPs2[i][2], 0);
                        me->SetHomePosition(SecondPortalTrashWPs2[7][0], SecondPortalTrashWPs2[7][1], SecondPortalTrashWPs2[7][2], 3.149439f);
                    }
                    break;
                case 2:
                    for(int i=0;i<8;i++)
                        AddWaypoint(i, ThirdPortalTrashWPs[i][0]+irand(-1, 1), ThirdPortalTrashWPs[i][1]+irand(-1, 1), ThirdPortalTrashWPs[i][2], 0);
                    me->SetHomePosition(ThirdPortalTrashWPs[7][0], ThirdPortalTrashWPs[7][1], ThirdPortalTrashWPs[7][2], 3.149439f);
                    break;
                case 3:
                    for(int i=0;i<9;i++)
                        AddWaypoint(i, FourthPortalTrashWPs[i][0]+irand(-1, 1), FourthPortalTrashWPs[i][1]+irand(-1, 1), FourthPortalTrashWPs[i][2], 0);
                    me->SetHomePosition(FourthPortalTrashWPs[8][0], FourthPortalTrashWPs[8][1], FourthPortalTrashWPs[8][2], 3.149439f);
                    break;
                case 4:
                    for(int i=0;i<6;i++)
                        AddWaypoint(i, FifthPortalTrashWPs[i][0]+irand(-1, 1), FifthPortalTrashWPs[i][1]+irand(-1, 1), FifthPortalTrashWPs[i][2], 0);
                    me->SetHomePosition(FifthPortalTrashWPs[5][0], FifthPortalTrashWPs[5][1], FifthPortalTrashWPs[5][2], 3.149439f);
                    break;
                case 5:
                    for(int i=0;i<4;i++)
                        AddWaypoint(i, SixthPoralTrashWPs[i][0]+irand(-1, 1), SixthPoralTrashWPs[i][1]+irand(-1, 1), SixthPoralTrashWPs[i][2], 0);
                    me->SetHomePosition(SixthPoralTrashWPs[3][0], SixthPoralTrashWPs[3][1], SixthPoralTrashWPs[3][2], 3.149439f);
                    break;
            }
            SetDespawnAtEnd(false);
            Start(true, true);
        }

        npc_escortAI::UpdateAI(diff);
    }

    void JustDied(Unit* /*unit*/)
    {
        if (pInstance)
            if (Creature* portal = ObjectAccessor::GetCreature((*me), pInstance->GetData64(DATA_TELEPORTATION_PORTAL_GUID)))
                CAST_AI(npc_vh_teleportation_portal::npc_vh_teleportation_portalAI, portal->AI())->SummonedMobDied(me);
    }

    void CreatureStartAttackDoor()
    {
        RemoveEscortState(STATE_ESCORT_ESCORTING | STATE_ESCORT_RETURNING | STATE_ESCORT_PAUSED);
        me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
        me->CastSpell((Unit*)NULL, SPELL_DESTROY_DOOR_SEAL, true);
    }

    void EnterEvadeMode()
    {
        if (!HasEscortState(STATE_ESCORT_ESCORTING))
        {
            me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
            me->SetHomePosition(1845.577759f+rand_norm()*5-2.5f, 800.681152f+rand_norm()*5-2.5f, 44.104248f, M_PI);
        }

        me->DeleteThreatList();
        me->CombatStop(true);
        if (HasEscortState(STATE_ESCORT_ESCORTING))
        {
            AddEscortState(STATE_ESCORT_RETURNING);
            ReturnToLastPoint();
        }
        else
        {
            me->GetMotionMaster()->MoveTargetedHome();
            Reset();
        }
        me->ClearUnitState(UNIT_STATE_EVADE);
    }
};

/***********
** TRASH SPELLS
***********/

enum AzureInvaderSpells
{   
    SPELL_CLEAVE = 15496,
    SPELL_IMPALE_N = 58459,
    SPELL_IMPALE_H = 59256,
    SPELL_BRUTAL_STRIKE = 58460,
    SPELL_SUNDER_ARMOR = 58461,
};
#define SPELL_IMPALE                DUNGEON_MODE(SPELL_IMPALE_N, SPELL_IMPALE_H)

enum AzureSpellbreakerSpells
{
    SPELL_ARCANE_BLAST_N = 58462,
    SPELL_ARCANE_BLAST_H = 59257,
    SPELL_SLOW = 25603,
    SPELL_CHAINS_OF_ICE = 58464,
    SPELL_CONE_OF_COLD_N = 58463,
    SPELL_CONE_OF_COLD_H = 59258
};
#define SPELL_ARCANE_BLAST          DUNGEON_MODE(SPELL_ARCANE_BLAST_N, SPELL_ARCANE_BLAST_H)
#define SPELL_CONE_OF_COLD          DUNGEON_MODE(SPELL_CONE_OF_COLD_N, SPELL_CONE_OF_COLD_H)

enum AzureBinderSpells
{
    SPELL_ARCANE_BARRAGE_N = 58456,
    SPELL_ARCANE_BARRAGE_H = 59248,
    SPELL_ARCANE_EXPLOSION_N = 58455,
    SPELL_ARCANE_EXPLOSION_H = 59245,
    SPELL_FROST_NOVA_N = 58458,
    SPELL_FROST_NOVA_H = 59253,
    SPELL_FROSTBOLT_N = 58457,
    SPELL_FROSTBOLT_H = 59251,
};
#define SPELL_ARCANE_BARRAGE        DUNGEON_MODE(SPELL_ARCANE_BARRAGE_N, SPELL_ARCANE_BARRAGE_H)
#define SPELL_ARCANE_EXPLOSION      DUNGEON_MODE(SPELL_ARCANE_EXPLOSION_N, SPELL_ARCANE_EXPLOSION_H)
#define SPELL_FROST_NOVA            DUNGEON_MODE(SPELL_FROST_NOVA_N, SPELL_FROST_NOVA_H)
#define SPELL_FROSTBOLT             DUNGEON_MODE(SPELL_FROSTBOLT_N, SPELL_FROSTBOLT_H)

enum AzureMageSlayerSpells
{
    SPELL_ARCANE_EMPOWERMENT = 58469,
    SPELL_SPELL_LOCK = 30849
};

enum AzureCaptainSpells
{
    SPELL_MORTAL_STRIKE = 32736,
    SPELL_WHIRLWIND_OF_STEEL = 41056
};

enum AzureSorcerorSpells
{
    SPELL_ARCANE_STREAM_N = 60181,
    SPELL_ARCANE_STREAM_H = 60204,
    SPELL_MANA_DETONATION_N = 60182,
    SPELL_MANA_DETONATION_H = 60205
};
#define SPELL_ARCANE_STREAM         DUNGEON_MODE(SPELL_ARCANE_STREAM_N, SPELL_ARCANE_STREAM_H)
#define SPELL_MANA_DETONATION       DUNGEON_MODE(SPELL_MANA_DETONATION_N, SPELL_MANA_DETONATION_H)

enum AzureRaiderSpells
{
    SPELL_CONCUSSION_BLOW = 52719,
    SPELL_MAGIC_REFLECTION = 60158
};

enum AzureStalkerSpells
{
    SPELL_BACKSTAB = 58471,
    SPELL_TACTICAL_BLINK = 58470
};

/***********
** TRASH
***********/

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_invaderAI (pCreature);
    }

    struct npc_azure_invaderAI : public violet_hold_trashAI
    {
        npc_azure_invaderAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiCleaveTimer;
        uint32 uiImpaleTimer;
        uint32 uiBrutalStrikeTimer;
        uint32 uiSunderArmorTimer;

        void Reset()
        {
            uiCleaveTimer = 5000;
            uiImpaleTimer = 4000;
            uiBrutalStrikeTimer = 5000;
            uiSunderArmorTimer = 4000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (me->GetEntry() == NPC_AZURE_INVADER_1)
            {
                if (uiCleaveTimer <= diff)
                {
                    DoCast(me->GetVictim(), SPELL_CLEAVE);
                    uiCleaveTimer = 5000;
                } else uiCleaveTimer -= diff;

                if (uiImpaleTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 5.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_IMPALE);
                    uiImpaleTimer = 4000;
                } else uiImpaleTimer -= diff;
            }

            if (me->GetEntry() == NPC_AZURE_INVADER_2)
            {
                if (uiBrutalStrikeTimer <= diff)
                {
                    DoCast(me->GetVictim(), SPELL_BRUTAL_STRIKE);
                    uiBrutalStrikeTimer = 5000;
                } else uiBrutalStrikeTimer -= diff;

                if (uiSunderArmorTimer <= diff)
                {
                    DoCast(me->GetVictim(), SPELL_SUNDER_ARMOR);
                    uiSunderArmorTimer = urand(8000, 10000);
                } else uiSunderArmorTimer -= diff;
            }

            DoMeleeAttackIfReady();
        }
    };
};



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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_binderAI (pCreature);
    }

    struct npc_azure_binderAI : public violet_hold_trashAI
    {
        npc_azure_binderAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiArcaneExplosionTimer;
        uint32 uiArcainBarrageTimer;
        uint32 uiFrostNovaTimer;
        uint32 uiFrostboltTimer;

        void Reset()
        {
            uiArcaneExplosionTimer = 5000;
            uiArcainBarrageTimer = 4000;
            uiFrostNovaTimer = 5000;
            uiFrostboltTimer = 4000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (me->GetEntry() == NPC_AZURE_BINDER_1)
            {
                if (uiArcaneExplosionTimer <= diff)
                {
                    DoCast(SPELL_ARCANE_EXPLOSION);
                    uiArcaneExplosionTimer = 5000;
                } else uiArcaneExplosionTimer -= diff;

                if (uiArcainBarrageTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_ARCANE_BARRAGE);
                    uiArcainBarrageTimer = 6000;
                } else uiArcainBarrageTimer -= diff;
            }

            if (me->GetEntry() == NPC_AZURE_BINDER_2)
            {
                if (uiFrostNovaTimer <= diff)
                {
                    DoCast(SPELL_FROST_NOVA);
                    uiFrostNovaTimer = 5000;
                } else uiFrostNovaTimer -= diff;

                if (uiFrostboltTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 40.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_FROSTBOLT);
                    uiFrostboltTimer = 6000;
                } else uiFrostboltTimer -= diff;
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_mage_slayerAI (pCreature);
    }

    struct npc_azure_mage_slayerAI : public violet_hold_trashAI
    {
        npc_azure_mage_slayerAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiArcaneEmpowermentTimer;
        uint32 uiSpellLockTimer;

        void Reset()
        {
            uiArcaneEmpowermentTimer = 5000;
            uiSpellLockTimer = 5000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (me->GetEntry() == NPC_AZURE_MAGE_SLAYER_1)
            {
                if (uiArcaneEmpowermentTimer <= diff)
                {
                    DoCast(me, SPELL_ARCANE_EMPOWERMENT);
                    uiArcaneEmpowermentTimer = 14000;
                } else uiArcaneEmpowermentTimer -= diff;
            }

            if (me->GetEntry() == NPC_AZURE_MAGE_SLAYER_2)
            {
                if (uiSpellLockTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_SPELL_LOCK);
                    uiSpellLockTimer = 9000;
                } else uiSpellLockTimer -= diff;
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_raiderAI (pCreature);
    }

    struct npc_azure_raiderAI : public violet_hold_trashAI
    {
        npc_azure_raiderAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiConcussionBlowTimer;
        uint32 uiMagicReflectionTimer;

        void Reset()
        {
            uiConcussionBlowTimer = 5000;
            uiMagicReflectionTimer = 8000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (uiConcussionBlowTimer <= diff)
            {
                DoCast(me->GetVictim(), SPELL_CONCUSSION_BLOW);
                uiConcussionBlowTimer = 5000;
            } else uiConcussionBlowTimer -= diff;

            if (uiMagicReflectionTimer <= diff)
            {
                DoCast(SPELL_MAGIC_REFLECTION);
                uiMagicReflectionTimer = urand(10000, 15000);
            } else uiMagicReflectionTimer -= diff;

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_stalkerAI (pCreature);
    }

    struct npc_azure_stalkerAI : public violet_hold_trashAI
    {
        npc_azure_stalkerAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiBackstabTimer;
        uint32 uiTacticalBlinkTimer;
        bool TacticalBlinkCasted;

        void Reset()
        {
            uiBackstabTimer = 1300;
            uiTacticalBlinkTimer = 8000;
            TacticalBlinkCasted =false;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            /*if (!TacticalBlinkCasted)
            {
                if (uiTacticalBlinkTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 40.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_TACTICAL_BLINK);
                    uiTacticalBlinkTimer = 10000;
                    TacticalBlinkCasted = true;
                } else uiTacticalBlinkTimer -= diff;
            }

            else*/
            {
                if (uiBackstabTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_NEAREST, 0, 5.0f, true);
                    if (pTarget && !pTarget->HasInArc(M_PI, me))
                        DoCast(pTarget, SPELL_BACKSTAB);
                    TacticalBlinkCasted = false;
                    uiBackstabTimer = 4000;
                } else uiBackstabTimer -= diff;
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_spellbreakerAI (pCreature);
    }

    struct npc_azure_spellbreakerAI : public violet_hold_trashAI
    {
        npc_azure_spellbreakerAI(Creature* c) : violet_hold_trashAI(c) {}

        uint32 uiArcaneBlastTimer;
        uint32 uiSlowTimer;
        uint32 uiChainsOfIceTimer;
        uint32 uiConeOfColdTimer;

        void Reset()
        {
            uiArcaneBlastTimer = 5000;
            uiSlowTimer = 4000;
            uiChainsOfIceTimer = 5000;
            uiConeOfColdTimer = 4000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (me->GetEntry() == NPC_AZURE_SPELLBREAKER_1)
            {
                if (uiArcaneBlastTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_ARCANE_BLAST);
                    uiArcaneBlastTimer = 6000;
                } else uiArcaneBlastTimer -= diff;

                if (uiSlowTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_SLOW);
                    uiSlowTimer = 5000;
                } else uiSlowTimer -= diff;
            }

            if (me->GetEntry() == NPC_AZURE_SPELLBREAKER_2)
            {
                if (uiChainsOfIceTimer <= diff)
                {
                    Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, true);
                    if (pTarget)
                        DoCast(pTarget, SPELL_CHAINS_OF_ICE);
                    uiChainsOfIceTimer = 7000;
                } else uiChainsOfIceTimer -= diff;

                if (uiConeOfColdTimer <= diff)
                {
                   DoCast(SPELL_CONE_OF_COLD);
                    uiConeOfColdTimer = 5000;
                } else uiConeOfColdTimer -= diff;
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_captainAI (pCreature);
    }

    struct  npc_azure_captainAI : public violet_hold_trashAI
    {
        npc_azure_captainAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiMortalStrikeTimer;
        uint32 uiWhirlwindTimer;

        void Reset()
        {
            uiMortalStrikeTimer = 5000;
            uiWhirlwindTimer = 8000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (uiMortalStrikeTimer <= diff)
            {
                DoCast(me->GetVictim(), SPELL_MORTAL_STRIKE);
                uiMortalStrikeTimer = 5000;
            } else uiMortalStrikeTimer -= diff;

            if (uiWhirlwindTimer <= diff)
            {
                DoCastAOE(SPELL_WHIRLWIND_OF_STEEL);
                uiWhirlwindTimer = 8000;
            } else uiWhirlwindTimer -= diff;

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_sorcerorAI (pCreature);
    }

    struct  npc_azure_sorcerorAI : public violet_hold_trashAI
    {
        npc_azure_sorcerorAI(Creature *c) : violet_hold_trashAI(c) {}

        uint32 uiArcaneStreamTimer;
        uint32 uiArcaneStreamTimerStartingValueHolder;
        uint32 uiManaDetonationTimer;

        void Reset()
        {
            uiArcaneStreamTimer = 4000;
            uiArcaneStreamTimerStartingValueHolder = uiArcaneStreamTimer;
            uiManaDetonationTimer = 5000;
        }

        void UpdateAI(uint32 diff)
        {
            violet_hold_trashAI::UpdateAI(diff);

            if (!UpdateVictim())
                return;

            if (uiArcaneStreamTimer <= diff)
            {
                Unit* pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 35.0f, true);
                if (pTarget)
                    DoCast(pTarget, SPELL_ARCANE_STREAM);
                uiArcaneStreamTimer = urand(0, 5000)+5000;
                uiArcaneStreamTimerStartingValueHolder = uiArcaneStreamTimer;
            } else uiArcaneStreamTimer -= diff;

            if (uiManaDetonationTimer <= diff && uiArcaneStreamTimer >=1500 && uiArcaneStreamTimer <= uiArcaneStreamTimerStartingValueHolder/2)
            {
                DoCastAOE(SPELL_MANA_DETONATION);
                uiManaDetonationTimer = urand(2000, 6000);
            } else uiManaDetonationTimer -= diff;

            DoMeleeAttackIfReady();
        }
    };
};

/***********
** SABOTEUR
***********/

enum AzureSaboteurSpells
{
    SABOTEUR_SHIELD_DISRUPTION = 58291,
    SABOTEUR_SHIELD_EFFECT = 45775,
    SPELL_TELEPORT_VISUAL = 52096,
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_azure_saboteurAI (pCreature);
    }

    struct npc_azure_saboteurAI : public npc_escortAI
    {
        npc_azure_saboteurAI(Creature *c) : npc_escortAI(c)
        {
            pInstance = c->GetInstanceScript();
            uiBoss = 0;
            if (pInstance)
                uiBoss = pInstance->GetData(DATA_WAVE_COUNT) == 6 ? pInstance->GetData(DATA_FIRST_BOSS_NUMBER) : pInstance->GetData(DATA_SECOND_BOSS_NUMBER);
            bAddedWPs = false;
            bOpening = false;
        }

        InstanceScript* pInstance;
        bool bAddedWPs;
        uint8 uiBoss;
        bool bOpening;
        uint32 timer;
        uint8 count;

        void WaypointReached(uint32 uiWPointId)
        {
            if (!pInstance)
                return;

            switch(uiBoss)
            {
                case 1:
                    if(uiWPointId == 2)
                        FinishPointReached();
                    break;
                case 2:
                    if(uiWPointId == 2)
                        FinishPointReached();
                    break;
                case 3:
                    if(uiWPointId == 1)
                        FinishPointReached();
                    break;
                case 4:
                    if(uiWPointId == 0)
                        FinishPointReached();
                    break;
                case 5:
                    if(uiWPointId == 0)
                        FinishPointReached();
                    break;
                case 6:
                    if(uiWPointId == 4)
                        FinishPointReached();
                    break;
            }
        }

        void UpdateAI(uint32 diff)
        {
            npc_escortAI::UpdateAI(diff);

            if(!bAddedWPs)
            {
                bAddedWPs = true;
                switch(uiBoss)
                {
                    case 1:
                        for(int i=0;i<3;i++)
                            AddWaypoint(i, SaboteurFinalPos1[i][0], SaboteurFinalPos1[i][1], SaboteurFinalPos1[i][2], 0);
                        me->SetHomePosition(SaboteurFinalPos1[2][0], SaboteurFinalPos1[2][1], SaboteurFinalPos1[2][2], 4.762346f);
                        break;
                    case 2:
                        for(int i=0;i<3;i++)
                            AddWaypoint(i, SaboteurFinalPos2[i][0], SaboteurFinalPos2[i][1], SaboteurFinalPos2[i][2], 0);
                        me->SetHomePosition(SaboteurFinalPos2[2][0], SaboteurFinalPos2[2][1], SaboteurFinalPos2[2][2], 1.862674f);
                        break;
                    case 3:
                        for(int i=0;i<2;i++)
                            AddWaypoint(i, SaboteurFinalPos3[i][0], SaboteurFinalPos3[i][1], SaboteurFinalPos3[i][2], 0);
                        me->SetHomePosition(SaboteurFinalPos3[1][0], SaboteurFinalPos3[1][1], SaboteurFinalPos3[1][2], 5.500638f);
                        break;
                    case 4:
                        AddWaypoint(0, SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 0);
                        me->SetHomePosition(SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 3.991108f);
                        break;
                    case 5:
                        AddWaypoint(0, SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 0);
                        me->SetHomePosition(SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 1.100841f);
                        break;
                    case 6:
                        for(int i=0;i<5;i++)
                            AddWaypoint(i, SaboteurFinalPos6[i][0], SaboteurFinalPos6[i][1], SaboteurFinalPos6[i][2], 0);
                        me->SetHomePosition(SaboteurFinalPos6[4][0], SaboteurFinalPos6[4][1], SaboteurFinalPos6[4][2], 0.983031f);
                        break;
                }
                SetDespawnAtEnd(false);
                Start(true, true);
            }

            if (bOpening)
            {
                if (timer <= diff)
                {
                    if (count < 2)
                    {
                        me->CastSpell(me, SABOTEUR_SHIELD_DISRUPTION, false);
                        timer = 1000;
                    }
                    else if (count == 2)
                    {
                        me->CastSpell(me, SABOTEUR_SHIELD_DISRUPTION, false);
                        if (pInstance)
                            pInstance->SetData(DATA_RELEASE_BOSS, 0);
                        timer = 500;
                    }
                    else
                    {
                        bOpening = false;
                        me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                        me->SetDisplayId(11686);
                        me->CastSpell(me, SPELL_TELEPORT_VISUAL, true);
                        me->DespawnOrUnsummon(1000);
                    }
                    ++count;
                }
                else timer -= diff;
            }
        }

        void FinishPointReached()
        {
            bOpening = true;
            timer = 1000;
            count = 0;
            me->CastSpell(me, SABOTEUR_SHIELD_DISRUPTION, false);
        }

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

/***********
** DESTROY DOOR SEAL SPELL SCRIPT
***********/

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

    class spell_destroy_door_sealAuraScript : public AuraScript
    {
        PrepareAuraScript(spell_destroy_door_sealAuraScript)

        void HandleEffectPeriodic(AuraEffect const * aurEff)
        {
            PreventDefaultAction();
            if (Unit* target = GetTarget())
                if (InstanceScript* pInstance = target->GetInstanceScript())
                    pInstance->SetData(DATA_DECRASE_DOOR_HEALTH, 0);
        }

        void Register()
        {
            OnEffectPeriodic += AuraEffectPeriodicFn(spell_destroy_door_sealAuraScript::HandleEffectPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
        }
    };

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


class go_violet_hold_gate_lever : public GameObjectScript
{
public:
    go_violet_hold_gate_lever() : GameObjectScript("go_violet_hold_gate_lever") { }

    bool OnGossipHello(Player* plr, GameObject* go)
    {
        if (GameObject* gate = go->GetMap()->GetGameObject(MAKE_NEW_GUID(61606, 193019, HIGHGUID_GAMEOBJECT)))
            if (gate->getLootState() == GO_READY)
                gate->UseDoorOrButton(0, false, plr);
        return false;
    }
};

void AddSC_violet_hold()
{
    new go_vh_activation_crystal();
    new npc_vh_sinclari();
    new npc_vh_teleportation_portal();
    new npc_azure_saboteur();

    new npc_azure_invader();
    new npc_azure_spellbreaker();
    new npc_azure_binder();
    new npc_azure_mage_slayer();
    new npc_azure_captain();
    new npc_azure_sorceror();
    new npc_azure_raider();
    new npc_azure_stalker();

    new spell_destroy_door_seal();
    new go_violet_hold_gate_lever();
}
