using R3;
using NaughtyAttributes;
using UnityEngine;
using KitsuneCafe.System;
using KitsuneCafe.Extension;

namespace KitsuneCafe.Entity
{
    public class EntityAnimator : MonoBehaviour
    {
        private const float epsilon = 0.05f;

        [SerializeField]
        private Animator animator;

        [SerializeField]
        private new Rigidbody rigidbody;

        [SerializeField]
        private Motor motor;

        [SerializeField]
        private SprintFeature sprint;

        [SerializeField, AnimatorParam("animator")]
        private string movementSpeedParameter;

        [SerializeField]
        private float movementSmoothTime = 0.1f;

        [SerializeField, AnimatorParam("animator")]
        private string sprintingParameter;

        [SerializeField, Range(0f, 1f)]
        private float walkAnimationParamMax = 0.5f;

        [SerializeField, Range(0f, 1f)]
        private float sprintAnimationParamMax = 1.0f;

        [SerializeField, AnimatorParam("animator")]
        private string readyParam;

        private void Reset()
        {
            animator = GetComponent<Animator>();
            rigidbody = GetComponent<Rigidbody>();
            motor = GetComponent<Motor>();
            sprint = GetComponent<SprintFeature>();
        }

        private void Awake()
        {
            var d = Disposable.CreateBuilder();

            CurrentVelocity()
                .Compose(CurrentAnimationSpeed)
                .SmoothDamp(
                    () => animator.GetFloat(movementSpeedParameter),
                    movementSmoothTime,
                    UnityTimeProvider.FixedUpdate
                )
                .Subscribe(speed =>
                {
                    animator.SetFloat(movementSpeedParameter, speed);
                })
                .AddTo(ref d);

            sprint.IsSprinting
                .DistinctUntilChanged()
                .Subscribe(isSprinting =>
                {
                    animator.SetBool(sprintingParameter, isSprinting);
                })
                .AddTo(ref d);

            d.RegisterTo(destroyCancellationToken);
        }

        private Observable<float> CurrentVelocity()
        {
            return Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
                .Select(_ => rigidbody.linearVelocity.sqrMagnitude)
                .DistinctUntilChanged()
                .Select(Mathf.Sqrt);
        }

        private Observable<float> CurrentAnimationSpeed(Observable<float> speedSource)
        {
            var scaleFactor = motor.MaxSpeedSource.Select(maxSpeed => NormalizationFactor(maxSpeed, motor.DefaultMaxSpeed));

            return speedSource
                .CombineLatest(motor.MaxSpeedSource, scaleFactor, NormalizeSpeed)
                .DistinctUntilChanged(FEqualityComparer<float>.Create(KMath.IsApproxEqual));
        }

        private float NormalizationFactor(float maxSpeed, float defaultMaxSpeed)
        {
            return maxSpeed > defaultMaxSpeed ? sprintAnimationParamMax : walkAnimationParamMax;
        }

        private float NormalizeSpeed(float currentSpeed, float maxSpeed, float factor)
        {
            if (currentSpeed > maxSpeed && maxSpeed > epsilon)
            {
                return factor;
            }
            else
            {
                return Mathf.InverseLerp(0, maxSpeed, currentSpeed) * factor;
            }
        }
    }
}