//---------------------------------------------------------------------------------------------------------------------------------------------------
// <copyright file="AI.cs" company="DarkWynter Studios">
//     Copyright (C)2007 DarkWynter Studios.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {License Information: Creative Commons}
//---------------------------------------------------------------------------------------------------------------------------------------------------

namespace DarkWynterEngine.GameObjects
{
    #region Using Statements
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using System.Xml;
    using System.Diagnostics;
    #endregion

    using Globals;
    using ObjectLib;
    using GameObjects;

    /// <summary>
    /// Bots
    /// </summary>
    public class AI : Player
    {
        // Stimuli for the AI to react to
        PerceptInterface percept = new PerceptInterface();

        private Enums.TurnDirection turnDirection = Enums.TurnDirection.NOT_SET;
        private Enums.AIState aiState = new Enums.AIState();
        private Enums.AIAttackState aiAttackState = new Enums.AIAttackState();

        private Vector3 randomDestination = new Vector3();

        private Stopwatch jumpTimer = null;

        private Vector3 playerToAttack = new Vector3(-1.0f);
        private Vector3 playerToAvoid = new Vector3(-1.0f);

        private AIVision aiVision;
       
        private Random rand = new Random();

        private int ATTACK_RADIUS = 100;
        private float HYSTERESIS_AMOUNT = 5.0f; //range
        //private int BOT_SPAWN_HEIGHT = 500;
        private int LOWHEALTH = 50;
        private int LOWMANNA = 50;
        private int JUMP_DELAY = 1;

        private class PerceptInterface
        {
            public Enums.TerrainType hitTerrain;
            public Enums.ObjectType upcomingTerrain;
            public bool hitLeftByParticle;
            public bool hitRightByParticle;
            public List<Vector3> playersInView = new List<Vector3>();

            public Enums.AmountType mannaAmount;
            public Enums.AmountType healthAmount;

            public void init()
            {
                hitTerrain = Enums.TerrainType.NONE;
                hitLeftByParticle = false;
                hitRightByParticle = false;
                playersInView.Clear();
                mannaAmount = Enums.AmountType.NEUTRAL;
                healthAmount = Enums.AmountType.NEUTRAL;
                upcomingTerrain = Enums.ObjectType.NONE;
            }
        };

        /// <summary>
        /// Constructor
        /// </summary>
        public AI()
            : base()
        {
            mass.objectType = Enums.ObjectType.PLAYER;
            mass.gameObjectPointer = this;
            aiVision = new AIVision(mass);
        }

        /// <summary>
        /// Spawns a new AI
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary</param>
        public void SpawnPlayer(ObjectLibrary objectLibrary)
        {
            spawnHeight = objectLibrary.terrain.GetTerrainHeight(spawnPosition.X / Statics.TerrainSettings.terrainScaleFactor,
                                                               spawnPosition.Z / Statics.TerrainSettings.terrainScaleFactor);

            base.SpawnPlayer();
            health = Statics.PlayerSettings.AI_MAX_HEALTH;
        }

        /// <summary>
        /// Load stuff from XML
        /// </summary>
        /// <param name="node">The Bot node</param>
        /// <param name="objectLibrary">ObjectLibrary</param>
        /// <returns>True if load was successful</returns>
        public override bool Load(XmlNode node, ObjectLibrary objectLibrary)
        {
            if (!base.Load(node, objectLibrary))
            {
                return false;
            }

            health = Statics.PlayerSettings.AI_MAX_HEALTH;
            manna = 100;



            //aiState = Enums.AIState.IDLE;
            aiState = Enums.AIState.ATTACK;
            aiAttackState = Enums.AIAttackState.PURSUE;

            return true;
        }

        private float AngleToDirection(Vector3 direction)
        {
            Vector2 diff2 = new Vector2(direction.X, direction.Z);
            diff2.Normalize();
            Vector2 N = new Vector2(mass.normalVector.X, mass.normalVector.Z);
            N.Normalize();
            return (float)Math.Acos((double)(Vector2.Dot(diff2, N)));
        }

        private Enums.TurnDirection DirectionToOrientation(Vector3 direction)
        {
            // Determine which direction to turn
            Vector3 toDirection = new Vector3(direction.X, 0, direction.Z);
            toDirection.Normalize();
            Vector3 lookAt = new Vector3(mass.normalVector.X, 0, mass.normalVector.Z);
            lookAt.Normalize();

            Vector3 cross = Vector3.Cross(toDirection, lookAt);
            if (cross.Y < 0.2)
            {
                return Enums.TurnDirection.LEFT;
            }
            else
            {
                return Enums.TurnDirection.RIGHT;
            }
        }

