/**
 * 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 System;
using UnityEngine;
namespace Live2D.Cubism.Framework.Pose
{
    /// 
    /// Cubism pose controller.
    /// 
    public sealed class CubismPoseController : MonoBehaviour, ICubismUpdatable
    {
        #region variable
        /// 
        /// Default visible pose index.
        /// 
        [SerializeField]
        public int defaultPoseIndex = 0;
        /// 
        /// Back opacity threshold.
        /// 
        private const float BackOpacityThreshold = 0.15f;
        /// 
        /// Cubism model cache.
        /// 
        private CubismModel _model;
        /// 
        /// Model has update controller component.
        /// 
        [HideInInspector]
        public bool HasUpdateController { get; set; }
        /// 
        /// Pose data.
        /// 
        private CubismPoseData[][] _poseData;
        #endregion
        #region Function
        /// 
        /// update hidden part opacity.
        /// 
        public void Refresh()
        {
            _model = this.FindCubismModel();
            // Fail silently...
            if (_model == null)
            {
                return;
            }
            var tags = _model
                .Parts
                .GetComponentsMany();
            for(var i = 0; i < tags.Length; ++i)
            {
                var groupIndex = tags[i].GroupIndex;
                var partIndex = tags[i].PartIndex;
                if(_poseData == null || _poseData.Length <= groupIndex)
                {
                    Array.Resize(ref _poseData, groupIndex + 1);
                }
                if(_poseData[groupIndex] == null || _poseData[groupIndex].Length <= partIndex)
                {
                    Array.Resize(ref _poseData[groupIndex], partIndex + 1);
                }
                _poseData[groupIndex][partIndex].PosePart = tags[i];
                _poseData[groupIndex][partIndex].Part= tags[i].GetComponent();
                defaultPoseIndex = (defaultPoseIndex < 0) ? 0 : defaultPoseIndex;
                if (partIndex != defaultPoseIndex)
                {
                    _poseData[groupIndex][partIndex].Part.Opacity = 0.0f;
                }
                _poseData[groupIndex][partIndex].Opacity = _poseData[groupIndex][partIndex].Part.Opacity;
                if(tags[i].Link == null || tags[i].Link.Length == 0)
                {
                    continue;
                }
                _poseData[groupIndex][partIndex].LinkParts = new CubismPart[tags[i].Link.Length];
                for(var j = 0; j < tags[i].Link.Length; ++j)
                {
                    var linkId = tags[i].Link[j];
                    _poseData[groupIndex][partIndex].LinkParts[j] = _model.Parts.FindById(linkId);
                }
            }
            // Get cubism update controller.
            HasUpdateController = (GetComponent() != null);
        }
        /// 
        /// update hidden part opacity.
        /// 
        private void DoFade()
        {
            for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
            {
                var appearPartsGroupIndex = -1;
                var appearPartsGroupOpacity = 1.0f;
                // Find appear parts group index and opacity.
                for (var i = 0; i < _poseData[groupIndex].Length; ++i)
                {
                    var part = _poseData[groupIndex][i].Part;
                    if(part.Opacity > _poseData[groupIndex][i].Opacity)
                    {
                        appearPartsGroupIndex = i;
                        appearPartsGroupOpacity = part.Opacity;
                        break;
                    }
                }
                // Fail silently...
                if(appearPartsGroupIndex < 0)
                {
                    return;
                }
                // Delay disappearing parts groups disappear.
                for (var i = 0; i < _poseData[groupIndex].Length; ++i)
                {
                    // Fail silently...
                    if(i == appearPartsGroupIndex)
                    {
                        continue;
                    }
                    var part = _poseData[groupIndex][i].Part;
                    var delayedOpacity = part.Opacity;
                    var backOpacity = (1.0f - delayedOpacity) * (1.0f - appearPartsGroupOpacity);
                    // When restricting the visible proportion of the background
                    if (backOpacity > BackOpacityThreshold)
                    {
                        delayedOpacity = 1.0f - BackOpacityThreshold / (1.0f - appearPartsGroupOpacity);
                    }
                    // Overwrite the opacity if it's greater than the delayed opacity.
                    if (part.Opacity > delayedOpacity)
                    {
                        part.Opacity = delayedOpacity;
                    }
                }
            }
        }
        /// 
        /// Copy opacity to linked parts.
        /// 
        private void CopyPartOpacities()
        {
            for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
            {
                for (var partIndex = 0; partIndex < _poseData[groupIndex].Length; ++partIndex)
                {
                    var linkParts = _poseData[groupIndex][partIndex].LinkParts;
                    if(linkParts == null)
                    {
                        continue;
                    }
                    var opacity = _poseData[groupIndex][partIndex].Part.Opacity;
                    for (var linkIndex = 0; linkIndex < linkParts.Length; ++linkIndex)
                    {
                        var linkPart = linkParts[linkIndex];
                        if(linkPart != null)
                        {
                            linkPart.Opacity = opacity;
                        }
                    }
                }
            }
        }
        /// 
        /// Save parts opacity.
        /// 
        private void SavePartOpacities()
        {
            for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
            {
                for (var partIndex = 0; partIndex < _poseData[groupIndex].Length; ++partIndex)
                {
                    _poseData[groupIndex][partIndex].Opacity = _poseData[groupIndex][partIndex].Part.Opacity;
                }
            }
        }
        /// 
        /// Called by cubism update controller. Order to invoke OnLateUpdate.
        /// 
        public int ExecutionOrder
        {
            get { return CubismUpdateExecutionOrder.CubismPoseController; }
        }
        /// 
        /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
        /// 
        public bool NeedsUpdateOnEditing
        {
            get { return false; }
        }
        /// 
        /// Called by cubism update manager. Updates controller.
        /// 
        public void OnLateUpdate()
        {
            // Fail silently...
            if (!enabled || _model == null || _poseData == null)
            {
               return;
            }
            DoFade();
            CopyPartOpacities();
            SavePartOpacities();
        }
        #endregion
        #region Unity Event Handling
        /// 
        /// Called by Unity. Makes sure cache is initialized.
        /// 
        private void OnEnable()
        {
            Refresh();
        }
        /// 
        /// Called by Unity.
        /// 
        private void LateUpdate()
        {
            if(!HasUpdateController)
            {
                OnLateUpdate();
            }
        }
        #endregion
    }
}