/*
 * 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.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Scripting;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Game.AI
{
    public class SmartAI : CreatureAI
    {
        const int SMART_ESCORT_MAX_PLAYER_DIST = 50;
        const int SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2;

        public SmartAI(Creature creature) : base(creature)
        {
            // copy script to local (protection for table reload)

            mWayPoints = null;
            mEscortState = SmartEscortState.None;
            mCurrentWPID = 0;//first wp id is 1 !!
            mWPReached = false;
            mWPPauseTimer = 0;
            mLastWP = null;

            mCanRepeatPath = false;

            // spawn in run mode
            creature.SetWalk(false);
            mRun = false;

            mLastOOCPos = creature.GetPosition();

            mCanAutoAttack = true;
            mCanCombatMove = true;

            mForcedPaused = false;
            mLastWPIDReached = 0;

            mEscortQuestID = 0;

            mDespawnTime = 0;
            mDespawnState = 0;

            mEscortInvokerCheckTimer = 1000;
            mFollowGuid = ObjectGuid.Empty;
            mFollowDist = 0;
            mFollowAngle = 0;
            mFollowCredit = 0;
            mFollowArrivedEntry = 0;
            mFollowCreditType = 0;
            mInvincibilityHpLevel = 0;
        }

        bool IsAIControlled()
        {
            if (me.IsControlledByPlayer())
                return false;
            if (mIsCharmed)
                return false;
            return true;
        }

        void UpdateDespawn(uint diff)
        {
            if (mDespawnState <= 1 || mDespawnState > 3)
                return;

            if (mDespawnTime < diff)
            {
                if (mDespawnState == 2)
                {
                    me.SetVisible(false);
                    mDespawnTime = 5000;
                    mDespawnState++;
                }
                else
                    me.DespawnOrUnsummon();
            }
            else
                mDespawnTime -= diff;
        }

        public override void Reset()
        {
            if (!HasEscortState(SmartEscortState.Escorting))//dont mess up escort movement after combat
                SetRun(mRun);
            GetScript().OnReset();
        }

        SmartPath GetNextWayPoint()
        {
            if (mWayPoints == null || mWayPoints.Empty())
                return null;

            mCurrentWPID++;
            var path = mWayPoints.LookupByKey(mCurrentWPID);
            if (path != null)
            {
                mLastWP = path;
                if (mLastWP.id != mCurrentWPID)
                {
                    Log.outError(LogFilter.Server, "SmartAI.GetNextWayPoint: Got not expected waypoint id {0}, expected {1}", mLastWP.id, mCurrentWPID);
                }
                return path;
            }
            return null;
        }

        public void StartPath(bool run = false, uint path = 0, bool repeat = false, Unit invoker = null)
        {
            if (me.IsInCombat())// no wp movement in combat
            {
                Log.outError(LogFilter.Server, "SmartAI.StartPath: Creature entry {0} wanted to start waypoint movement while in combat, ignoring.", me.GetEntry());
                return;
            }
            if (HasEscortState(SmartEscortState.Escorting))
                StopPath();

            if (path != 0)
                if (!LoadPath(path))
                    return;

            if (mWayPoints == null || mWayPoints.Empty())
                return;

            AddEscortState(SmartEscortState.Escorting);
            mCanRepeatPath = repeat;

            SetRun(run);

            SmartPath wp = GetNextWayPoint();
            if (wp != null)
            {
                mLastOOCPos = me.GetPosition();
                me.GetMotionMaster().MovePoint(wp.id, wp.x, wp.y, wp.z);
                GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, wp.id, GetScript().GetPathId());
            }
        }

        bool LoadPath(uint entry)
        {
            if (HasEscortState(SmartEscortState.Escorting))
                return false;

            mWayPoints = Global.SmartAIMgr.GetPath(entry);
            if (mWayPoints == null)
            {
                GetScript().SetPathId(0);
                return false;
            }
            GetScript().SetPathId(entry);
            return true;
        }

        public void PausePath(uint delay, bool forced)
        {
            if (!HasEscortState(SmartEscortState.Escorting))
                return;
            if (HasEscortState(SmartEscortState.Paused))
            {
                Log.outError(LogFilter.Server, "SmartAI.PausePath: Creature entry {0} wanted to pause waypoint movement while already paused, ignoring.", me.GetEntry());
                return;
            }
            mForcedPaused = forced;
            mLastOOCPos = me.GetPosition();
            AddEscortState(SmartEscortState.Paused);
            mWPPauseTimer = delay;
            if (forced)
            {
                SetRun(mRun);
                me.StopMoving();//force stop
                me.GetMotionMaster().MoveIdle();//force stop
            }
            GetScript().ProcessEventsFor(SmartEvents.WaypointPaused, null, mLastWP.id, GetScript().GetPathId());
        }

        public void StopPath(uint DespawnTime = 0, uint quest = 0, bool fail = false)
        {
            if (!HasEscortState(SmartEscortState.Escorting))
                return;

            if (quest != 0)
                mEscortQuestID = quest;
            SetDespawnTime(DespawnTime);
            mDespawnTime = DespawnTime;

            mLastOOCPos = me.GetPosition();
            me.StopMoving();//force stop
            me.GetMotionMaster().MoveIdle();
            GetScript().ProcessEventsFor(SmartEvents.WaypointStopped, null, mLastWP.id, GetScript().GetPathId());
            EndPath(fail);
        }

        public void EndPath(bool fail = false)
        {
            GetScript().ProcessEventsFor(SmartEvents.WaypointEnded, null, mLastWP.id, GetScript().GetPathId());

            RemoveEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning);
            mWayPoints = null;
            mCurrentWPID = 0;
            mWPPauseTimer = 0;
            mLastWP = null;

            if (mCanRepeatPath)
            {
                if (IsAIControlled())
                    StartPath(mRun, GetScript().GetPathId(), true);
            }
            else
                GetScript().SetPathId(0);

            List<WorldObject> targets = GetScript().GetTargetList(SharedConst.SmartEscortTargets);
            if (targets != null && mEscortQuestID != 0)
            {
                if (targets.Count == 1 && GetScript().IsPlayer(targets.First()))
                {
                    Player player = targets.First().ToPlayer();
                    if (!fail && player.IsAtGroupRewardDistance(me) && player.GetCorpse() == null)
                        player.GroupEventHappens(mEscortQuestID, me);

                    if (fail && player.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete)
                        player.FailQuest(mEscortQuestID);

                    Group group = player.GetGroup();
                    if (group)
                    {
                        for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
                        {
                            Player groupGuy = groupRef.GetSource();

                            if (!fail && groupGuy.IsAtGroupRewardDistance(me) && !groupGuy.GetCorpse())
                                groupGuy.AreaExploredOrEventHappens(mEscortQuestID);
                            if (fail && groupGuy.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete)
                                groupGuy.FailQuest(mEscortQuestID);
                        }
                    }
                }
                else
                {
                    foreach (var obj in targets)
                    {
                        if (GetScript().IsPlayer(obj))
                        {
                            Player player = obj.ToPlayer();
                            if (!fail && player.IsAtGroupRewardDistance(me) && player.GetCorpse() == null)
                                player.AreaExploredOrEventHappens(mEscortQuestID);
                            if (fail && player.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete)
                                player.FailQuest(mEscortQuestID);
                        }
                    }
                }
            }
            if (mDespawnState == 1)
                StartDespawn();
        }

        public void ResumePath()
        {
            SetRun(mRun);
            if (mLastWP != null)
                me.GetMotionMaster().MovePoint(mLastWP.id, mLastWP.x, mLastWP.y, mLastWP.z);
        }

        void ReturnToLastOOCPos()
        {
            if (!IsAIControlled())
                return;

            SetRun(mRun);
            me.GetMotionMaster().MovePoint(EventId.SmartEscortLastOCCPoint, mLastOOCPos);
        }

        void UpdatePath(uint diff)
        {
            if (!HasEscortState(SmartEscortState.Escorting))
                return;
            if (mEscortInvokerCheckTimer < diff)
            {
                if (!IsEscortInvokerInRange())
                {
                    StopPath(mDespawnTime, mEscortQuestID, true);
                }
                mEscortInvokerCheckTimer = 1000;
            }
            else mEscortInvokerCheckTimer -= diff;
            // handle pause
            if (HasEscortState(SmartEscortState.Paused))
            {
                if (mWPPauseTimer < diff)
                {
                    if (!me.IsInCombat() && !HasEscortState(SmartEscortState.Returning) && (mWPReached || mLastWPIDReached == EventId.SmartEscortLastOCCPoint || mForcedPaused))
                    {
                        GetScript().ProcessEventsFor(SmartEvents.WaypointResumed, null, mLastWP.id, GetScript().GetPathId());
                        RemoveEscortState(SmartEscortState.Paused);
                        if (mForcedPaused)// if paused between 2 wps resend movement
                        {
                            ResumePath();
                            mWPReached = false;
                            mForcedPaused = false;
                        }
                        if (mLastWPIDReached == EventId.SmartEscortLastOCCPoint)
                            mWPReached = true;
                    }
                    mWPPauseTimer = 0;
                }
                else
                {
                    mWPPauseTimer -= diff;
                }
            }
            if (HasEscortState(SmartEscortState.Returning))
            {
                if (mWPReached)//reached OOC WP
                {
                    RemoveEscortState(SmartEscortState.Returning);
                    if (!HasEscortState(SmartEscortState.Paused))
                        ResumePath();
                    mWPReached = false;
                }
            }
            if (me.IsInCombat() || HasEscortState(SmartEscortState.Paused | SmartEscortState.Returning))
                return;
            // handle next wp
            if (mWPReached)//reached WP
            {
                mWPReached = false;
                SmartPath wp = GetNextWayPoint();
                if (mCurrentWPID == GetWPCount())
                {
                    EndPath();
                }
                else if (wp != null)
                {
                    SetRun(mRun);
                    me.GetMotionMaster().MovePoint(wp.id, wp.x, wp.y, wp.z);
                }
            }
        }

        public override void UpdateAI(uint diff)
        {
            GetScript().OnUpdate(diff);
            UpdatePath(diff);
            UpdateDespawn(diff);

            UpdateFollow(diff);

            if (!IsAIControlled())
                return;

            if (!UpdateVictim())
                return;

            if (mCanAutoAttack)
                DoMeleeAttackIfReady();
        }

        bool IsEscortInvokerInRange()
        {
            var targets = GetScript().GetTargetList(SharedConst.SmartEscortTargets);
            if (targets != null)
            {
                if (targets.Count == 1 && GetScript().IsPlayer(targets.First()))
                {
                    Player player = targets.First().ToPlayer();
                    if (me.GetDistance(player) <= SMART_ESCORT_MAX_PLAYER_DIST)
                        return true;

                    Group group = player.GetGroup();
                    if (group)
                    {
                        for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
                        {
                            Player groupGuy = groupRef.GetSource();

                            if (me.GetDistance(groupGuy) <= SMART_ESCORT_MAX_PLAYER_DIST)
                                return true;
                        }
                    }
                }
                else
                {
                    foreach (var obj in targets)
                    {
                        if (GetScript().IsPlayer(obj))
                        {
                            if (me.GetDistance(obj.ToPlayer()) <= SMART_ESCORT_MAX_PLAYER_DIST)
                                return true;
                        }
                    }
                }
            }
            return true;//escort targets were not set, ignore range check
        }

        void MovepointReached(uint id)
        {
            if (id != EventId.SmartEscortLastOCCPoint && mLastWPIDReached != id)
                GetScript().ProcessEventsFor(SmartEvents.WaypointReached, null, id);

            mLastWPIDReached = id;
            mWPReached = true;
        }

        public override void MovementInform(MovementGeneratorType MovementType, uint Data)
        {
            if ((MovementType == MovementGeneratorType.Point && Data == EventId.SmartEscortLastOCCPoint) || MovementType == MovementGeneratorType.Follow)
                me.ClearUnitState(UnitState.Evade);

            GetScript().ProcessEventsFor(SmartEvents.Movementinform, null, (uint)MovementType, Data);
            if (MovementType != MovementGeneratorType.Point || !HasEscortState(SmartEscortState.Escorting))
                return;
            MovepointReached(Data);
        }

        void RemoveAuras()
        {
            //fixme: duplicated logic in CreatureAI._EnterEvadeMode (could use RemoveAllAurasExceptType)
            foreach (var pair in me.GetAppliedAuras())
            {
                Aura aura = pair.Value.GetBase();
                if (!aura.IsPassive() && !aura.HasEffectType(AuraType.ControlVehicle) && !aura.HasEffectType(AuraType.CloneCaster) && aura.GetCasterGUID() != me.GetGUID())
                    me.RemoveAura(pair);
            }
        }

        public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
        {
            if (mEvadeDisabled)
            {
                GetScript().ProcessEventsFor(SmartEvents.Evade);
                return;
            }

            if (!_EnterEvadeMode())
                return;

            me.AddUnitState(UnitState.Evade);

            GetScript().ProcessEventsFor(SmartEvents.Evade);//must be after aura clear so we can cast spells from db

            SetRun(mRun);
            Unit target = !mFollowGuid.IsEmpty() ? Global.ObjAccessor.GetUnit(me, mFollowGuid) : null;
            Unit owner = me.GetCharmerOrOwner();
            if (HasEscortState(SmartEscortState.Escorting))
            {
                AddEscortState(SmartEscortState.Returning);
                ReturnToLastOOCPos();
            }
            else if (target)
            {
                me.GetMotionMaster().MoveFollow(target, mFollowDist, mFollowAngle);
                // evade is not cleared in MoveFollow, so we can't keep it
                me.ClearUnitState(UnitState.Evade);
            }
            else if (owner)
            {
                me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
                me.ClearUnitState(UnitState.Evade);
            }
            else
                me.GetMotionMaster().MoveTargetedHome();

            Reset();
        }

        public override void MoveInLineOfSight(Unit who)
        {
            if (who == null)
                return;

            GetScript().OnMoveInLineOfSight(who);

            if (!IsAIControlled())
                return;

            if (AssistPlayerInCombatAgainst(who))
                return;

            base.MoveInLineOfSight(who);
        }

        public override bool CanAIAttack(Unit victim)
        {
            return !me.HasReactState(ReactStates.Passive);
        }

        bool AssistPlayerInCombatAgainst(Unit who)
        {
            if (me.HasReactState(ReactStates.Passive) || !IsAIControlled())
                return false;

            if (who == null || who.GetVictim() == null)
                return false;

            //experimental (unknown) flag not present
            if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist))
                return false;

            //not a player
            if (who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself() == null)
                return false;

            //never attack friendly
            if (me.IsFriendlyTo(who))
                return false;

            //too far away and no free sight?
            if (me.IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me.IsWithinLOSInMap(who))
            {
                //already fighting someone?
                if (me.GetVictim() == null)
                {
                    AttackStart(who);
                    return true;
                }
                else
                {
                    who.SetInCombatWith(me);
                    me.AddThreat(who, 0.0f);
                    return true;
                }
            }

            return false;
        }

        public override void JustRespawned()
        {
            mDespawnTime = 0;
            mDespawnState = 0;
            mEscortState = SmartEscortState.None;
            me.SetVisible(true);
            if (me.getFaction() != me.GetCreatureTemplate().Faction)
                me.RestoreFaction();
            mJustReset = true;
            JustReachedHome();
            GetScript().ProcessEventsFor(SmartEvents.Respawn);
            mFollowGuid.Clear();//do not reset follower on Reset(), we need it after combat evade
            mFollowDist = 0;
            mFollowAngle = 0;
            mFollowCredit = 0;
            mFollowArrivedTimer = 1000;
            mFollowArrivedEntry = 0;
            mFollowCreditType = 0;
        }

        public override void JustReachedHome()
        {
            GetScript().OnReset();
            if (!mJustReset)
            {
                GetScript().ProcessEventsFor(SmartEvents.ReachedHome);

                if (!UpdateVictim() && me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle && me.GetWaypointPath() != 0)
                    me.GetMotionMaster().MovePath(me.GetWaypointPath(), true);
            }
            mJustReset = false;
        }

        public override void EnterCombat(Unit victim)
        {
            if (IsAIControlled())
                me.InterruptNonMeleeSpells(false); // must be before ProcessEvents

            GetScript().ProcessEventsFor(SmartEvents.Aggro, victim);

            if (!IsAIControlled())
                return;

            mLastOOCPos = me.GetPosition();
            SetRun(mRun);
            if (me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Point)
                me.GetMotionMaster().MovementExpired();
        }

        public override void JustDied(Unit killer)
        {
            GetScript().ProcessEventsFor(SmartEvents.Death, killer);
            if (HasEscortState(SmartEscortState.Escorting))
            {
                EndPath(true);
                me.StopMoving();//force stop
                me.GetMotionMaster().MoveIdle();
            }
        }

        public override void KilledUnit(Unit victim)
        {
            GetScript().ProcessEventsFor(SmartEvents.Kill, victim);
        }

        public override void JustSummoned(Creature summon)
        {
            GetScript().ProcessEventsFor(SmartEvents.SummonedUnit, summon);
        }

        public override void AttackStart(Unit who)
        {
            if (who != null && me.Attack(who, true))
            {
                SetRun(mRun);
                if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point)
                    me.GetMotionMaster().MovementExpired();

                if (mCanCombatMove)
                    me.GetMotionMaster().MoveChase(who);

                mLastOOCPos = me.GetPosition();
            }
        }

        public override void SpellHit(Unit caster, SpellInfo spell)
        {
            GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spell);
        }

        public override void SpellHitTarget(Unit target, SpellInfo spell)
        {
            GetScript().ProcessEventsFor(SmartEvents.SpellhitTarget, target, 0, 0, false, spell);
        }

        public override void DamageTaken(Unit attacker, ref uint damage)
        {
            GetScript().ProcessEventsFor(SmartEvents.Damaged, attacker, damage);

            if (!IsAIControlled()) // don't allow players to use unkillable units
                return;

            if (mInvincibilityHpLevel != 0 && (damage >= me.GetHealth() - mInvincibilityHpLevel))
                damage = (uint)(me.GetHealth() - mInvincibilityHpLevel);  // damage should not be nullified, because of player damage req.
        }

        public override void HealReceived(Unit by, uint addhealth)
        {
            GetScript().ProcessEventsFor(SmartEvents.ReceiveHeal, by, addhealth);
        }

        public override void ReceiveEmote(Player player, TextEmotes emoteId)
        {
            GetScript().ProcessEventsFor(SmartEvents.ReceiveEmote, player, (uint)emoteId);
        }

        public override void IsSummonedBy(Unit summoner)
        {
            GetScript().ProcessEventsFor(SmartEvents.JustSummoned, summoner);
        }

        public override void DamageDealt(Unit victim, ref uint damage, DamageEffectType damageType)
        {
            GetScript().ProcessEventsFor(SmartEvents.DamagedTarget, victim, damage);
        }

        public override void SummonedCreatureDespawn(Creature summon)
        {
            GetScript().ProcessEventsFor(SmartEvents.SummonDespawned, summon);
        }

        public override void CorpseRemoved(long respawnDelay)
        {
            GetScript().ProcessEventsFor(SmartEvents.CorpseRemoved, null, (uint)respawnDelay);
        }

        public override void PassengerBoarded(Unit passenger, sbyte seatId, bool apply)
        {
            GetScript().ProcessEventsFor(apply ? SmartEvents.PassengerBoarded : SmartEvents.PassengerRemoved, passenger, (uint)seatId, 0, apply);
        }

        public override void InitializeAI()
        {
            mScript.OnInitialize(me);
            if (!me.IsDead())
                mJustReset = true;
            JustReachedHome();
            GetScript().ProcessEventsFor(SmartEvents.Respawn);
        }

        public override void OnCharmed(bool apply)
        {
            if (apply) // do this before we change charmed state, as charmed state might prevent these things from processing
            {
                if (HasEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning))
                    EndPath(true);
                me.StopMoving();
            }
            mIsCharmed = apply;

            if (!apply && !me.IsInEvadeMode())
            {
                if (mCanRepeatPath)
                    StartPath(mRun, GetScript().GetPathId(), true);
                else
                    me.SetWalk(!mRun);

                Unit charmer = me.GetCharmer();
                if (charmer)
                    AttackStart(charmer);
            }

            GetScript().ProcessEventsFor(SmartEvents.Charmed, null, 0, 0, apply);
        }

        public override void DoAction(int param)
        {
            GetScript().ProcessEventsFor(SmartEvents.ActionDone, null, (uint)param);
        }

        public override uint GetData(uint id)
        {
            return 0;
        }

        public override void SetData(uint id, uint value)
        {
            GetScript().ProcessEventsFor(SmartEvents.DataSet, null, id, value);
        }

        public override void SetGUID(ObjectGuid guid, int id) { }

        public override ObjectGuid GetGUID(int id)
        {
            return ObjectGuid.Empty;
        }

        public void SetRun(bool run)
        {
            me.SetWalk(!run);
            mRun = run;
        }

        public void SetFly(bool fly)
        {
            me.SetDisableGravity(fly);
        }

        public void SetSwim(bool swim)
        {
            me.SetSwim(swim);
        }

        public void SetEvadeDisabled(bool disable)
        {
            mEvadeDisabled = disable;
        }

        public override void sGossipHello(Player player)
        {
            GetScript().ProcessEventsFor(SmartEvents.GossipHello, player);
        }

        public override void sGossipSelect(Player player, uint menuId, uint gossipListId)
        {
            GetScript().ProcessEventsFor(SmartEvents.GossipSelect, player, menuId, gossipListId);
        }

        public override void sGossipSelectCode(Player player, uint menuId, uint gossipListId, string code) { }

        public override void sQuestAccept(Player player, Quest quest)
        {
            GetScript().ProcessEventsFor(SmartEvents.AcceptedQuest, player, quest.Id);
        }

        public override void sQuestReward(Player player, Quest quest, uint opt)
        {
            GetScript().ProcessEventsFor(SmartEvents.RewardQuest, player, quest.Id, opt);
        }

        public override bool sOnDummyEffect(Unit caster, uint spellId, int effIndex)
        {
            GetScript().ProcessEventsFor(SmartEvents.DummyEffect, caster, spellId, (uint)effIndex);
            return true;
        }

        public void SetCombatMove(bool on)
        {
            if (mCanCombatMove == on)
                return;

            mCanCombatMove = on;
            if (!IsAIControlled())
                return;

            if (!HasEscortState(SmartEscortState.Escorting))
            {
                if (on && me.GetVictim() != null)
                {
                    if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle)
                    {
                        SetRun(mRun);
                        me.GetMotionMaster().MoveChase(me.GetVictim());
                        me.CastStop();
                    }
                }
                else
                {
                    if (me.HasUnitState(UnitState.ConfusedMove | UnitState.FleeingMove))
                        return;

                    me.GetMotionMaster().MovementExpired();
                    me.GetMotionMaster().Clear(true);
                    me.StopMoving();
                    me.GetMotionMaster().MoveIdle();
                }
            }
        }

        public void SetFollow(Unit target, float dist, float angle, uint credit, uint end, uint creditType)
        {
            if (target == null)
                return;

            mFollowGuid = target.GetGUID();
            mFollowDist = dist >= 0.0f ? dist : SharedConst.PetFollowDist;
            mFollowAngle = angle >= 0.0f ? angle : me.GetFollowAngle();
            mFollowArrivedTimer = 1000;
            mFollowCredit = credit;
            mFollowArrivedEntry = end;
            mFollowCreditType = creditType;
            SetRun(mRun);
            me.GetMotionMaster().MoveFollow(target, mFollowDist, mFollowAngle);
        }

        public void SetScript9(SmartScriptHolder e, uint entry, Unit invoker)
        {
            if (invoker != null)
                GetScript().mLastInvoker = invoker.GetGUID();
            GetScript().SetScript9(e, entry);
        }

        public override void sOnGameEvent(bool start, ushort eventId)
        {
            GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId);
        }

        public override void OnSpellClick(Unit clicker, ref bool result)
        {
            if (!result)
                return;

            GetScript().ProcessEventsFor(SmartEvents.OnSpellclick, clicker);
        }

        public void UpdateFollow(uint diff)
        {
            if (!mFollowGuid.IsEmpty())
            {
                if (mFollowArrivedTimer < diff)
                {
                    if (me.FindNearestCreature(mFollowArrivedEntry, SharedConst.InteractionDistance, true) != null)
                    {
                        Player player = Global.ObjAccessor.GetPlayer(me, mFollowGuid);
                        if (player != null)
                        {
                            if (mFollowCreditType == 0)
                                player.RewardPlayerAndGroupAtEvent(mFollowCredit, me);
                            else
                                player.GroupEventHappens(mFollowCredit, me);
                        }
                        mFollowGuid.Clear();
                        mFollowDist = 0;
                        mFollowAngle = 0;
                        mFollowCredit = 0;
                        mFollowArrivedTimer = 1000;
                        mFollowArrivedEntry = 0;
                        mFollowCreditType = 0;
                        SetDespawnTime(5000);
                        me.StopMoving();
                        me.GetMotionMaster().MoveIdle();
                        StartDespawn();
                        GetScript().ProcessEventsFor(SmartEvents.FollowCompleted);
                        return;
                    }
                    mFollowArrivedTimer = 1000;
                }
                else mFollowArrivedTimer -= diff;
            }
        }

        bool HasEscortState(SmartEscortState uiEscortState) { return mEscortState.HasAnyFlag(uiEscortState); }
        void AddEscortState(SmartEscortState uiEscortState) { mEscortState |= uiEscortState; }
        void RemoveEscortState(SmartEscortState uiEscortState) { mEscortState &= ~uiEscortState; }
        public void SetAutoAttack(bool on) { mCanAutoAttack = on; }
        public bool CanCombatMove() { return mCanCombatMove; }

        public SmartScript GetScript() { return mScript; }

        public void SetInvincibilityHpLevel(uint level) { mInvincibilityHpLevel = level; }

        public void SetDespawnTime(uint t)
        {
            mDespawnTime = t;
            mDespawnState = (uint)(t != 0 ? 1 : 0);
        }

        public void StartDespawn() { mDespawnState = 2; }

        int GetWPCount() { return mWayPoints != null ? mWayPoints.Count : 0; }

        bool mIsCharmed;
        uint mFollowCreditType;
        uint mFollowArrivedTimer;
        uint mFollowCredit;
        uint mFollowArrivedEntry;
        ObjectGuid mFollowGuid;
        float mFollowDist;
        float mFollowAngle;

        SmartScript mScript = new SmartScript();
        Dictionary<uint, SmartPath> mWayPoints;
        SmartEscortState mEscortState;
        uint mCurrentWPID;
        uint mLastWPIDReached;
        bool mWPReached;
        uint mWPPauseTimer;
        SmartPath mLastWP;
        Position mLastOOCPos;//set on enter combat

        bool mCanRepeatPath;
        bool mRun;
        bool mEvadeDisabled;
        bool mCanAutoAttack;
        bool mCanCombatMove;
        bool mForcedPaused;
        uint mInvincibilityHpLevel;

        uint mDespawnTime;
        uint mDespawnState;

        public uint mEscortQuestID;

        uint mEscortInvokerCheckTimer;
        bool mJustReset;
    }

    public class SmartGameObjectAI : GameObjectAI
    {
        public SmartGameObjectAI(GameObject g)
            : base(g)
        {
            go = g;
            mScript = new SmartScript();
        }

        public override void UpdateAI(uint diff)
        {
            GetScript().OnUpdate(diff);
        }

        public override void InitializeAI()
        {
            GetScript().OnInitialize(go);
            GetScript().ProcessEventsFor(SmartEvents.Respawn);
        }

        public override void Reset()
        {
            GetScript().OnReset();
        }

        public override bool GossipHello(Player player, bool isUse)
        {
            Log.outDebug(LogFilter.ScriptsAi, "SmartGameObjectAI.GossipHello");
            GetScript().ProcessEventsFor(SmartEvents.GossipHello, player, 0, 0, false, null, go);
            return false;
        }

        public override bool GossipSelect(Player player, uint sender, uint action)
        {
            GetScript().ProcessEventsFor(SmartEvents.GossipSelect, player, sender, action, false, null, go);
            return false;
        }

        public override bool GossipSelectCode(Player player, uint sender, uint action, string code)
        {
            return false;
        }

        public override bool QuestAccept(Player player, Quest quest)
        {
            GetScript().ProcessEventsFor(SmartEvents.AcceptedQuest, player, quest.Id, 0, false, null, go);
            return false;
        }

        public override bool QuestReward(Player player, Quest quest, uint opt)
        {
            GetScript().ProcessEventsFor(SmartEvents.RewardQuest, player, quest.Id, opt, false, null, go);
            return false;
        }

        public override uint GetDialogStatus(Player player)
        {
            return 100;
        }

        public override void Destroyed(Player player, uint eventId)
        {
            GetScript().ProcessEventsFor(SmartEvents.Death, player, eventId, 0, false, null, go);
        }

        public override void SetData(uint id, uint value)
        {
            GetScript().ProcessEventsFor(SmartEvents.DataSet, null, id, value);
        }

        public void SetScript9(SmartScriptHolder e, uint entry, Unit invoker)
        {
            if (invoker != null)
                GetScript().mLastInvoker = invoker.GetGUID();
            GetScript().SetScript9(e, entry);
        }

        public override void OnGameEvent(bool start, ushort eventId)
        {
            GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId);
        }

        public override void OnStateChanged(uint state, Unit unit)
        {
            GetScript().ProcessEventsFor(SmartEvents.GoStateChanged, unit, state);
        }

        public override void EventInform(uint eventId)
        {
            GetScript().ProcessEventsFor(SmartEvents.GoEventInform, null, eventId);
        }

        public SmartScript GetScript() { return mScript; }


        new GameObject go;
        SmartScript mScript;
    }

    [Script]
    class SmartTrigger : AreaTriggerScript
    {
        public SmartTrigger() : base("SmartTrigger") { }

        public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered)
        {
            if (!player.IsAlive())
                return false;

            Log.outDebug(LogFilter.ScriptsAi, "AreaTrigger {0} is using SmartTrigger script", trigger.Id);
            SmartScript script = new SmartScript();
            script.OnInitialize(null, trigger);
            script.ProcessEventsFor(SmartEvents.AreatriggerOntrigger, player, trigger.Id);
            return true;
        }
    }

    [Script]
    class SmartScene : SceneScript
    {
        public SmartScene() : base("SmartScene") { }

        public override void OnSceneStart(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            SmartScript smartScript = new SmartScript();
            smartScript.OnInitialize(null, null, sceneTemplate);
            smartScript.ProcessEventsFor(SmartEvents.SceneStart, player);
        }

        public override void OnSceneTriggerEvent(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName)
        {
            SmartScript smartScript = new SmartScript();
            smartScript.OnInitialize(null, null, sceneTemplate);
            smartScript.ProcessEventsFor(SmartEvents.SceneTrigger, player, 0, 0, false, null, null, triggerName);
        }

        public override void OnSceneCancel(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            SmartScript smartScript = new SmartScript();
            smartScript.OnInitialize(null, null, sceneTemplate);
            smartScript.ProcessEventsFor(SmartEvents.SceneCancel, player);
        }

        public override void OnSceneComplete(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            SmartScript smartScript = new SmartScript();
            smartScript.OnInitialize(null, null, sceneTemplate);
            smartScript.ProcessEventsFor(SmartEvents.SceneComplete, player);
        }
    }

    public enum SmartEscortState
    {
        None = 0x00,                        //nothing in progress
        Escorting = 0x01,                        //escort is in progress
        Returning = 0x02,                        //escort is returning after being in combat
        Paused = 0x04                         //will not proceed with waypoints before state is removed
    }
}
