using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace RebootKit.Engine.Animations {
    [Serializable]
    public class MixerNode : IReAnimatorNode {
        [Serializable]
        struct InputState {
            [SerializeReference] public IReAnimatorNode node;

            [Range(0.0f, 1.0f)] public float targetWeight;
            [NonSerialized] public float CurrentWeight;
        }
        
        [field: SerializeField] public string Name { get; private set; }
        
        AnimationMixerPlayable m_Mixer;

        [SerializeField] float m_TransitionSpeed = 5.0f;
        [SerializeField] InputState[] m_Inputs;
        
        public void Tick(float deltaTime) {
            for (int i = 0; i < m_Inputs.Length; ++i){
                if (m_TransitionSpeed > 0.0f) {
                    m_Inputs[i].CurrentWeight = Mathf.MoveTowards(m_Inputs[i].CurrentWeight,
                                                                  m_Inputs[i].targetWeight,
                                                                  m_TransitionSpeed * deltaTime);
                } else {
                    m_Inputs[i].CurrentWeight = m_Inputs[i].targetWeight;
                }
                
                m_Mixer.SetInputWeight(i, m_Inputs[i].CurrentWeight);

                m_Inputs[i].node.Tick(deltaTime);
            }
        }

        public IPlayable Build(PlayableGraph graph) {
            m_Mixer = AnimationMixerPlayable.Create(graph, m_Inputs.Length);

            for (int i = 0; i < m_Inputs.Length; ++i) {
                IPlayable playable = m_Inputs[i].node.Build(graph);

                if (playable is AnimationMixerPlayable mixerPlayable) {
                    m_Mixer.ConnectInput(i, mixerPlayable, 0, m_Inputs[i].targetWeight);
                } else if (playable is AnimationClipPlayable clipPlayable) {
                    m_Mixer.ConnectInput(i, clipPlayable, 0, m_Inputs[i].targetWeight);
                } else if (playable is AnimationLayerMixerPlayable layerMixerPlayable) {
                    m_Mixer.ConnectInput(i, layerMixerPlayable, 0, m_Inputs[i].targetWeight);
                }
            }

            return m_Mixer;
        }

        public bool TryFindChild(string name, out IReAnimatorNode node) {
            for (int i = 0; i < m_Inputs.Length; i++) {
                if (m_Inputs[i].node.Name.Equals(name, StringComparison.Ordinal)) {
                    node = m_Inputs[i].node;
                    return true;
                }

                if (m_Inputs[i].node.TryFindChild(name, out IReAnimatorNode childNode)) {
                    node = childNode;
                    return true;
                }
            }

            node = null;
            return false;
        }

        public void SetInputWeight(int index, float weight) {
            m_Inputs[index].targetWeight = weight;
        }
    }
}