using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.AI;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace PolyPerfect
{
    [RequireComponent(typeof(Animator)), RequireComponent(typeof(CharacterController))]
    public class WanderScript : MonoBehaviour
    {
        private const float contingencyDistance = 1f;

        [SerializeField]
        public IdleState[] idleStates;
        [SerializeField]
        private MovementState[] movementStates;
        [SerializeField]
        private AIState[] attackingStates;
        [SerializeField]
        private AIState[] deathStates;

        [SerializeField]
        private string species = "NA";

        [SerializeField, Tooltip("This specific animal stats asset, create a new one from the asset menu under (LowPolyAnimals/NewAnimalStats)")]
        AIStats stats;

        [SerializeField, Tooltip("How far away from it's origin this animal will wander by itself.")]
        private float wanderZone = 10f;
        public float MaxDistance
        {
            get
            {
                return wanderZone;
            }
            set
            {
#if UNITY_EDITOR
                SceneView.RepaintAll();
#endif
                wanderZone = value;
            }
        }

        // [SerializeField, Tooltip("How dominent this animal is in the food chain, agressive animals will attack less dominant animals.")]
        private int dominance = 1;
        private int originalDominance = 0;

        [SerializeField, Tooltip("How far this animal can sense a predator.")]
        private float awareness = 30f;

        [SerializeField, Tooltip("How far this animal can sense it's prey.")]
        private float scent = 30f;
        private float originalScent = 0f;

        // [SerializeField, Tooltip("How many seconds this animal can run for before it gets tired.")]
        private float stamina = 10f;

        // [SerializeField, Tooltip("How much this damage this animal does to another animal.")]
        private float power = 10f;

        // [SerializeField, Tooltip("How much health this animal has.")]
        private float toughness = 5f;

        // [SerializeField, Tooltip("Chance of this animal attacking another animal."), Range(0f, 100f)]
        private float agression = 0f;
        private float originalAgression = 0f;

        // [SerializeField, Tooltip("How quickly the animal does damage to another animal (every 'attackSpeed' seconds will cause 'power' amount of damage).")]
        private float attackSpeed = 0.5f;

        // [SerializeField, Tooltip("If true, this animal will attack other animals of the same specices.")]
        private bool territorial = false;

        // [SerializeField, Tooltip("Stealthy animals can't be detected by other animals.")]
        private bool stealthy = false;

        [SerializeField, Tooltip("If true, this animal will never leave it's zone, even if it's chasing or running away from another animal.")]
        private bool constainedToWanderZone = false;

        [SerializeField, Tooltip("This animal will be peaceful towards species in this list.")]
        private string[] nonAgressiveTowards;

        private static List<WanderScript> allAnimals = new List<WanderScript>();
        public static List<WanderScript> AllAnimals { get { return allAnimals; } }

        //[Space(), Space(5)]
        [SerializeField, Tooltip("If true, this animal will rotate to match the terrain. Ensure you have set the layer of the terrain as 'Terrain'.")]
        private bool matchSurfaceRotation = false;
        [SerializeField, Tooltip("How fast the animnal rotates to match the surface rotation.")]
        private float surfaceRotationSpeed = 2f;

        //[Space(), Space(5)]
        [SerializeField, Tooltip("If true, AI changes to this animal will be logged in the console.")]
        private bool logChanges = false;
        [SerializeField, Tooltip("If true, gizmos will be drawn in the editor.")]
        private bool showGizmos = false;
        [SerializeField]
        private bool drawWanderRange = true;
        [SerializeField]
        private bool drawScentRange = true;
        [SerializeField]
        private bool drawAwarenessRange = true;

        public UnityEngine.Events.UnityEvent deathEvent;
        public UnityEngine.Events.UnityEvent attackingEvent;
        public UnityEngine.Events.UnityEvent idleEvent;
        public UnityEngine.Events.UnityEvent movementEvent;



        private Color distanceColor = new Color(0f, 0f, 205f);
        private Color awarnessColor = new Color(1f, 0f, 1f, 1f);
        private Color scentColor = new Color(1f, 0f, 0f, 1f);
        private Animator animator;
        private CharacterController characterController;
        private NavMeshAgent navMeshAgent;
        private Vector3 origin;
        private int totalIdleStateWeight;
        private int currentState = 0;
        private bool dead = false;
        private bool moving = false;
        private bool useNavMesh = false;
        private Vector3 targetLocation = Vector3.zero;
        private float currentTurnSpeed = 0f;
        private bool attacking = false;

        public void OnDrawGizmosSelected()
        {
            if (!showGizmos)
                return;

            if (drawWanderRange)
            {
                // Draw circle of radius wander zone
                Gizmos.color = distanceColor;
                Gizmos.DrawWireSphere(origin == Vector3.zero ? transform.position : origin, wanderZone);

                Vector3 IconWander = new Vector3(transform.position.x, transform.position.y + wanderZone, transform.position.z);
                Gizmos.DrawIcon(IconWander, "ico-wander", true);
            }

            if (drawAwarenessRange)
            {
                //Draw circle radius for Awarness.
                Gizmos.color = awarnessColor;
                Gizmos.DrawWireSphere(transform.position, awareness);


                Vector3 IconAwareness = new Vector3(transform.position.x, transform.position.y + awareness, transform.position.z);
                Gizmos.DrawIcon(IconAwareness, "ico-awareness", true);
            }

            if (drawScentRange)
            {
                //Draw circle radius for Scent.
                Gizmos.color = scentColor;
                Gizmos.DrawWireSphere(transform.position, scent);

                Vector3 IconScent = new Vector3(transform.position.x, transform.position.y + scent, transform.position.z);
                Gizmos.DrawIcon(IconScent, "ico-scent", true);
            }

            if (!Application.isPlaying)
                return;

            // Draw target position.
            if (useNavMesh)
            {
                if (navMeshAgent.remainingDistance > 1f)
                {
                    Gizmos.DrawSphere(navMeshAgent.destination + new Vector3(0f, 0.1f, 0f), 0.2f);
                    Gizmos.DrawLine(transform.position, navMeshAgent.destination);
                }
            }
            else
            {
                if (targetLocation != Vector3.zero)
                {
                    Gizmos.DrawSphere(targetLocation + new Vector3(0f, 0.1f, 0f), 0.2f);
                    Gizmos.DrawLine(transform.position, targetLocation);
                }
            }
        }

        private void Awake()
        {
            animator = GetComponent<Animator>();

            var runtimeController = animator.runtimeAnimatorController;

            if (runtimeController == null)
            {
                Debug.LogError(string.Format("{0} has no animator controller, make sure you put one in to allow the character to walk. See documentation for more details (1)", gameObject.name));
                enabled = false;
                return;
            }

            if (animator.avatar == null)
            {
                Debug.LogError(string.Format("{0} has no avatar, make sure you put one in to allow the character to animate. See documentation for more details (2)", gameObject.name));
                enabled = false;
                return;
            }

            if (animator.hasRootMotion == true)
            {
                Debug.LogError(string.Format("{0} has root motion applied, consider turning this off as our script will deactivate this on play as we do not use it (3)", gameObject.name));
                animator.applyRootMotion = false;
            }

            if (idleStates.Length == 0 || movementStates.Length == 0)
            {
                Debug.LogError(string.Format("{0} has no idle or movement states, make sure you fill these out. See documentation for more details (4)", gameObject.name));
                enabled = false;
                return;
            }

            if (idleStates.Length > 0)
            {
                for (int i = 0; i < idleStates.Length; i++)
                {
                    if (idleStates[i].animationBool == "")
                    {
                        Debug.LogError(string.Format("{0} has " + idleStates.Length + " Idle states, you need to make sure that each state has an animation boolean. See documentation for more details (4)", gameObject.name));
                        enabled = false;
                        return;
                    }
                }
            }

            if (movementStates.Length > 0)
            {
                for (int i = 0; i < movementStates.Length; i++)
                {
                    if (movementStates[i].animationBool == "")
                    {
                        Debug.LogError(string.Format("{0} has " + movementStates.Length + " Movement states, you need to make sure that each state has an animation boolean to see the character walk. See documentation for more details (4)", gameObject.name));
                        enabled = false;
                        return;
                    }

                    if (movementStates[i].moveSpeed <= 0)
                    {
                        Debug.LogError(string.Format("{0} has a movement state with a speed of 0 or less, you need to set the speed higher than 0 to see the character move. See documentation for more details (4)", gameObject.name));
                        enabled = false;
                        return;
                    }

                    if (movementStates[i].turnSpeed <= 0)
                    {
                        Debug.LogError(string.Format("{0} has a turn speed state with a speed of 0 or less, you need to set the speed higher than 0 to see the character turn. See documentation for more details (4)", gameObject.name));
                        enabled = false;
                        return;
                    }
                }
            }

            if (attackingStates.Length == 0)
            {
                Debug.Log(string.Format("{0} has " + attackingStates.Length + " this character will not be able to attack. See documentation for more details (4)", gameObject.name));
            }

            if (attackingStates.Length > 0)
            {
                for (int i = 0; i < attackingStates.Length; i++)
                {
                    if (attackingStates[i].animationBool == "")
                    {
                        Debug.LogError(string.Format("{0} has " + attackingStates.Length + " attacking states, you need to make sure that each state has an animation boolean. See documentation for more details (4)", gameObject.name));
                        enabled = false;
                        return;
                    }
                }
            }

            if (stats == null)
            {
                Debug.LogError(string.Format("{0} has no AI stats, make sure you assign one to the wander script. See documentation for more details (5)", gameObject.name));
                enabled = false;
                return;
            }

            foreach (IdleState state in idleStates)
            {
                totalIdleStateWeight += state.stateWeight;
            }

            origin = transform.position;
            animator.applyRootMotion = false;
            characterController = GetComponent<CharacterController>();
            navMeshAgent = GetComponent<NavMeshAgent>();

            //Assign the stats to variables
            originalDominance = stats.dominance;
            toughness = stats.toughness;
            territorial = stats.territorial;
            stamina = stats.stamina;
            originalAgression = stats.agression;
            attackSpeed = stats.attackSpeed;
            stealthy = stats.stealthy;

            originalScent = scent;

            if (navMeshAgent)
            {
                useNavMesh = true;
                navMeshAgent.stoppingDistance = contingencyDistance;
            }

            if (matchSurfaceRotation && transform.childCount > 0)
            {
                transform.GetChild(0).gameObject.AddComponent<SurfaceRotation>().SetRotationSpeed(surfaceRotationSpeed);
            }

            allAnimals.Add(this);
        }

        private void Start()
        {
            if (WanderManager.Instance != null && WanderManager.Instance.PeaceTime)
            {
                SetPeaceTime(true);
            }

            StartCoroutine(InitYield());
        }

        private void OnDestroy()
        {
            allAnimals.Remove(this);
        }

        private IEnumerator InitYield()
        {
            yield return new WaitForSeconds((Random.Range(0, 200) / 100));
            DecideNextState(false, true);
        }

        private void DecideNextState(bool wasIdle, bool firstState = false)
        {
            attacking = false;

            // Look for a predator.
            if (awareness > 0)
            {
                for (int i = 0; i < allAnimals.Count; i++)
                {
                    if (allAnimals[i].dead == true || allAnimals[i] == this || allAnimals[i].species == species || allAnimals[i].dominance <= dominance || allAnimals[i].stealthy || allAnimals[i].gameObject.activeSelf == false)
                    {
                        continue;
                    }

                    if (Vector3.Distance(transform.position, allAnimals[i].transform.position) > awareness)
                    {
                        continue;
                    }

                    if (useNavMesh)
                    {
                        RunAwayFromAnimal(allAnimals[i]);
                    }
                    else
                    {
                        NonNavMeshRunAwayFromAnimal(allAnimals[i]);
                    }

                    if (logChanges)
                    {
                        Debug.Log(string.Format("{0}: Found predator ({1}), running away.", gameObject.name, allAnimals[i].gameObject.name));
                    }

                    return;
                }
            }

            // Look for pray.
            if (dominance > 0)
            {
                for (int i = 0; i < allAnimals.Count; i++)
                {
                    if (allAnimals[i].dead == true || allAnimals[i] == this || (allAnimals[i].species == species && !territorial) || allAnimals[i].dominance > dominance || allAnimals[i].stealthy)
                    {
                        continue;
                    }

                    int p = System.Array.IndexOf(nonAgressiveTowards, allAnimals[i].species);
                    if (p > -1)
                    {
                        continue;
                    }

                    if (Vector3.Distance(transform.position, allAnimals[i].transform.position) > scent)
                    {
                        continue;
                    }

                    if (Random.Range(0, 99) > agression)
                    {
                        continue;
                    }

                    if (logChanges)
                    {
                        Debug.Log(string.Format("{0}: Found prey ({1}), chasing.", gameObject.name, allAnimals[i].gameObject.name));
                    }

                    if (allAnimals[i] == null)
                    {
                        continue;
                    }

                    ChaseAnimal(allAnimals[i]);
                    return;
                }
            }

            if (wasIdle && movementStates.Length > 0)
            {
                if (logChanges)
                {
                    Debug.Log(string.Format("{0}: Wandering.", gameObject.name));
                }
                BeginWanderState();
                return;
            }
            else if (idleStates.Length > 0)
            {
                if (logChanges)
                {
                    Debug.Log(string.Format("{0}: Idling.", gameObject.name));
                }
                BeginIdleState(firstState);
                return;
            }

            // Backup selection.
            if (idleStates.Length == 0)
            {
                BeginWanderState();
            }
            else if (movementStates.Length == 0)
            {
                BeginIdleState();
            }
        }

        private void BeginIdleState(bool firstState = false)
        {
            if (!firstState)
            {
                int randomValue = Random.Range(0, totalIdleStateWeight);
                for (int i = 0; i < idleStates.Length; i++)
                {
                    if (randomValue < idleStates[i].stateWeight)
                    {
                        currentState = i;
                        break;
                    }

                    randomValue = randomValue - idleStates[i].stateWeight;
                }
            }

            if (idleStates.Length == 0)
            {
                BeginWanderState();
                return;
            }

            if (!string.IsNullOrEmpty(idleStates[currentState].animationBool))
            {
                animator.SetBool(idleStates[currentState].animationBool, true);
            }

            float stateTime = firstState ?
              (Random.Range(50f, idleStates[currentState].minStateTime * 100f)) / 100f
              : (Random.Range(idleStates[currentState].minStateTime * 100f, idleStates[currentState].maxStateTime * 100f)) / 100f;

            StartCoroutine(IdleState(stateTime));
        }

        private IEnumerator IdleState(float stateTime)
        {
            moving = false;

            yield return new WaitForSeconds(stateTime);

            if (idleStates[currentState] != null || idleStates.Length < currentState)
            {
                if (!string.IsNullOrEmpty(idleStates[currentState].animationBool))
                {
                    animator.SetBool(idleStates[currentState].animationBool, false);
                }
            }

            idleEvent.Invoke();

            DecideNextState(true);
        }

        private void BeginWanderState()
        {
            Vector3 target = RandonPointInRange();

            int slowestMovementState = 0;
            for (int i = 0; i < movementStates.Length; i++)
            {
                if (movementStates[i].moveSpeed < movementStates[slowestMovementState].moveSpeed)
                {
                    slowestMovementState = i;
                }
            }
            currentState = slowestMovementState;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, true);
            }

            movementEvent.Invoke();

            if (useNavMesh)
            {
                StartCoroutine(MovementState(target));
            }
            else
            {
                StartCoroutine(NonNavMeshMovementState(target));
            }
        }

        private IEnumerator MovementState(Vector3 target)
        {
            moving = true;

            navMeshAgent.speed = movementStates[currentState].moveSpeed;
            navMeshAgent.angularSpeed = movementStates[currentState].turnSpeed;
            navMeshAgent.SetDestination(target);

            float timeMoving = 0f;
            while ((navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance || timeMoving < 0.1f) && timeMoving < movementStates[currentState].maxStateTime)
            {
                timeMoving += Time.deltaTime;
                yield return null;
            }

            navMeshAgent.SetDestination(transform.position);

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            DecideNextState(false);
        }

        private IEnumerator NonNavMeshMovementState(Vector3 target)
        {
            moving = true;
            targetLocation = target;
            currentTurnSpeed = movementStates[currentState].turnSpeed;

            float walkTime = 0f;
            float timeUntilAbortWalk = Vector3.Distance(transform.position, target) / movementStates[currentState].moveSpeed;

            while (Vector3.Distance(transform.position, target) > contingencyDistance && walkTime < timeUntilAbortWalk)
            {
                characterController.SimpleMove(transform.TransformDirection(Vector3.forward) * movementStates[currentState].moveSpeed);

                Vector3 relativePos = target - transform.position;
                Quaternion rotation = Quaternion.LookRotation(relativePos);
                transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * (currentTurnSpeed / 10));
                currentTurnSpeed += Time.deltaTime;

                walkTime += Time.deltaTime;
                yield return null;
            }

            targetLocation = Vector3.zero;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            DecideNextState(false);
        }

        private void RunAwayFromAnimal(WanderScript predator)
        {
            moving = true;

            Quaternion startRotation = transform.rotation;
            transform.rotation = Quaternion.LookRotation(transform.position - predator.transform.position);
            Vector3 areaAwayFromPredator = transform.position + transform.forward * 5f;
            NavMeshHit hit;
            NavMesh.SamplePosition(areaAwayFromPredator, out hit, 5, 1 << NavMesh.GetAreaFromName("Walkable"));
            Vector3 target = hit.position;
            transform.rotation = startRotation;

            if (constainedToWanderZone && Vector3.Distance(target, origin) > wanderZone)
            {
                target = RandonPointInRange();
            }

            int fastestMovementState = 0;
            for (int i = 0; i < movementStates.Length; i++)
            {
                if (movementStates[i].moveSpeed > movementStates[fastestMovementState].moveSpeed)
                {
                    fastestMovementState = i;
                }
            }
            currentState = fastestMovementState;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, true);
            }

            StartCoroutine(RunAwayState(target, predator));
        }

        private IEnumerator RunAwayState(Vector3 target, WanderScript predator)
        {
            navMeshAgent.speed = movementStates[currentState].moveSpeed;
            navMeshAgent.angularSpeed = movementStates[currentState].turnSpeed;
            navMeshAgent.SetDestination(target);

            float timeMoving = 0f;
            while ((navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance || timeMoving < 0.1f) && timeMoving < stamina)
            {
                timeMoving += Time.deltaTime;
                yield return null;
            }

            navMeshAgent.SetDestination(transform.position);

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            if (timeMoving > stamina || predator.dead || Vector3.Distance(transform.position, predator.transform.position) > awareness)
            {
                BeginIdleState();
            }
            else
            {
                RunAwayFromAnimal(predator);
            }
        }

        private void NonNavMeshRunAwayFromAnimal(WanderScript predator)
        {
            moving = true;

            Quaternion startRotation = transform.rotation;
            transform.rotation = Quaternion.LookRotation(transform.position - predator.transform.position);
            targetLocation = transform.position + transform.forward * 5f;
            transform.rotation = startRotation;

            if (constainedToWanderZone && Vector3.Distance(targetLocation, origin) > wanderZone)
            {
                targetLocation = RandonPointInRange();
            }

            int fastestMovementState = 0;
            for (int i = 0; i < movementStates.Length; i++)
            {
                if (movementStates[i].moveSpeed > movementStates[fastestMovementState].moveSpeed)
                {
                    fastestMovementState = i;
                }
            }
            currentState = fastestMovementState;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, true);
            }

            StartCoroutine(NonNavMeshRunAwayState(targetLocation, predator));
        }

        private IEnumerator NonNavMeshRunAwayState(Vector3 target, WanderScript predator)
        {
            currentTurnSpeed = movementStates[currentState].turnSpeed;

            float walkTime = 0f;
            float timeUntilAbortWalk = Vector3.Distance(transform.position, target) / movementStates[currentState].moveSpeed;

            while (Vector3.Distance(transform.position, target) > contingencyDistance && walkTime < timeUntilAbortWalk && stamina > 0)
            {
                characterController.SimpleMove(transform.TransformDirection(Vector3.forward) * movementStates[currentState].moveSpeed);

                Vector3 relativePos = target - transform.position;
                Quaternion rotation = Quaternion.LookRotation(relativePos);
                transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * (currentTurnSpeed / 10));
                currentTurnSpeed += Time.deltaTime;

                walkTime += Time.deltaTime;
                stamina -= Time.deltaTime;
                yield return null;
            }

            targetLocation = Vector3.zero;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            if (stamina <= 0 || predator.dead || Vector3.Distance(transform.position, predator.transform.position) > awareness)
            {
                BeginIdleState();
            }
            else
            {
                NonNavMeshRunAwayFromAnimal(predator);
            }
        }

        private void ChaseAnimal(WanderScript prey)
        {
            Vector3 target = prey.transform.position;
            prey.BeginChase(this);

            if (movementStates.Length <= 0)
            {
                Debug.Log("Movement states length is 0");
                this.enabled = false;
                return;
            }
            int fastestMovementState = 0;
            for (int i = 0; i < movementStates.Length; i++)
            {
                if (movementStates[i].moveSpeed > movementStates[fastestMovementState].moveSpeed)
                {
                    fastestMovementState = i;
                }
            }
            currentState = fastestMovementState;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, true);
            }

            if (useNavMesh)
            {
                StartCoroutine(ChaseState(prey));
            }
            else
            {
                StartCoroutine(NonNavMeshChaseState(prey));
            }
        }

        private IEnumerator ChaseState(WanderScript prey)
        {
            moving = true;

            navMeshAgent.speed = movementStates[currentState].moveSpeed;
            navMeshAgent.angularSpeed = movementStates[currentState].turnSpeed;
            navMeshAgent.SetDestination(prey.transform.position);

            float timeMoving = 0f;
            bool gotAway = false;
            while ((navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance || timeMoving < 0.1f) && timeMoving < stamina)
            {
                navMeshAgent.SetDestination(prey.transform.position);

                timeMoving += Time.deltaTime;

                if (Vector3.Distance(transform.position, prey.transform.position) < 2f)
                {
                    if (logChanges)
                    {
                        Debug.Log(string.Format("{0}: Caught prey ({1})!", gameObject.name, prey.gameObject.name));
                    }

                    if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
                    {
                        animator.SetBool(movementStates[currentState].animationBool, false);
                    }

                    AttackAnimal(prey);
                    yield break;
                }

                if (constainedToWanderZone && Vector3.Distance(transform.position, origin) > wanderZone)
                {
                    gotAway = true;
                    navMeshAgent.SetDestination(transform.position);
                    break;
                }

                yield return null;
            }

            navMeshAgent.SetDestination(transform.position);

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            if (timeMoving > stamina || prey.dead || Vector3.Distance(transform.position, prey.transform.position) > scent || gotAway)
            {
                BeginIdleState();
            }
            else
            {
                ChaseAnimal(prey);
            }
        }

        private IEnumerator NonNavMeshChaseState(WanderScript prey)
        {
            moving = true;
            targetLocation = prey.transform.position;
            currentTurnSpeed = movementStates[currentState].turnSpeed;

            float walkTime = 0f;
            bool gotAway = false;
            float timeUntilAbortWalk = Vector3.Distance(transform.position, targetLocation) / movementStates[currentState].moveSpeed;


            while (Vector3.Distance(transform.position, targetLocation) > contingencyDistance && walkTime < timeUntilAbortWalk && stamina > 0)
            {
                characterController.SimpleMove(transform.TransformDirection(Vector3.forward) * movementStates[currentState].moveSpeed);
                targetLocation = prey.transform.position;

                Vector3 relativePos = targetLocation - transform.position;
                Quaternion rotation = Quaternion.LookRotation(relativePos);
                transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * (currentTurnSpeed / 10));
                currentTurnSpeed += Time.deltaTime;

                walkTime += Time.deltaTime;
                stamina -= Time.deltaTime;

                if (Vector3.Distance(transform.position, prey.transform.position) < 2f)
                {
                    if (logChanges)
                    {
                        Debug.Log(string.Format("{0}: Caught prey ({1})!", gameObject.name, prey.gameObject.name));
                    }

                    if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
                    {
                        animator.SetBool(movementStates[currentState].animationBool, false);
                    }

                    AttackAnimal(prey);
                    yield break;
                }

                if (constainedToWanderZone && Vector3.Distance(transform.position, origin) > wanderZone)
                {
                    gotAway = true;
                    targetLocation = transform.position;
                    break;
                }

                yield return null;
            }

            targetLocation = Vector3.zero;

            if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
            {
                animator.SetBool(movementStates[currentState].animationBool, false);
            }

            if (stamina <= 0 || prey.dead || Vector3.Distance(transform.position, prey.transform.position) > scent || gotAway)
            {
                BeginIdleState();
            }
            else
            {
                ChaseAnimal(prey);
            }
        }

        private void AttackAnimal(WanderScript target)
        {
            attacking = true;

            if (logChanges)
            {
                Debug.Log(string.Format("{0}: Attacking {1}!", gameObject.name, target.gameObject.name));
            }

            if (useNavMesh)
            {
                navMeshAgent.SetDestination(transform.position);
            }
            else
            {
                targetLocation = transform.position;
            }

            currentState = Random.Range(0, attackingStates.Length);

            if (attackingStates.Length > 0)
            {
                if (!string.IsNullOrEmpty(attackingStates[currentState].animationBool))
                {
                    animator.SetBool(attackingStates[currentState].animationBool, true);
                }
            }

            StartCoroutine(MakeAttack(target));
        }

        private IEnumerator MakeAttack(WanderScript target)
        {
            target.GetAttacked(this);

            float timer = 0f;
            while (!target.dead)
            {
                timer += Time.deltaTime;

                if (timer > attackSpeed)
                {
                    target.TakeDamage(power);
                    timer = 0f;
                }

                yield return null;
            }

            if (attackingStates.Length > 0 && !string.IsNullOrEmpty(attackingStates[currentState].animationBool))
            {
                animator.SetBool(attackingStates[currentState].animationBool, false);
            }

            attackingEvent.Invoke();

            StopAllCoroutines();
            DecideNextState(false);
        }

        private void GetAttacked(WanderScript attacker)
        {
            if (attacking)
            {
                return;
            }

            if (logChanges)
            {
                Debug.Log(string.Format("{0}: Getting attacked by {1}!", gameObject.name, attacker.gameObject.name));
            }
            StopAllCoroutines();

            StartCoroutine(TurnToLookAtTarget(attacker.transform));

            if (agression > 0)
            {
                if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
                {
                    animator.SetBool(movementStates[currentState].animationBool, false);
                }

                AttackAnimal(attacker);
            }
            else
            {
                if (moving)
                {
                    if (useNavMesh)
                    {
                        navMeshAgent.SetDestination(transform.position);
                    }
                    else
                    {
                        targetLocation = transform.position;
                    }

                    if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
                    {
                        animator.SetBool(movementStates[currentState].animationBool, false);
                    }

                    moving = false;
                }
                else
                {
                    if (idleStates.Length > 0 && !string.IsNullOrEmpty(idleStates[currentState].animationBool))
                    {
                        animator.SetBool(idleStates[currentState].animationBool, false);
                    }
                }
            }
        }

        private void TakeDamage(float damage)
        {
            toughness -= damage;

            if (toughness <= 0)
            {
                Die();
            }
        }

        public void Die()
        {
            if (logChanges)
            {
                Debug.Log(string.Format("{0}: Died!", gameObject.name));
            }

            StopAllCoroutines();
            dead = true;


            if (useNavMesh)
            {
                navMeshAgent.SetDestination(transform.position);
            }
            else
            {
                targetLocation = transform.position;
            }

            foreach (AIState state in idleStates)
            {
                if (!string.IsNullOrEmpty(state.animationBool))
                {
                    animator.SetBool(state.animationBool, false);
                }
            }

            foreach (AIState state in movementStates)
            {
                if (!string.IsNullOrEmpty(state.animationBool))
                {
                    animator.SetBool(state.animationBool, false);
                }
            }

            foreach (AIState state in attackingStates)
            {
                if (!string.IsNullOrEmpty(state.animationBool))
                {
                    animator.SetBool(state.animationBool, false);
                }
            }

            if (deathStates.Length > 0)
            {
                currentState = Random.Range(0, deathStates.Length);

                if (!string.IsNullOrEmpty(deathStates[currentState].animationBool))
                {
                    animator.SetBool(deathStates[currentState].animationBool, true);
                }
            }

            else
            {
                var renderer = GetComponentsInChildren<SkinnedMeshRenderer>();

                foreach (var item in renderer)
                {
                    item.enabled = false;
                }
            }

            deathEvent.Invoke();
        }

        public void SetPeaceTime(bool peace)
        {
            if (peace)
            {
                dominance = 0;
                scent = 0f;
                agression = 0f;
            }
            else
            {
                dominance = originalDominance;
                scent = originalScent;
                agression = originalAgression;
            }
        }

        private Vector3 RandonPointInRange()
        {
            Vector3 randomPoint = origin + Random.insideUnitSphere * wanderZone;
            return new Vector3(randomPoint.x, transform.position.y, randomPoint.z);
        }

        private IEnumerator TurnToLookAtTarget(Transform target)
        {
            while (true)
            {
                Vector3 direction = target.position - transform.position;

                if (Vector3.Angle(direction, transform.forward) < 1f)
                {
                    break;
                }

                float step = 2f * Time.deltaTime;
                Vector3 newDirection = Vector3.RotateTowards(transform.forward, direction, step, 0.0f);
                transform.rotation = Quaternion.LookRotation(newDirection);
                yield return null;
            }
        }

        private void BeginChase(WanderScript chasingAnimal)
        {
            if (attacking)
            {
                return;
            }

            StartCoroutine(ChaseCheck(chasingAnimal));
        }

        private IEnumerator ChaseCheck(WanderScript chasingAnimal)
        {
            while (Vector3.Distance(transform.position, chasingAnimal.transform.position) > awareness)
            {
                yield return new WaitForSeconds(0.5f);
            }

            StopAllCoroutines();
            if (moving)
            {
                if (useNavMesh)
                {
                    navMeshAgent.SetDestination(transform.position);
                }
                else
                {
                    targetLocation = transform.position;
                }

                if (!string.IsNullOrEmpty(movementStates[currentState].animationBool))
                {
                    animator.SetBool(movementStates[currentState].animationBool, false);
                }

                moving = false;
            }
            else
            {
                if (idleStates.Length - 1 >= currentState)
                    if (idleStates.Length > 0 && !string.IsNullOrEmpty(idleStates[currentState].animationBool))
                    {
                        animator.SetBool(idleStates[currentState].animationBool, false);
                    }
            }

            DecideNextState(false);
        }
    }
}