// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //

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

using Animancer.Units;
using UnityEngine;

namespace Animancer.Samples.Mixers
{
    /// <summary>
    /// Controls Animancer parameters to make the character move
    /// towards the mouse position using Root Motion in a 2D Mixer.
    /// </summary>
    /// 
    /// <remarks>
    /// <strong>Sample:</strong>
    /// <see href="https://kybernetik.com.au/animancer/docs/samples/mixers/directional">
    /// Directional Mixers</see>
    /// </remarks>
    /// 
    /// https://kybernetik.com.au/animancer/api/Animancer.Samples.Mixers/FollowMousePosition
    /// 
    [AddComponentMenu(Strings.SamplesMenuPrefix + "Mixers - Follow Mouse Position")]
    [AnimancerHelpUrl(typeof(FollowMousePosition))]
    public class FollowMousePosition : MonoBehaviour
    {
        /************************************************************************************************************************/
#if UNITY_PHYSICS_3D
        /************************************************************************************************************************/

        // We could hard code the parameter names like this:
        // public static readonly StringReference ParameterX = "Movement X";
        // public static readonly StringReference ParameterY = "Movement Y";
        // But that would make this script less flexible
        // and you wouldn't be able to see what it's using in the Inspector.

        [SerializeField] private AnimancerComponent _Animancer;
        [SerializeField] private StringAsset _ParameterX;
        [SerializeField] private StringAsset _ParameterY;
        [SerializeField, Seconds] private float _ParameterSmoothTime = 0.15f;
        [SerializeField, Meters] private float _StopProximity = 0.1f;

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

        private SmoothedVector2Parameter _SmoothedParameters;

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

        protected virtual void Awake()
        {
            _SmoothedParameters = new SmoothedVector2Parameter(
                _Animancer,
                _ParameterX,
                _ParameterY,
                _ParameterSmoothTime);
        }

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

        protected virtual void Update()
        {
            // Calculate the movement direction.
            Vector3 movementDirection = GetMovementDirection();

            // The movement direction is in world space,
            // so we need to convert it to the character's local space
            // to be appropriate for their current rotation.
            Vector3 localDirection = transform.InverseTransformDirection(movementDirection);

            // Then set the target value for the parameters to move towards:
            // - Parameter X towards Direction X (right/left).
            // - Parameter Y towards Direction Z (forwards/backwards).
            // - Ignore Direction Y because the Mixer is only 2D.
            _SmoothedParameters.TargetValue = new Vector2(localDirection.x, localDirection.z);
        }

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

        private Vector3 GetMovementDirection()
        {
            // Get a ray from the main camera in the direction of the mouse cursor.
            Ray ray = Camera.main.ScreenPointToRay(SampleInput.MousePosition);

            // Raycast with it and stop trying to move it it doesn't hit anything.
            if (!Physics.Raycast(ray, out RaycastHit raycastHit))// Note the exclamation mark !
                return Vector3.zero;

            // If the ray hit something, calculate the direction from this object to that point.
            Vector3 direction = raycastHit.point - transform.position;

            // If we are close to the destination, stop moving.
            float squaredDistance = direction.sqrMagnitude;
            if (squaredDistance <= _StopProximity * _StopProximity)
            {
                return Vector3.zero;
            }
            else
            {
                // Otherwise normalize the direction so that we don't change speed based on distance.
                // Calling direction.Normalize() would do the same thing, but would calculate the magnitude again.
                return direction / Mathf.Sqrt(squaredDistance);
            }
        }

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

        protected virtual void OnDestroy()
        {
            // It's not necessary for this sample,
            // but if this component could be destroyed before the rest of the character
            // then we need to Dispose the smoother to remove it from the target parameters.
            _SmoothedParameters.Dispose();
        }

        /************************************************************************************************************************/
#else
        /************************************************************************************************************************/

        protected virtual void Awake()
        {
            SampleReadMe.LogMissingPhysics3DModuleError(this);
        }

        /************************************************************************************************************************/
#endif
        /************************************************************************************************************************/
    }
}
