/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * 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 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 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/>.
 */

using Framework.Constants;
using Game.AI;
using Game.Entities;
using Game.Maps;
using Game.Scripting;
using System.Collections.Generic;

namespace Scripts.Pets
{
    struct PetMageConst
    {
        public const uint SpellCloneMe = 45204;
        public const uint SpellMastersThreatList = 58838;
        public const uint SpellMageFrostBolt = 59638;
        public const uint SpellMageFireBlast = 59637;


        public const uint TimerMirrorImageInit = 0;
        public const uint TimerMirrorImageFrostBolt = 4000;
        public const uint TimerMirrorImageFireBlast = 6000;
    }


    [Script]
    class npc_pet_mage_mirror_image : CreatureScript
    {
        public npc_pet_mage_mirror_image() : base("npc_pet_mage_mirror_image") { }

        class npc_pet_mage_mirror_imageAI : CasterAI
        {
            public npc_pet_mage_mirror_imageAI(Creature creature) : base(creature) { }

            void Init()
            {
                Unit owner = me.GetCharmerOrOwner();

                List<Unit> targets = new List<Unit>();
                var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f);
                var searcher = new UnitListSearcher(me, targets, u_check);
                Cell.VisitAllObjects(me, searcher, 40.0f);

                Unit highestThreatUnit = null;
                float highestThreat = 0.0f;
                Unit nearestPlayer = null;
                foreach (var unit in targets)
                {
                    // Consider only units without CC
                    if (!unit.HasBreakableByDamageCrowdControlAura(unit))
                    {
                        // Take first found unit
                        if (!highestThreatUnit && !unit.IsTypeId(TypeId.Player))
                        {
                            highestThreatUnit = unit;
                            continue;
                        }
                        if (!nearestPlayer && unit.IsTypeId(TypeId.Player))
                        {
                            nearestPlayer = unit;
                            continue;
                        }
                        // else compare best fit unit with current unit
                        var triggers = unit.GetThreatManager().getThreatList();
                        foreach (var reference in triggers)
                        {
                            // Try to find threat referenced to owner
                            if (reference.getTarget() == owner)
                            {
                                // Check if best fit hostile unit hs lower threat than this current unit
                                if (highestThreat < reference.getThreat())
                                {
                                    // If so, update best fit unit
                                    highestThreat = reference.getThreat();
                                    highestThreatUnit = unit;
                                    break;
                                }
                            }
                        }
                        // In case no unit with threat was found so far, always check for nearest unit (only for players)
                        if (unit.IsTypeId(TypeId.Player))
                        {
                            // If this player is closer than the previous one, update it
                            if (me.GetDistance(unit.GetPosition()) < me.GetDistance(nearestPlayer.GetPosition()))
                                nearestPlayer = unit;
                        }
                    }
                }
                // Prioritize units with threat referenced to owner
                if (highestThreat > 0.0f && highestThreatUnit)
                    me.Attack(highestThreatUnit, false);
                // If there is no such target, try to attack nearest hostile unit if such exists
                else if (nearestPlayer)
                    me.Attack(nearestPlayer, false);
            }

            bool IsInThreatList(Unit target)
            {
                Unit owner = me.GetCharmerOrOwner();

                List<Unit> targets = new List<Unit>();
                var u_check   = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f);
                var searcher   = new UnitListSearcher(me, targets, u_check);
                Cell.VisitAllObjects(me, searcher, 40.0f);

                foreach (var unit in targets)
                {
                    if (unit == target)
                    {
                        // Consider only units without CC
                        if (!unit.HasBreakableByDamageCrowdControlAura(unit))
                        {
                            var triggers = unit.GetThreatManager().getThreatList();
                            foreach (var reference in triggers)
                            {
                                // Try to find threat referenced to owner
                                if (reference.getTarget() == owner)
                                    return true;
                            }
                        }
                    }
                }
                return false;
            }

            public override void InitializeAI()
            {
                base.InitializeAI();
                Unit owner = me.GetOwner();
                if (!owner)
                    return;

                // here mirror image casts on summoner spell (not present in client dbc) 49866
                // here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcasted by mirror images (stats related?)
                // Clone Me!
                owner.CastSpell(me, PetMageConst.SpellCloneMe, false);
            }

            public override void EnterCombat(Unit victim)
            {
                if (me.GetVictim() && !me.GetVictim().HasBreakableByDamageCrowdControlAura(me))
                {
                    me.CastSpell(victim, PetMageConst.SpellMageFireBlast, false);
                    _events.ScheduleEvent(PetMageConst.SpellMageFrostBolt, PetMageConst.TimerMirrorImageInit);
                    _events.ScheduleEvent(PetMageConst.SpellMageFireBlast, PetMageConst.TimerMirrorImageFireBlast);
                }
                else
                    EnterEvadeMode(EvadeReason.Other);
            }

            public override void Reset()
            {
                _events.Reset();
            }

            public override void UpdateAI(uint diff)
            {
                Unit owner = me.GetCharmerOrOwner();
                if (!owner)
                    return;

                Unit target = owner.getAttackerForHelper();

                _events.Update(diff);

                // prevent CC interrupts by images
                if (me.GetVictim() && me.GetVictim().HasBreakableByDamageCrowdControlAura(me))
                {
                    me.InterruptNonMeleeSpells(false);
                    return;
                }

                if (me.HasUnitState(UnitState.Casting))
                    return;

                // assign target if image doesnt have any or the target is not actual
                if (!target || me.GetVictim() != target)
                {
                    Unit ownerTarget = null;
                    Player owner1 = me.GetCharmerOrOwner().ToPlayer();
                    if (owner1)
                        ownerTarget = owner1.GetSelectedUnit();

                    // recognize which victim will be choosen
                    if (ownerTarget && ownerTarget.IsTypeId(TypeId.Player))
                    {
                        if (!ownerTarget.HasBreakableByDamageCrowdControlAura(ownerTarget))
                            me.Attack(ownerTarget, false);
                    }
                    else if (ownerTarget && !ownerTarget.IsTypeId(TypeId.Player) && IsInThreatList(ownerTarget))
                    {
                        if (!ownerTarget.HasBreakableByDamageCrowdControlAura(ownerTarget))
                            me.Attack(ownerTarget, false);
                    }
                    else
                        Init();
                }

                _events.ExecuteEvents(spellId =>
                {
                    if (spellId == PetMageConst.SpellMageFrostBolt)
                    {
                        _events.ScheduleEvent(PetMageConst.SpellMageFrostBolt, PetMageConst.TimerMirrorImageFrostBolt);
                        DoCastVictim(spellId);
                    }
                    else if (spellId == PetMageConst.SpellMageFireBlast)
                    {
                        DoCastVictim(spellId);
                        _events.ScheduleEvent(PetMageConst.SpellMageFireBlast, PetMageConst.TimerMirrorImageFireBlast);
                    }
                });
            }

            // Do not reload Creature templates on evade mode enter - prevent visual lost
            public override void EnterEvadeMode(EvadeReason why)
            {
                if (me.IsInEvadeMode() || !me.IsAlive())
                    return;

                Unit owner = me.GetCharmerOrOwner();

                me.CombatStop(true);
                if (owner && !me.HasUnitState(UnitState.Follow))
                {
                    me.GetMotionMaster().Clear(false);
                    me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active);
                }
                Init();
            }
        }

        public override CreatureAI GetAI(Creature creature)
        {
            return new npc_pet_mage_mirror_imageAI(creature);
        }
    }
}
