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

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

            public AvatarMask mask;
            public bool isAdditive;

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

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

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

        public IPlayable Build(PlayableGraph graph) {
            m_LayerMixer = AnimationLayerMixerPlayable.Create(graph, m_Layers.Length);

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

                if (playable is AnimationMixerPlayable mixerPlayable) {
                    m_LayerMixer.ConnectInput(i, mixerPlayable, 0, m_Layers[i].targetWeight);
                } else if (playable is AnimationClipPlayable clipPlayable) {
                    m_LayerMixer.ConnectInput(i, clipPlayable, 0, m_Layers[i].targetWeight);
                } else if (playable is AnimationLayerMixerPlayable layerMixerPlayable) {
                    m_LayerMixer.ConnectInput(i, layerMixerPlayable, 0, m_Layers[i].targetWeight);
                }
                
                m_LayerMixer.SetLayerAdditive((uint)i, m_Layers[i].isAdditive);

                if (m_Layers[i].mask != null) {
                    m_LayerMixer.SetLayerMaskFromAvatarMask((uint)i, m_Layers[i].mask);
                }
            }

            return m_LayerMixer;
        }

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

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

            node = null;
            return false;
        }

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