// Animancer // Copyright 2020 Kybernetik //

#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.

using Animancer.FSM;
using UnityEngine;

namespace Animancer.Examples.StateMachines.Brains
{
    /// <summary>
    /// A <see cref="CreatureBrain"/> which controls the creature using mouse input.
    /// </summary>
    [AddComponentMenu(Strings.MenuPrefix + "Examples/Brains - Mouse Brain")]
    [HelpURL(Strings.APIDocumentationURL + ".Examples.StateMachines.Brains/MouseBrain")]
    public sealed class MouseBrain : CreatureBrain
    {
        /************************************************************************************************************************/

        [SerializeField] private CreatureState _Locomotion;
        [SerializeField] private float _StopDistance = 0.2f;
        [SerializeField] private float _MinRunDistance = 1;

        private Vector3? _Destination;

        /************************************************************************************************************************/

        private void OnEnable()
        {
            _Destination = null;
        }

        /************************************************************************************************************************/

        private void Update()
        {
            UpdateInput();
            UpdateMovement();
        }

        /************************************************************************************************************************/

        private void UpdateInput()
        {
            if (Input.GetMouseButton(0))
            {
                var creaturePosition = Creature.Rigidbody.position;

                var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

                RaycastHit raycastHit;
                if (Physics.Raycast(ray, out raycastHit))
                {
                    _Destination = raycastHit.point;
                    Debug.DrawLine(ray.origin, raycastHit.point, Color.green);
                }
                else
                {
                    // If the ray does not hit anything, just use the point where it intersects
                    // the XZ plane at the height the creature is currently at.
                    _Destination = CalculateRayTargetXZ(ray, creaturePosition.y);
                    if (_Destination != null)
                        Debug.DrawLine(ray.origin, _Destination.Value, Color.red);
                }

                IsRunning =
                    _Destination != null &&
                    Vector3.Distance(creaturePosition, _Destination.Value) >= _MinRunDistance;
            }
            else
            {
                IsRunning = false;
            }
        }

        /************************************************************************************************************************/

        public static Vector3? CalculateRayTargetXZ(Ray ray, float y = 0)
        {
            y = ray.origin.y - y;

            // If the ray starts above the target and is pointing up then it will never intersect.
            // Same if it is below and pointing down or if it is perfectly horizontal.
            if (ray.direction.y == 0 || SameSign(y, ray.direction.y))
                return null;

            return ray.origin - ray.direction * (y / ray.direction.y);
        }

        public static bool SameSign(float x, float y)
        {
            return
                (x > 0 && y > 0) ||
                (x < 0 && y < 0) ||
                (x == 0 && y == 0);
        }

        /************************************************************************************************************************/

        private void UpdateMovement()
        {
            if (_Destination != null)
            {
                var fromCurrentToDestination = _Destination.Value - Creature.Rigidbody.position;

                // Vector magnitudes are calculated using Pythagoras' Theorem which involves a square root.
                // Square roots are a much slower operation than simple arithmetic operations.
                // Since we only need to see which is greater, we can compare the squared magnitude and stop distance.
                if (fromCurrentToDestination.sqrMagnitude > _StopDistance * _StopDistance)
                {
                    MovementDirection = fromCurrentToDestination;
                    _Locomotion.TryEnterState();
                    Debug.DrawLine(Creature.Rigidbody.position, _Destination.Value, Color.cyan);
                    return;
                }
            }

            _Destination = null;
            MovementDirection = Vector3.zero;
            Creature.Idle.TryEnterState();
        }

        /************************************************************************************************************************/
    }
}