        private void MoveTowardDirection(Vector3 direction)
        {
            float angle = AngleToDirection(direction);
            if (float.IsNaN(angle) || angle < 0.1)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = 0.0f;
                DarkWynterGame.playerController.gameInput.playerMotion.Y = 10.0f;
            }
            else
            {
                if (turnDirection == Enums.TurnDirection.NOT_SET)
                {
                    // Determine which direction to turn
                    turnDirection = DirectionToOrientation(direction);
                }
                if (turnDirection == Enums.TurnDirection.LEFT)
                {
                    DarkWynterGame.playerController.gameInput.playerRotation.X = 0.05f;
                }
                else
                {
                    DarkWynterGame.playerController.gameInput.playerRotation.X = -0.05f;
                }
            }
        }

        private Vector3 GetClosestPlayerInView()
        {
            Vector3 closestPlayer = new Vector3(-1.0f);

            // Get the closest player in the playersInView array
            if (percept.playersInView.Count == 0)
            {
                return closestPlayer;
            }

            float closestDistance = 100000.0f;

            // Check for AI in radius
            for (int i = 0; i < percept.playersInView.Count; i++)
            {
                float distance = (percept.playersInView[i] - mass.currentPosition).Length();
                if (distance < closestDistance)
                {
                    closestPlayer = percept.playersInView[i];
                    closestDistance = distance;
                }
            }
            return closestPlayer;
        }

        private bool DoSmarterThing()
        {
            int probability = rand.Next(1000);
            return (probability < (Statics.PlayerSettings.AI_INTELLIGENCE * 100) - HYSTERESIS_AMOUNT);
        }
        private bool DoDumberThing()
        {
            int probability = rand.Next(1000);
            return (probability > (Statics.PlayerSettings.AI_INTELLIGENCE * 100) + HYSTERESIS_AMOUNT);
        }

        private void Jump()
        {
            if (jumpTimer == null)
            {
                jumpTimer = Stopwatch.StartNew();
            }
            if (jumpTimer.Elapsed.Seconds >= JUMP_DELAY)
            {
                DarkWynterGame.playerController.gameInput.jump = true;
                jumpTimer = Stopwatch.StartNew();
            }
        }

