using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;


using InfServer.Game;
using InfServer.Protocol;
using InfServer.Scripting;
using InfServer.Bots;

using Assets;
using Axiom.Math;
using Bnoerj.AI.Steering;

namespace InfServer.Script.GameType_BHX
{   // Script Class
    /// Provides the interface between the script and bot
    ///////////////////////////////////////////////////////
    public partial class Skrall : Bot
    {   ///////////////////////////////////////////////////
        // Member Variables
        ///////////////////////////////////////////////////
        //private Bot _bot;							//Pointer to our bot class
        private Random _rand;

        private Player _target;                     //The player we're currently stalking
        private Vehicle _botTarget;
        private Team _targetTeam;
        protected bool bOverriddenPoll;         //Do we have custom actions for poll?

        protected List<Vector3> _path;			//The path to our destination
        protected int _pathTarget;				//The next target node of the path
        protected int _tickLastPath;			//The time at which we last made a path to the player
        protected int _lastHeal;
        protected int _tickLastUpdate;
        protected int _energy;
        protected double _getEnergy;                    //For energy repair
        private int _tickLastRadarDot;
        private int _tickLastCollision;
        private int _tickLastTargetSeen;

        private bool _bPatrolEnemy;
        protected SteeringController steering;	//System for controlling the bot's steering
        private float _seperation;
        private int _tickNextStrafeChange;          //The last time we changed strafe direction
        private bool _bStrafeLeft;                  //Are we strafing left or right?

        private class Projectile
        {
            public Projectile(Vehicle vehicle, int tickCreation)
            {
                target = vehicle;
                tickShotFired = tickCreation;
            }

            public Vehicle target; //The target we were shooting at.
            public int tickShotFired; //The time at which we fired our projectile
        }
        private List<Projectile> _shotsFired;


        public Skrall(VehInfo.Car type, Helpers.ObjectState state, Arena arena)
            : base(type, state, arena,
            new SteeringController(type, state, arena))
        {
            Random rnd = new Random();
            _seperation = (float)rnd.NextDouble();
            steering = _movement as SteeringController;
            _rand = new Random();

            if (type.InventoryItems[0] != 0)
                _weapon.equip(AssetManager.Manager.getItemByID(type.InventoryItems[0]));

            _shotsFired = new List<Projectile>();
            _state.energy = (short)_type.EnergyMax;

        }

        public void init(Team targetTeam)
        {
            _targetTeam = targetTeam;
  
        }



