/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.MotionFade;
using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
namespace Live2D.Cubism.Framework.Motion
{
    /// 
    /// Cubism motion controller.
    /// 
    [RequireComponent(typeof(CubismFadeController))]
    public class CubismMotionController : MonoBehaviour
    {
        #region Action
        /// 
        /// Action animation end handler.
        /// 
        [SerializeField]
        public Action AnimationEndHandler;
        /// 
        /// Action OnAnimationEnd.
        /// 
        private void OnAnimationEnd(int layerIndex, float instanceId)
        {
            _motionPriorities[layerIndex] = CubismMotionPriority.PriorityNone;
            if (AnimationEndHandler != null)
            {
                AnimationEndHandler(instanceId);
            }
        }
        #endregion
        #region Variable
        /// 
        /// Layer count.
        /// 
        public int LayerCount = 1;
        /// 
        /// List of cubism fade motion.
        /// 
        private CubismFadeMotionList _cubismFadeMotionList;
        /// 
        /// Motion controller is active.
        /// 
        private bool _isActive = false;
        /// 
        /// Playable graph controller.
        /// 
        private PlayableGraph _playableGrap;
        /// 
        /// Playable output.
        /// 
        private AnimationPlayableOutput _playableOutput;
        /// 
        /// Animation layer mixer.
        /// 
        private AnimationLayerMixerPlayable _layerMixer;
        /// 
        /// Cubism motion layers.
        /// 
        private CubismMotionLayer[] _motionLayers;
        /// 
        /// Cubism motion priorities.
        /// 
        private int[] _motionPriorities;
        #endregion Variable
        #region Function
        /// 
        /// Play animations.
        /// 
        /// Animator clip.
        /// layer index.
        /// Animation priority
        /// Animation is loop.
        /// Animation speed.
        public void PlayAnimation(AnimationClip clip, int layerIndex = 0, int priority = CubismMotionPriority.PriorityNormal, bool isLoop = true, float speed = 1.0f)
        {
            // Fail silently...
            if(!enabled || !_isActive || _cubismFadeMotionList == null || clip == null
               || layerIndex < 0 || layerIndex >= LayerCount ||
               ((_motionPriorities[layerIndex] >= priority) && (priority != CubismMotionPriority.PriorityForce)))
            {
                Debug.Log("can't start motion.");
                return;
            }
            _motionPriorities[layerIndex] = priority;
            _motionLayers[layerIndex].PlayAnimation(clip, isLoop, speed);
            // Play Playable Graph
            if(!_playableGrap.IsPlaying())
            {
                _playableGrap.Play();
            }
        }
        /// 
        /// Stop animation.
        /// 
        /// Animator index.
        /// layer index.
        public void StopAnimation(int animationIndex, int layerIndex = 0)
        {
            // Fail silently...
            if(layerIndex < 0 || layerIndex >= LayerCount)
            {
                return;
            }
            _motionLayers[layerIndex].StopAnimationClip();
        }
        /// 
        /// Stop all animation.
        /// 
        public void StopAllAnimation()
        {
            for(var i = 0; i < LayerCount; ++i)
            {
                _motionLayers[i].StopAnimationClip();
            }
        }
        /// 
        /// Is playing animation.
        /// 
        /// True if the animation is playing.
        public bool IsPlayingAnimation(int layerIndex = 0)
        {
            // Fail silently...
            if(layerIndex < 0 || layerIndex >= LayerCount)
            {
                return false;
            }
            return !_motionLayers[layerIndex].IsFinished;
        }
        /// 
        /// Set layer weight.
        /// 
        /// layer index.
        /// Layer weight.
        public void SetLayerWeight(int layerIndex, float weight)
        {
            // Fail silently...
            if(layerIndex <= 0 || layerIndex >= LayerCount)
            {
                return;
            }
            _motionLayers[layerIndex].SetLayerWeight(weight);
            _layerMixer.SetInputWeight(layerIndex, weight);
        }
        /// 
        /// Set layer blend type is additive.
        /// 
        /// layer index.
        /// Blend type is additive.
        public void SetLayerAdditive(int layerIndex, bool isAdditive)
        {
            // Fail silently...
            if(layerIndex <= 0 || layerIndex >= LayerCount)
            {
                return;
            }
            _layerMixer.SetLayerAdditive((uint)layerIndex, isAdditive);
        }
        /// 
        /// Set animation speed.
        /// 
        /// layer index.
        /// index of playing motion list.
        /// Animation speed.
        public void SetAnimationSpeed(int layerIndex, int index, float speed)
        {
            // Fail silently...
            if(layerIndex < 0 || layerIndex >= LayerCount)
            {
                return;
            }
            _motionLayers[layerIndex].SetStateSpeed(index, speed);
        }
        /// 
        /// Set animation is loop.
        /// 
        /// layer index.
        /// Index of playing motion list.
        /// State is loop.
        public void SetAnimationIsLoop(int layerIndex, int index, bool isLoop)
        {
            // Fail silently...
            if(layerIndex < 0 || layerIndex >= LayerCount)
            {
                return;
            }
            _motionLayers[layerIndex].SetStateIsLoop(index, isLoop);
        }
        /// 
        /// Get cubism fade states.
        /// 
        public ICubismFadeState[] GetFadeStates()
        {
            if(_motionLayers == null)
            {
                LayerCount = (LayerCount < 1) ? 1 : LayerCount;
                _motionLayers = new CubismMotionLayer[LayerCount];
                _motionPriorities = new int[LayerCount];
            }
            return _motionLayers;
        }
        #endregion Function
        #region Unity Events Handling
        /// 
        /// Called by Unity.
        /// 
        private void OnEnable()
        {
            _cubismFadeMotionList = GetComponent().CubismFadeMotionList;
            // Fail silently...
            if(_cubismFadeMotionList == null)
            {
                Debug.LogError("CubismMotionController : CubismFadeMotionList doesn't set in CubismFadeController.");
                return;
            }
            // Get Animator.
            var animator = GetComponent();
            if (animator.runtimeAnimatorController != null)
            {
                Debug.LogWarning("Animator Controller was set in Animator component.");
                return;
            }
            _isActive = true;
            // Disable animator's playablegrap.
            var graph = animator.playableGraph;
            if(graph.IsValid())
            {
                graph.GetOutput(0).SetWeight(0);
            }
            // Create Playable Graph.
#if UNITY_2018_1_OR_NEWER
            _playableGrap = PlayableGraph.Create("Playable Graph : " + this.FindCubismModel().name);
#else
            _playableGrap = PlayableGraph.Create();
#endif
            _playableGrap.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
            // Create Playable Output.
            _playableOutput = AnimationPlayableOutput.Create(_playableGrap, "Animation", animator);
            _playableOutput.SetWeight(1);
            // Create animation layer mixer.
            _layerMixer = AnimationLayerMixerPlayable.Create(_playableGrap, LayerCount);
            // Create cubism motion layers.
            if(_motionLayers == null)
            {
                LayerCount = (LayerCount < 1) ? 1 : LayerCount;
                _motionLayers = new CubismMotionLayer[LayerCount];
                _motionPriorities = new int[LayerCount];
            }
            for(var i = 0; i < LayerCount; ++i)
            {
                _motionLayers[i] = CubismMotionLayer.CreateCubismMotionLayer(_playableGrap, _cubismFadeMotionList, i);
                _motionLayers[i].AnimationEndHandler += OnAnimationEnd;
                _layerMixer.ConnectInput(i, _motionLayers[i].PlayableOutput, 0);
                _layerMixer.SetInputWeight(i, 1.0f);
            }
            // Set Playable Output.
            _playableOutput.SetSourcePlayable(_layerMixer);
        }
        /// 
        /// Called by Unity.
        /// 
        private void OnDisable()
        {
            // Destroy _playableGrap.
            if(_playableGrap.IsValid())
            {
                _playableGrap.Destroy();
            }
        }
        /// 
        /// Called by Unity.
        /// 
        private void Update()
        {
            // Fail silently...
            if(!_isActive)
            {
                return;
            }
            for( var i = 0; i < _motionLayers.Length; ++i)
            {
                _motionLayers[i].Update();
                if (_motionLayers[i].IsFinished)
                {
                    _motionPriorities[i] = 0;
                }
            }
        }
        #endregion Unity Events Handling
    }
}