        /// <summary>
        /// General update function
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary</param>
        public override void Update(ref ObjectLibrary objectLibrary)
        {
            //if dead they can't update
            if (IsAlive() == false)
            {
                return;
            }

            // Reset all controller inputs
            DarkWynterGame.playerController.gameInput.init();
            // Turn terrain mod back off
            terrainModEnabled = false;

            // Fuzzy state machine
            // * <- means smarter choice

            // Update percepts
            UpdatePercepts(objectLibrary);

            // Was he hit by something?
            if (percept.hitLeftByParticle || percept.hitRightByParticle)
            {
                if (aiState != Enums.AIState.ATTACK && aiState != Enums.AIState.EVADE)
                {
                    if (percept.hitRightByParticle)
                    {
                        // Hit on the right
                        turnDirection = Enums.TurnDirection.RIGHT;
                    }
                    else
                    {
                        // Hit on the left
                        turnDirection = Enums.TurnDirection.LEFT;
                    }
                    aiState = Enums.AIState.ATTACK;

                }
            }
            // Now look for players within view
            else if (percept.playersInView.Count > 0)
            {
                playerToAttack = GetClosestPlayerInView();

                Vector3 toPlayer = playerToAttack - mass.currentPosition;
                DarkWynterGame.playerController.gameInput.playerRotation = Vector2.Zero;

                // Check your health
                if (percept.healthAmount == Enums.AmountType.LOW)
                {
                    // Low health
                    if (DoSmarterThing())
                    {
                       // RUN AWAY
                       aiState = Enums.AIState.START_EVADE;
                    }
                    else if (DoDumberThing())
                    {
                        // ATTACK!
                        aiState = Enums.AIState.ATTACK;
                    }
                }
                else if (percept.healthAmount == Enums.AmountType.HIGH)
                {
                    if (percept.mannaAmount == Enums.AmountType.LOW)
                    {
                        // Low manna
                        if (DoSmarterThing())
                        {
                            // RUN AWAY
                            aiState = Enums.AIState.START_EVADE;
                        }
                        else if (DoDumberThing())
                        {
                            // ATTACK!
                            aiState = Enums.AIState.ATTACK;
                        }
                    }
                    else if (percept.mannaAmount == Enums.AmountType.HIGH)
                    {
                        // Have manna
                        if (DoSmarterThing())
                        {
                            // ATTACK!
                            aiState = Enums.AIState.ATTACK;
                        }
                        else if (DoDumberThing())
                        {
                            // RUN AWAY
                            aiState = Enums.AIState.START_EVADE;
                        }
                    }
                }
            }
            // Hit by terrain means he either fell from a high place or walked into a wall
            else if (percept.hitTerrain == Enums.TerrainType.GROUND)
            {
                // Recalculate where to go
                aiState = Enums.AIState.IDLE;
            }
            else if (percept.hitTerrain == Enums.TerrainType.LOW)
            {
                //terrainModEnabled = true;
                //controllerInput.defendAmount = -1.0f;
            }

            if (percept.hitTerrain == Enums.TerrainType.MEDIUM ||
                percept.hitTerrain == Enums.TerrainType.HIGH)
            {
                Jump();
            }

            UpdateState();

            DarkWynterGame.playerController.gameInput.attack = DarkWynterGame.playerController.gameInput.attackAmount + DarkWynterGame.playerController.gameInput.defendAmount;

            base.Update(ref objectLibrary);
        }
        private void UpdatePercepts(ObjectLibrary objectLibrary)
        {
            // Clear out percepts first
            percept.init();

            // First check for all stimuli
            // Was he hit by something?
            if (hitWhere.Contains(Enums.HitWhere.LEFT))
            {
                percept.hitLeftByParticle = true;
            }
            if (hitWhere.Contains(Enums.HitWhere.RIGHT))
            {
                percept.hitRightByParticle = true;
            }
            
            //Find all players in view
            percept.playersInView = aiVision.GetVisiblePlayers(objectLibrary);

            // If health is within danger range
            if (health < LOWHEALTH - HYSTERESIS_AMOUNT)
            {
                percept.healthAmount = Enums.AmountType.LOW;
            }
            else if (health > LOWHEALTH + HYSTERESIS_AMOUNT)
            {
                percept.healthAmount = Enums.AmountType.HIGH;
            }
            else
            {
                percept.healthAmount = Enums.AmountType.NEUTRAL;
            }
            
            // If manna is within danger range
            if (manna < LOWMANNA - HYSTERESIS_AMOUNT)
            {
                percept.mannaAmount = Enums.AmountType.LOW;
            }
            else if (manna > LOWMANNA + HYSTERESIS_AMOUNT)
            {
                percept.mannaAmount = Enums.AmountType.HIGH;
            }
            else
            {
                percept.mannaAmount = Enums.AmountType.NEUTRAL;
            }

            hitWhere.Clear();

            List<Vector3> playerLocations = aiVision.GetVisiblePlayers(objectLibrary);

            if (playerLocations.Count != 0)
            {
                return;
            }
            //playerToAttack = objectLibrary.getPlayerAt( playerLocations );
        }
        private void UpdateState()
        {
            if (aiState == Enums.AIState.IDLE)
            {
                idleLogic();
            }
            else if (aiState == Enums.AIState.GOTO)
            {
                gotoLogic();
            }
            else if (aiState == Enums.AIState.SEARCH_MODE)
            {
                searchLogic();
            }
            else if (aiState == Enums.AIState.ATTACK)
            {
                attackLogic();
            }
            else if (aiState == Enums.AIState.START_EVADE)
            {
                startEvadeLogic();
            }
            else if (aiState == Enums.AIState.EVADE)
            {
                evadeLogic();
            }
        }

        private void evadeLogic()
        {
            if (playerToAvoid != new Vector3(-1.0f))
            {
                Vector3 vecToPlayer = playerToAvoid - mass.currentPosition;
                MoveTowardDirection(-vecToPlayer);

                if (vecToPlayer.Length() > ATTACK_RADIUS)
                {
                    aiState = Enums.AIState.ATTACK;
                    //aiState = Enums.AIState.IDLE;
                }

                //If they were also hit by something
                if (percept.hitLeftByParticle || percept.hitRightByParticle)
                {
                    Jump();
                }
            }
            else
            {
                aiState = Enums.AIState.ATTACK;
//                aiState = Enums.AIState.IDLE;
            }
        }