        /// <summary>
        /// Allows the script to maintain itself
        /// </summary>
        public override bool poll()
        {
            if (bOverriddenPoll)
                return base.poll();

            if (_movement == null)
            {
                _movement = new MovementController(_type, _state, _arena);
                steering = _movement as SteeringController;
                _movement.bEnabled = true;
                _movement.updateState(1);
                steering.bEnabled = true;
                return base.poll();
            }

            bool bClearPath = false;

            //Dead? Do nothing
            if (IsDead)
            {
                steering.steerDelegate = null;
                return base.poll();
            }
            int now = Environment.TickCount;

            if (now - _tickLastTargetSeen >= 8000 && _tickLastTargetSeen != 0)
                destroy(false);

            //Maintain our energy
            if (_type.EnergyMax > 0 && _type.EnergyRate > 0 && now - _tickLastUpdate >= (_type.EnergyRate * 10))
            {
                double _incEnergy = ((double)_type.EnergyRate / 10);
                _tickLastUpdate = now;
                _state.energy += (short)_incEnergy;

                if (_state.energy > _type.EnergyMax)
                    _state.energy = (short)_type.EnergyMax;
            }

            if (_movement.bCollision)
            {
                //Try to update our path
                if (_target != null)
                    updatePath(_target._state, now, true);

                steering.steerDelegate = delegate (InfantryVehicle vehicle)
                {
                    Vector3 seek = vehicle.SteerForFlee(steering._lastCollision);
                    return seek;
                };


                
                return base.poll();
            }
           
            if (_movement.bCollision && now - _tickLastCollision < 500)
            {
                steering.steerDelegate = delegate (InfantryVehicle vehicle)
                {
                    Vector3 seek = vehicle.SteerForFlee(steering._lastCollision);
                    return seek;
                };

                _tickLastCollision = now;
                return base.poll();
            }
            

            //Allows the bot to update the target every poll
            _target = null;

            //Get the closest player
             bClearPath = false;
            _target = getTargetPlayer(ref bClearPath, _targetTeam);

            if (_target != null)
            {
                if (bClearPath)
                {   //What is our distance to the target?
                    double distance = (_state.position() - _target._state.position()).Length;
                    bool bFleeing = false;

                    //Too far?
                    if (distance > pursueDist)
                        steering.steerDelegate = steerForPersuePlayer;
                    //Quite short?
                    else if (distance < shortDist)
                    {
                        steering.bSkipRotate = true;
                        steering.steerDelegate = delegate (InfantryVehicle vehicle)
                        {
                            if (_target != null)
                                return vehicle.SteerForFlee(_target._state.position());
                            else
                                return Vector3.Zero;
                        };
                    }
                    //Just right
                    else
                        steering.steerDelegate = null;




                    if (!bFleeing)
                    {
                        //Should we be firing our ranged attack?
                        if (distance <= fireDist && distance > minRangedAttack)
                        {
                            int energyCost = 0;

                            ItemInfo item = AssetManager.Manager.getItemByID(_type.InventoryItems[3]);
                            switch (item.itemType)
                            {
                                case ItemInfo.ItemType.MultiUse:
                                    {
                                        ItemInfo.MultiUse mu = item as ItemInfo.MultiUse;
                                        energyCost = mu.energyCostTerrain0 / 1000;
                                    }
                                    break;
                                case ItemInfo.ItemType.Projectile:
                                    {
                                        ItemInfo.Projectile projectile = item as ItemInfo.Projectile;
                                        energyCost = projectile.terrain0EnergyCost / 1000;
                                    }
                                    break;
                            }
                            if (_weapon.ItemID != item.id)
                                _weapon.equip(item);

                            if (_weapon.ableToFire() && _state.energy > energyCost)
                            {
                                int aimResult = _weapon.getAimAngle(_target._state);

                                if (_weapon.isAimed(aimResult))
                                {   //Spot on! Fire?
                                    _itemUseID = _weapon.ItemID;
                                    _weapon.shotFired();
                                    _state.energy -= (short)energyCost;
                                }

                                steering.bSkipAim = true;
                                steering.angle = aimResult;
                            }
                        }
                        //Should we be firing our melee?
                        else if (distance <= minMeleeDist)
                        {
                            if (_weapon.ItemID != 3007)
                                _weapon.equip(AssetManager.Manager.getItemByID(3007));

                            if (_weapon.ableToFire())
                            {
                                int aimResult = _weapon.getAimAngle(_target._state);

                                if (_weapon.isAimed(aimResult))
                                {   //Spot on! Fire?
                                    _itemUseID = _weapon.ItemID;
                                    _weapon.shotFired();
                                }

                                steering.bSkipAim = true;
                                steering.angle = aimResult;
                            }
                            else
                                steering.bSkipAim = false;
                        }
                    }
                }
                else
                {
                    updatePath(_target._state, now, false);

                    //Navigate to him
                    if (_path == null)
                        //If we can't find out way to him, just mindlessly walk in his direction for now
                        steering.steerDelegate = steerForPersuePlayer;
                    else
                        steering.steerDelegate = steerAlongPath;
                }
            }

                //Handle normal functionality
                return base.poll();
        }

        public Helpers.ObjectState findOpenPath()
        {
            Helpers.ObjectState target = new Helpers.ObjectState();

            int blockedAttempts = 30;
            short pX;
            short pY;
            while (true)
            {
                pX = _state.positionX;
                pY = _state.positionY;
                Helpers.randomPositionInArea(_arena, 200, ref pX, ref pY);
                if (_arena.getTile(pX, pY).Blocked)
                {
                    blockedAttempts--;
                    if (blockedAttempts <= 0)
                        //Consider the spawn to be blocked
                        return null;
                    continue;
                }

                bool bClearPath = Helpers.calcBresenhemsPredicate(_arena,
                _state.positionX, _state.positionY, pX, pY,
                delegate (LvlInfo.Tile t)
                {
                    return !t.Blocked;
                }
                );

                if (!bClearPath)
                {
                    blockedAttempts--;
                    if (blockedAttempts <= 0)
                        //Consider the spawn to be blocked
                        return null;
                    continue;
                }

                target.positionX = pX;
                target.positionY = pY;
                break;
            }
            return target;
        }


