/*
 * Copyright (C) 
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 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 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/>.
 */

/*
 * Ordered alphabetically using scriptname.
 * Scriptnames of files in this file should be prefixed with "npc_pet_dk_".
 */

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

enum DeathKnightSpells
{
    SPELL_DK_SUMMON_GARGOYLE_1      = 49206,
    SPELL_DK_SUMMON_GARGOYLE_2      = 50514,
    SPELL_DK_DISMISS_GARGOYLE       = 50515,
    SPELL_DK_SANCTUARY              = 54661,
    SPELL_DK_NIGHT_OF_THE_DEAD      = 62137,
    SPELL_DK_PET_SCALING            = 61017
};

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

        struct npc_pet_dk_ebon_gargoyleAI : ScriptedAI
        {
            npc_pet_dk_ebon_gargoyleAI(Creature* creature) : ScriptedAI(creature)
            {
                _despawnTimer = 36000; // 30 secs + 4 fly out + 2 initial attack timer
                _despawning = false;
                _initialSelection = true;
                _targetGUID = 0;
            }

            void MovementInform(uint32 type, uint32 point)
            {
                if (type == POINT_MOTION_TYPE && point == 1)
                {
                    me->SetCanFly(false);
                    me->SetDisableGravity(false);
                }
            }

            void InitializeAI()
            {
                ScriptedAI::InitializeAI();
                Unit* owner = me->GetOwner();
                if (!owner)
                    return;

                // Xinef: Night of the Dead avoidance
                if (Aura *aur = me->GetAura(SPELL_DK_NIGHT_OF_THE_DEAD))
                    if (Unit* owner = me->GetOwner())
                        if (AuraEffect *aurEff = owner->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_DEATHKNIGHT, 2718, 0))
                            if (aur->GetEffect(0))
                                aur->GetEffect(0)->SetAmount(-aurEff->GetSpellInfo()->Effects[EFFECT_2].CalcValue());
                
                float tz = me->GetMap()->GetHeight(me->GetPhaseMask(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), true, MAX_FALL_DISTANCE);
                me->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), tz, 7.0f, 1);
                me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD);
                me->SetCanFly(true);
                me->SetDisableGravity(true);
                _selectionTimer = 2000;
                _initialCastTimer = 0;
            }

            void MySelectNextTarget()
            {
                Unit* owner = me->GetOwner();
                if (owner && owner->GetTypeId() == TYPEID_PLAYER && (!me->GetVictim() || me->GetVictim()->IsImmunedToSpell(sSpellMgr->GetSpellInfo(51963)) || !me->IsValidAttackTarget(me->GetVictim()) || !owner->CanSeeOrDetect(me->GetVictim())))
                {
                    Unit* selection = owner->ToPlayer()->GetSelectedUnit();
                    if (selection && selection != me->GetVictim() && me->IsValidAttackTarget(selection))
                    {
                        me->GetMotionMaster()->Clear(false);
                        SetGazeOn(selection);
                    }
                    else if (!me->GetVictim() || !owner->CanSeeOrDetect(me->GetVictim()))
                    {
                        me->CombatStop(true);
                        me->GetMotionMaster()->Clear(false);
                        me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, 0.0f);
                        RemoveTargetAura();
                    }
                }
            }

            void AttackStart(Unit* who)
            {
                RemoveTargetAura();
                _targetGUID = who->GetGUID();
                me->AddAura(SPELL_DK_SUMMON_GARGOYLE_1, who);
                ScriptedAI::AttackStart(who);
            }

            void RemoveTargetAura()
            {
                if (Unit* target = ObjectAccessor::GetUnit(*me, _targetGUID))
                    target->RemoveAura(SPELL_DK_SUMMON_GARGOYLE_1, me->GetGUID());
            }

            void Reset()
            {
                _selectionTimer = 0;
                me->SetReactState(REACT_PASSIVE);
                MySelectNextTarget();
            }

            // Fly away when dismissed
            void FlyAway()
            {
                RemoveTargetAura();

                // Stop Fighting
                me->CombatStop(true);
                me->ApplyModFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE, true);

                // Sanctuary
                me->CastSpell(me, SPELL_DK_SANCTUARY, true);
                me->SetReactState(REACT_PASSIVE);

                me->SetSpeed(MOVE_FLIGHT, 1.0f, true);
                me->SetSpeed(MOVE_RUN, 1.0f, true);
                float x = me->GetPositionX() + 20 * cos(me->GetOrientation());
                float y = me->GetPositionY() + 20 * sin(me->GetOrientation());
                float z = me->GetPositionZ() + 40;
                me->DisableSpline();
                me->GetMotionMaster()->Clear(false);

                me->GetMotionMaster()->MoveCharge(x, y, z, 7.0f, 1);
                me->SetCanFly(true);
                me->SetDisableGravity(true);

                _despawning = true;
            }

            void UpdateAI(uint32 diff)
            {
                if (_initialSelection)
                {
                    _initialSelection = false;
                    // Find victim of Summon Gargoyle spell
                    std::list<Unit*> targets;
                    Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(me, me, 50);
                    Trinity::UnitListSearcher<Trinity::AnyUnfriendlyUnitInObjectRangeCheck> searcher(me, targets, u_check);
                    me->VisitNearbyObject(50, searcher);
                    for (std::list<Unit*>::const_iterator iter = targets.begin(); iter != targets.end(); ++iter)
                        if ((*iter)->GetAura(SPELL_DK_SUMMON_GARGOYLE_1, me->GetOwnerGUID()))
                        {
                            (*iter)->RemoveAura(SPELL_DK_SUMMON_GARGOYLE_1, me->GetOwnerGUID());
                            SetGazeOn(*iter);
                            _targetGUID = (*iter)->GetGUID();
                            break;
                        }
                }
                if (_despawnTimer > 4000)
                {
                    _despawnTimer -= diff;
                    if (!UpdateVictimWithGaze())
                    {
                        MySelectNextTarget();
                        return;
                    }

                    _initialCastTimer += diff;
                    _selectionTimer += diff;
                    if (_selectionTimer >= 1000)
                    {
                        MySelectNextTarget();
                        _selectionTimer = 0;
                    }
                    if (_initialCastTimer >= 2000 && !me->HasUnitState(UNIT_STATE_CASTING|UNIT_STATE_LOST_CONTROL) && me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
                        me->CastSpell(me->GetVictim(), 51963, false);
                }
                else
                {
                    if (!_despawning)
                        FlyAway();

                    if (_despawnTimer > diff)
                        _despawnTimer -= diff;
                    else
                        me->DespawnOrUnsummon();
                }
            }

        private:
            uint64 _targetGUID;
            uint32 _despawnTimer;
            uint32 _selectionTimer;
            uint32 _initialCastTimer;
            bool _despawning;
            bool _initialSelection;
        };

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

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

        struct npc_pet_dk_ghoulAI : public CombatAI
        {
            npc_pet_dk_ghoulAI(Creature *c) : CombatAI(c) { }

            void JustDied(Unit *who)
            {
                if (me->IsGuardian() || me->IsSummon())
                    me->ToTempSummon()->UnSummon();
            }
        };

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

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

        struct npc_pet_dk_army_of_the_deadAI : public CombatAI
        {
            npc_pet_dk_army_of_the_deadAI(Creature* creature) : CombatAI(creature) { }

            void InitializeAI()
            {
                CombatAI::InitializeAI();
                ((Minion*)me)->SetFollowAngle(rand_norm()*2*M_PI);

                // Heroism / Bloodlust immunity
                me->ApplySpellImmune(0, IMMUNITY_ID, 32182, true);
                me->ApplySpellImmune(0, IMMUNITY_ID, 2825, true);
            }
        };

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

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

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

            void InitializeAI()
            {
                // Xinef: Hit / Expertise scaling
                me->AddAura(61017, me);
                if (Unit* owner = me->GetOwner())
                    me->GetMotionMaster()->MoveFollow(owner, 0.01f, me->GetFollowAngle(), MOTION_SLOT_CONTROLLED);
                NullCreatureAI::InitializeAI();
            }
        };

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

void AddSC_deathknight_pet_scripts()
{
    new npc_pet_dk_ebon_gargoyle();
    new npc_pet_dk_ghoul();
    new npc_pet_dk_army_of_the_dead();
    new npc_pet_dk_dancing_rune_weapon();
}