        private void startEvadeLogic()
        {
            if (playerToAttack != new Vector3(-1.0f))
            {
                Vector3 vecToPlayer = playerToAttack - mass.currentPosition;
                if (vecToPlayer.Length() < ATTACK_RADIUS - HYSTERESIS_AMOUNT)
                {
                    turnDirection = Enums.TurnDirection.NOT_SET;
                    playerToAvoid = playerToAttack;
                    aiState = Enums.AIState.EVADE;
                }
                else if (vecToPlayer.Length() > ATTACK_RADIUS + HYSTERESIS_AMOUNT)
                {
                    //Player is too far away.. ignore
                    //aiState = Enums.AIState.IDLE;
                    aiState = Enums.AIState.ATTACK;
                }
            }
            else
            {
                aiState = Enums.AIState.ATTACK;
            }
        }

        private void attackLogic()
        {
            if (playerToAttack != new Vector3(-1.0f))
            {
                Vector3 vecToPlayer = playerToAttack - mass.currentPosition;
                if (aiAttackState == Enums.AIAttackState.NONE)
                {
                    turnDirection = Enums.TurnDirection.NOT_SET;
                    aiAttackState = Enums.AIAttackState.PURSUE;
                }
                else if (aiAttackState == Enums.AIAttackState.PURSUE)
                {
                    MoveTowardDirection(vecToPlayer);
                    float angle = AngleToDirection(vecToPlayer);

                    // NaN if he is facing you
                    if (float.IsNaN(angle) || angle < 0.1)
                    {
                        if (vecToPlayer.Length() <= ATTACK_RADIUS)
                        {
                            //AttackPlayer();
                            //aiAttackState = Enums.AIAttackState.DESTROY;
                        }
                    }
                }
                else if (aiAttackState == Enums.AIAttackState.DESTROY)
                {
                    vecToPlayer = destroyLogic(vecToPlayer);
                }

                //If hit while attacking...
                if (percept.hitLeftByParticle || percept.hitRightByParticle)
                {
                    DarkWynterGame.playerController.gameInput.playerMotion.Y = -1.0f;
                }
            }
            else
            {
                // Reset all controller inputs
                DarkWynterGame.playerController.gameInput.playerMotion = Vector2.Zero;
                DarkWynterGame.playerController.gameInput.playerRotation = Vector2.Zero;
                DarkWynterGame.playerController.gameInput.attackAmount = 0.0f;
                DarkWynterGame.playerController.gameInput.defendAmount = 0.0f;
                DarkWynterGame.playerController.gameInput.attack = 0;
                //aiState = Enums.AIState.IDLE;
                aiState = Enums.AIState.ATTACK;
            }
        }