        /// <summary>
        /// Obtains a suitable target player
        /// </summary>
        protected Player getTargetPlayer(ref bool bInSight, Team targetTeam)
        {
            //Look at the players on the target team
            if (targetTeam == null)
                return null;

            Player target = null;
            double lastDist = double.MaxValue;
            bInSight = false;

            IEnumerable<Player> trackingRange = _arena.getPlayersInRange(_state.positionX, _state.positionY, 2000).Where(plyr => plyr._team == targetTeam);
            foreach (Player p in trackingRange)
            {   //Find the closest player
                if (p.IsDead)
                    continue;

                if (_arena.getTerrainID(p._state.positionX, p._state.positionY) == 1)
                    continue;

                int distance = (int)(_state.position().Distance(p._state.position()) * 100);
                if (p.activeUtilities.Any(util => util != null && distance >= util.cloakDistance && util.cloakDistance != -1))
                    continue;

                double dist = Helpers.distanceSquaredTo(_state, p._state);
                bool bClearPath = Helpers.calcBresenhemsPredicate(_arena, _state.positionX, _state.positionY, p._state.positionX, p._state.positionY,
                    delegate (LvlInfo.Tile t)
                    {
                        return !t.Blocked;
                    }
                );

                if ((!bInSight || (bInSight && bClearPath)) && lastDist > dist)
                {
                    bInSight = bClearPath;
                    lastDist = dist;
                    target = p;
                }
            }

            if (target != null)
                _tickLastTargetSeen = Environment.TickCount;


            return target;
        }

        #region Steer Delegates
        public Vector3 steerForSeekTarget(InfantryVehicle vehicle)
        {
            Vector3 pursuitSteer = vehicle.SteerForPursuit(_target._baseVehicle.Abstract, 0.2f);
            return pursuitSteer;
        }

        public Vector3 steerForSeekBot(InfantryVehicle vehicle)
        {
            Vector3 pursuitSteer = vehicle.SteerForPursuit(_botTarget.Abstract, 0.2f);
            return pursuitSteer;
        }
        /// <summary>
        /// Steers the zombie along the defined path
        /// </summary>
        public Vector3 steerAlongPath(InfantryVehicle vehicle)
        {	//Are we at the end of the path?
            if (_pathTarget >= _path.Count)
            {	//Invalidate the path
                _path = null;
                _tickLastPath = 0;
                return Vector3.Zero;
            }

            //Find the nearest path point
            Vector3 point = _path[_pathTarget];

            //Are we close enough to go to the next?
            if (_pathTarget < _path.Count && vehicle.Position.Distance(point) < 0.74f)
                point = _path[_pathTarget++];

            return vehicle.SteerForSeek(point);
        }

        /// <summary>
        /// Steers the zombie along the defined path
        /// </summary>
        public Vector3 steerOnPathForCollision(InfantryVehicle vehicle)
        {
            _pathTarget--;

            if (_pathTarget >= _path.Count)
            {	//Invalidate the path
                _path = null;
                _tickLastPath = 0;
                return Vector3.Zero;
            }

            if (_pathTarget < 0)
                _pathTarget = 0;

            //Find the nearest path point
            Vector3 point = _path[_pathTarget];

            //Are we close enough to go to the next?
            if (_pathTarget < _path.Count && vehicle.Position.Distance(point) < 0.75f)
                point = _path[_pathTarget++];


            Vector3 wander = vehicle.SteerForWander(10.5f);
            Vector3 seek = vehicle.SteerForSeek(point);

            return wander + (seek * 10.8f);
        }

        /// <summary>
        /// Moves the medic on a persuit course towards the player, while keeping seperated from other medics
        /// </summary>
        public Vector3 steerForPersuePlayer(InfantryVehicle vehicle)
        {
            if (_target == null)
                return Vector3.Zero;

            List<Vehicle> medics = _arena.getVehiclesInRange(vehicle.state.positionX, vehicle.state.positionY, 200,
                                                                delegate (Vehicle v)
                                                                { return (v is Skrall); });
            IEnumerable<IVehicle> medicbots = medics.ConvertAll<IVehicle>(
                delegate (Vehicle v)
                {
                    return (v as Skrall).Abstract;
                }
            );

            Vector3 seperationSteer = vehicle.SteerForSeparation(_seperation, -0.307f, medicbots);
            Vector3 pursuitSteer = vehicle.SteerForPursuit(_target._baseVehicle.Abstract, 0.2f);

            return (seperationSteer * 1.2f) + pursuitSteer;
        }




        public void updatePath(Helpers.ObjectState target, int now, bool bCollision)
        {
            //Does our path need to be updated?
            if (now - _tickLastPath > c_pathUpdateInterval || bCollision)
            {   //Update it!
                _tickLastPath = int.MaxValue;

                _arena._pathfinder.queueRequest(
                    (short)(_state.positionX / 16), (short)(_state.positionY / 16),
                    (short)(target.positionX / 16), (short)(target.positionY / 16),
                    delegate (List<Vector3> path, int pathLength)
                    {
                        if (path != null)
                        {   //Is the path too long?
                            //if (pathLength > c_MaxPath)
                            //{   //Destroy ourself and let another skrall take our place
                            //    _path = null;
                            //    destroy(true);
                            //}
                            //else
                            {
                                _path = path;
                                _pathTarget = 1;
                            }
                        }

                        _tickLastPath = now;
                    }
                );
            }
        }

       
        #endregion
    }
}
