using System;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

namespace Animation
{
    // 제네릭인 P 는 값 형식이면서 Enum을 상속한 타입이어야 한다.
    public class AnimatorManager<P> where P : struct, Enum
    {
        private readonly Animator animator;

        // 마지막 세팅 값을 저장해 불필요한 동작 방지용
        private readonly Dictionary<P, object> cache = new();

        // 키별 실제 타입을 매핑해두는 용도
        private readonly Dictionary<P, AnimatorControllerParameterType> types = new();

        // 없는 키를 경고하기 위한 용도 (Enum에는 있는데 Animator에는 없는 경우)
        private readonly HashSet<P> missing = new();

        // 없는 키를 경고하기 위한 용도 (Animator에는 있는데 Enum에는 없는 경우)
        private readonly HashSet<string> extras = new();


        public AnimatorManager(Animator animator)
        {
            this.animator = animator;
            if (this.animator == null) return;

            cache.Clear();
            types.Clear();
            missing.Clear();
            extras.Clear();

            var parameters = animator.parameters;
            foreach (var param in parameters)
            {
                if (Enum.TryParse<P>(param.name, out var key))
                {
                    types[key] = param.type;
                    switch (param.type)
                    {
                        case AnimatorControllerParameterType.Bool:
                            cache[key] = animator.GetBool(param.name);
                            break;
                        case AnimatorControllerParameterType.Float:
                            cache[key] = animator.GetFloat(param.name);
                            break;
                        case AnimatorControllerParameterType.Int:
                            cache[key] = animator.GetInteger(param.name);
                            break;
                    }
                }
                else
                {
                    extras.Add(param.name);
                }
            }

            foreach (P enumKey in (P[])Enum.GetValues(typeof(P)))
            {
                if (!types.ContainsKey(enumKey))
                    missing.Add(enumKey);
            }
        }

        public void SetValue(P key, object value)
        {
            if (animator == null) throw new InvalidOperationException("Animator is null");

            // missing은 가능하나 extras는 불가능(변수가 P 로 들어오기에 Animator에는 있는데 Enum에 없는 경우는 불가능)
            if (missing.Contains(key)) throw new KeyNotFoundException("key is missing in animator: " + key);

            string name = key.ToString();

            switch (types[key])
            {
                case AnimatorControllerParameterType.Bool:
                {
                    var newVal = Convert.ToBoolean(value);
                    if (!cache.TryGetValue(key, out var oldVal) || !Equals(newVal, (bool)oldVal))
                    {
                        animator.SetBool(name, newVal);
                        cache[key] = newVal;
                    }

                    break;
                }
                case AnimatorControllerParameterType.Float:
                {
                    var newVal = Convert.ToSingle(value);
                    if (!cache.TryGetValue(key, out var oldVal) || !Mathf.Approximately(newVal, (float)oldVal))
                    {
                        animator.SetFloat(name, newVal);
                        cache[key] = newVal;
                    }

                    break;
                }
                case AnimatorControllerParameterType.Int:
                {
                    var newVal = Convert.ToInt32(value);
                    if (!cache.TryGetValue(key, out var oldVal) || !Equals(newVal, (int)oldVal))
                    {
                        animator.SetInteger(name, newVal);
                        cache[key] = newVal;
                    }

                    break;
                }
                case AnimatorControllerParameterType.Trigger:
                {
                    var newVal = Convert.ToBoolean(value);
                    if (newVal) animator.SetTrigger(name);
                    else animator.ResetTrigger(name);
                    break;
                }
            }
        }

        public object GetValue(P key)
        {
            if (types[key] != AnimatorControllerParameterType.Trigger)
            {
                return cache[key];
            }
            else
            {
                throw new InvalidOperationException("Trigger type cannot be get");
            }
        }
    }

}