        private Vector3 destroyLogic(Vector3 vecToPlayer)
        {
            //controllerInput.attackAmount = 1.0f;
            DarkWynterGame.playerController.gameInput.playerMotion.Y = 0.0f;

            turnDirection = DirectionToOrientation(vecToPlayer);
            if (turnDirection == Enums.TurnDirection.LEFT)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = -0.1f;
            }
            else if (turnDirection == Enums.TurnDirection.RIGHT)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = 0.1f;
            }

            float angle = AngleToDirection(vecToPlayer);

            if (float.IsNaN(angle) || angle < 0.1f)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = 0.0f;
            }

            // If the player moves away then face and pursue again
            if (vecToPlayer.Length() > ATTACK_RADIUS)
            {
                turnDirection = Enums.TurnDirection.NOT_SET;
                aiAttackState = Enums.AIAttackState.PURSUE;
            }
            else
            {
                //in range
                AttackPlayer();
            }
            return vecToPlayer;
        }

        private void searchLogic()
        {
            // Search in the specified direction until the time runs out
            if (turnDirection == Enums.TurnDirection.LEFT)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = -0.1f;
            }
            else if (turnDirection == Enums.TurnDirection.RIGHT)
            {
                DarkWynterGame.playerController.gameInput.playerRotation.X = 0.1f;
            }
            // aiState = Enums.AIState.IDLE;
            aiState = Enums.AIState.ATTACK;

        }

        private void gotoLogic()
        {
            // Move towards a destination point
            Vector3 diff = new Vector3(randomDestination.X - mass.currentPosition.X, 0, randomDestination.Z - mass.currentPosition.Z);
            MoveTowardDirection(diff);

            if (Math.Abs(diff.X) < 5 && Math.Abs(diff.Z) < 5)
            {
                //aiState = Enums.AIState.IDLE;
                aiState = Enums.AIState.ATTACK;
            }
        }

        private void idleLogic()
        {
            // Find a new place to go
            // just always do it for now
            if (DoSmarterThing() == true)
            {
                randomDestination = new Vector3(rand.Next(0, Statics.TerrainSettings.collisionMapSize), Statics.PlayerSettings.DEFAULT_PLAYER_HEIGHT, rand.Next(0, Statics.TerrainSettings.collisionMapSize));
                turnDirection = Enums.TurnDirection.NOT_SET;
                //aiState = Enums.AIState.GOTO;
                aiState = Enums.AIState.ATTACK;
            }
        }

        private void AttackPlayer()
        {
            // return;

            //pick random element
            int element = rand.Next(3);
            switch (element)
            {
                case 0:
                    DarkWynterGame.playerController.gameInput.modeSwitch = Enums.AttackType.EARTH;
                    break;
                case 1:
                    DarkWynterGame.playerController.gameInput.modeSwitch = Enums.AttackType.WATER;
                    break;
                case 2:
                    DarkWynterGame.playerController.gameInput.modeSwitch = Enums.AttackType.WIND;
                    break;
            }
            DarkWynterGame.playerController.gameInput.attackMode = Enums.AttackMode.ATTACK;

            DarkWynterGame.playerController.gameInput.attackAmount = 1.0f;
            attackMagnitude = rand.Next(50);
            attackTemperature = rand.Next(50);
        }
    }
}

/* Legacy Code
        private List<Player> GetPlayersInView(ObjectLibrary objectLibrary)
        {        
            List<Player> playersInView = new List<Player>();

            BoundingFrustum viewFrustum;

            Matrix matrixModelView = Matrix.CreateLookAt(mass.currentPosition,
                                                         mass.currentPosition + mass.normalVector,
                                                         mass.upVector);

            // Create a limited range projection matrix for distance culling
            Matrix limitedMatrixProjection = Matrix.CreatePerspectiveFieldOfView((float)Math.PI / 4,
                                                                                 (1.0f * Statics.RenderSettings.GAME_WINDOW_WIDTH) /
                                                                                  Statics.RenderSettings.GAME_WINDOW_HEIGHT,
                                                                                 0.3f,
                                                                                 1000.0f * (1.0f * Statics.PlayerSettings.AI_INTELLIGENCE / 2.0f));

            viewFrustum = new BoundingFrustum(matrixModelView * limitedMatrixProjection);

            //// Check for human in radius
            //for (int i = 0; i < objectLibrary.humans.Count; i++)
            //{
            //    if (objectLibrary.humans[i].IsAlive())
            //    {
            //        // Player found!
            //        // Is he in view?
            //        if (viewFrustum.Contains(objectLibrary.humans[i].mass.boundingSphere) == ContainmentType.Contains ||
            //            viewFrustum.Contains(objectLibrary.humans[i].mass.boundingSphere) == ContainmentType.Intersects)
            //        {
            //            playersInView.Add(objectLibrary.humans[i]);
            //        }
            //    }
            //}

            // Check for AI in radius
            // If in survival mode.. ignore other AI bots
            // Also, don't attack other bots unless there are no human players visible
            //if (Statics.GameSettings.gameType != Enums.GameType.SURVIVAL && Statics.GameSettings.gameType != Enums.GameType.STORY_MODE && playersInView.Count == 0)
            //{
            //    for (int i = 0; i < objectLibrary.bots.Count; i++)
            //    {
            //        if (objectLibrary.bots[i].IsAlive() && objectLibrary.bots[i] != this)
            //        {
            //            //Player found!
            //            //is he in view?
            //            if (viewFrustum.Contains(objectLibrary.bots[i].mass.boundingSphere) == ContainmentType.Contains ||
            //                viewFrustum.Contains(objectLibrary.bots[i].mass.boundingSphere) == ContainmentType.Intersects)
            //            {
            //                playersInView.Add(objectLibrary.bots[i]);
            //            }
            //        }
            //    }
            //}
            return playersInView;
        }
*/