/**
 * 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;
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Live2D.Cubism.Rendering
{
    /// 
    /// Controls rendering of a .
    /// 
    [ExecuteInEditMode, CubismDontMoveOnReimport]
    public sealed class CubismRenderController : MonoBehaviour, ICubismUpdatable
    {
        /// 
        /// Model opacity.
        /// 
        /// 
        /// This is turned into a field to be available to s...
        /// 
        [SerializeField, HideInInspector]
        public float Opacity = 1f;
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private float _lastOpacity;
        /// 
        /// Last model opacity.
        /// 
        private float LastOpacity
        {
            get { return _lastOpacity; }
            set { _lastOpacity = value; }
        }
        /// 
        /// Sorting layer name.
        /// 
        public string SortingLayer
        {
            get
            {
                return UnityEngine.SortingLayer.IDToName(SortingLayerId);
            }
            set
            {
                SortingLayerId = UnityEngine.SortingLayer.NameToID(value);
            }
        }
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private int _sortingLayerId;
        /// 
        /// Sorting layer Id.
        /// 
        public int SortingLayerId
        {
            get
            {
                return _sortingLayerId;
            }
            set
            {
                if (value == _sortingLayerId)
                {
                    return;
                }
                _sortingLayerId = value;
                // Apply sorting layer.
                var renderers = Renderers;
                for (var i = 0; i < renderers.Length; ++i)
                {
                    renderers[i].OnControllerSortingLayerDidChange(_sortingLayerId);
                }
            }
        }
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private CubismSortingMode _sortingMode;
        /// 
        ///  sorting.
        /// 
        public CubismSortingMode SortingMode
        {
            get
            {
                return _sortingMode;
            }
            set
            {
                // Return early if same value given.
                if (value == _sortingMode)
                {
                    return;
                }
                _sortingMode = value;
                // Flip sorting.
                var renderers = Renderers;
                for (var i = 0; i < renderers.Length; ++i)
                {
                    renderers[i].OnControllerSortingModeDidChange(_sortingMode);
                }
            }
        }
        /// 
        /// Order in sorting layer.
        /// 
        [SerializeField, HideInInspector]
        private int _sortingOrder;
        /// 
        /// Order in sorting layer.
        /// 
        public int SortingOrder
        {
            get
            {
                return _sortingOrder;
            }
            set
            {
                // Return early in case same value given.
                if (value == _sortingOrder)
                {
                    return;
                }
                _sortingOrder = value;
                // Apply new sorting order.
                var renderers = Renderers;
                for (var i = 0; i < renderers.Length; ++i)
                {
                    renderers[i].OnControllerSortingOrderDidChange(SortingOrder);
                }
            }
        }
        /// 
        /// [Optional] Camera to face.
        /// 
        [SerializeField]
        public Camera CameraToFace;
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private Object _drawOrderHandler;
        /// 
        /// Draw order handler proxy object.
        /// 
        public Object DrawOrderHandler
        {
            get { return _drawOrderHandler; }
            set { _drawOrderHandler = value.ToNullUnlessImplementsInterface(); }
        }
        /// 
        ///  backing field.
        /// 
        [NonSerialized]
        private ICubismDrawOrderHandler _drawOrderHandlerInterface;
        /// 
        /// Listener for draw order changes.
        /// 
        private ICubismDrawOrderHandler DrawOrderHandlerInterface
        {
            get
            {
                if (_drawOrderHandlerInterface == null)
                {
                    _drawOrderHandlerInterface = DrawOrderHandler.GetInterface();
                }
                return _drawOrderHandlerInterface;
            }
        }
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private Object _opacityHandler;
        /// 
        /// Opacity handler proxy object.
        /// 
        public Object OpacityHandler
        {
            get { return _opacityHandler; }
            set { _opacityHandler = value.ToNullUnlessImplementsInterface(); }
        }
        /// 
        ///  backing field.
        /// 
        private ICubismOpacityHandler _opacityHandlerInterface;
        /// 
        /// Listener for opacity changes.
        /// 
        private ICubismOpacityHandler OpacityHandlerInterface
        {
            get
            {
                if (_opacityHandlerInterface == null)
                {
                    _opacityHandlerInterface = OpacityHandler.GetInterface();
                }
                return _opacityHandlerInterface;
            }
        }
        /// 
        /// The value to offset the s by.
        /// 
        /// 
        /// You only need to adjust this value when using perspective cameras.
        /// 
        [SerializeField, HideInInspector]
        public float _depthOffset = 0.00001f;
        /// 
        /// Depth offset used when sorting by depth.
        /// 
        public float DepthOffset
        {
            get { return _depthOffset; }
            set
            {
                // Return if same value given.
                if (Mathf.Abs(value - _depthOffset) < Mathf.Epsilon)
                {
                    return;
                }
                // Store value.
                _depthOffset = value;
                // Apply it.
                var renderers = Renderers;
                for (var i = 0; i < renderers.Length; ++i)
                {
                    renderers[i].OnControllerDepthOffsetDidChange(_depthOffset);
                }
            }
        }
        /// 
        /// Model the controller belongs to.
        /// 
        private CubismModel Model
        {
            get { return this.FindCubismModel(); }
        }
        /// 
        ///  backing field.
        /// 
        private Transform _drawablesRootTransform;
        /// 
        /// Root transform of all s of the model.
        /// 
        private Transform DrawablesRootTransform
        {
            get
            {
                if (_drawablesRootTransform == null)
                {
                    _drawablesRootTransform = Model.Drawables[0].transform.parent;
                }
                return _drawablesRootTransform;
            }
        }
        /// 
        /// s backing field.
        /// 
        [NonSerialized]
        private CubismRenderer[] _renderers;
        /// 
        /// s.
        /// 
        public CubismRenderer[] Renderers
        {
            get
            {
                if (_renderers== null)
                {
                    _renderers = Model.Drawables.GetComponentsMany();
                }
                return _renderers;
            }
            private set { _renderers = value; }
        }
        /// 
        /// Model has update controller component.
        /// 
        [HideInInspector]
        public bool HasUpdateController { get; set; }
        /// 
        /// Makes sure all s have s attached to them.
        /// 
        private void TryInitializeRenderers()
        {
            // Try get renderers.
            var renderers = Renderers;
            // Create renderers if necesssary.
            if (renderers == null || renderers.Length == 0)
            {
                // Create renders and apply it to backing field...
                var drawables = this
                .FindCubismModel()
                .Drawables;
                renderers = drawables.AddComponentEach();
                // Store renderers.
                Renderers = renderers;
            }
            // Make sure renderers are initialized.
            for (var i = 0; i < renderers.Length; ++i)
            {
                renderers[i].TryInitialize();
            }
            // Initialize sorting layer.
            // We set the backing field here directly because we pull the sorting layer directly from the renderer.
            _sortingLayerId = renderers[0]
                .MeshRenderer
                .sortingLayerID;
        }
        /// 
        /// Updates opacity if necessary.
        /// 
        private void UpdateOpacity()
        {
            // Return if same value given.
            if (Mathf.Abs(Opacity - LastOpacity) < Mathf.Epsilon)
            {
                return;
            }
            // Store value.
            Opacity = Mathf.Clamp(Opacity, 0f, 1f);
            LastOpacity = Opacity;
            // Apply opacity.
            var applyOpacityToRenderers = (OpacityHandlerInterface == null || Opacity > (1f - Mathf.Epsilon));
            if (applyOpacityToRenderers)
            {
                var renderers = Renderers;
                for (var i = 0; i < renderers.Length; ++i)
                {
                    renderers[i].OnModelOpacityDidChange(Opacity);
                }
            }
            // Call handler.
            if (OpacityHandlerInterface != null)
            {
                OpacityHandlerInterface.OnOpacityDidChange(this, Opacity);
            }
        }
        /// 
        /// Called by cubism update controller. Order to invoke OnLateUpdate.
        /// 
        public int ExecutionOrder
        {
            get { return CubismUpdateExecutionOrder.CubismRenderController; }
        }
        /// 
        /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
        /// 
        public bool NeedsUpdateOnEditing
        {
            get { return true; }
        }
        /// 
        /// Called by cubism update controller. Applies billboarding.
        /// 
        public void OnLateUpdate()
        {
            // Fail silently...
            if(!enabled)
            {
                return;
            }
            // Update opacity if necessary.
            UpdateOpacity();
            // Return early in case no camera is to be faced.
            if (CameraToFace == null)
            {
                return;
            }
            // Face camera.
            DrawablesRootTransform.rotation = (Quaternion.LookRotation(CameraToFace.transform.forward, Vector3.up));
        }
        #region Unity Event Handling
        /// 
        /// Called by Unity.
        /// 
        private void Start()
        {
            // Get cubism update controller.
            HasUpdateController = (GetComponent() != null);
        }
        /// 
        /// Called by Unity. Enables listening to render data updates.
        /// 
        private void OnEnable()
        {
            // Fail silently.
            if (Model == null)
            {
                return;
            }
            // Make sure renderers are available.
            TryInitializeRenderers();
            // Register listener.
            Model.OnDynamicDrawableData += OnDynamicDrawableData;
        }
        /// 
        /// Called by Unity. Disables listening to render data updates.
        /// 
        private void OnDisable()
        {
            // Fail silently.
            if (Model == null)
            {
                return;
            }
            // Deregister listener.
            Model.OnDynamicDrawableData -= OnDynamicDrawableData;
        }
        #endregion
        #region Cubism Event Handling
        /// 
        /// Called by Unity.
        /// 
        private void LateUpdate()
        {
            if(!HasUpdateController)
            {
                OnLateUpdate();
            }
        }
        /// 
        /// Called whenever new render data is available.
        /// 
        /// Model with new render data.
        /// New render data.
        private void OnDynamicDrawableData(CubismModel sender, CubismDynamicDrawableData[] data)
        {
            // Get drawables.
            var drawables = sender.Drawables;
            var renderers = Renderers;
            // Handle render data changes.
            for (var i = 0; i < data.Length; ++i)
            {
                // Controls whether mesh buffers are to be swapped.
                var swapMeshes = false;
                // Update visibility if last SwapInfo flag is true.
                renderers[i].UpdateVisibility();
                // Update render order if last SwapInfo flags is true.
                renderers[i].UpdateRenderOrder();
                // Skip completely non-dirty data.
                if (!data[i].IsAnyDirty)
                {
                    continue;
                }
                // Update visibility.
                if (data[i].IsVisibilityDirty)
                {
                    renderers[i].OnDrawableVisiblityDidChange(data[i].IsVisible);
                    swapMeshes = true;
                }
                // Update render order.
                if (data[i].IsRenderOrderDirty)
                {
                    renderers[i].OnDrawableRenderOrderDidChange(data[i].RenderOrder);
                    swapMeshes = true;
                }
                // Update opacity.
                if (data[i].IsOpacityDirty)
                {
                    renderers[i].OnDrawableOpacityDidChange(data[i].Opacity);
                    swapMeshes = true;
                }
                // Update vertex positions.
                if (data[i].AreVertexPositionsDirty)
                {
                    renderers[i].OnDrawableVertexPositionsDidChange(data[i].VertexPositions);
                    swapMeshes = true;
                }
                // Swap buffers if necessary.
                // [INV] Swapping only half of the meshes might improve performance even. Would that be visually feasible?
                if (swapMeshes)
                {
                    renderers[i].SwapMeshes();
                }
            }
            // Pass draw order changes to handler (if available).
            var drawOrderHandler = DrawOrderHandlerInterface;
            if (drawOrderHandler != null)
            {
                for (var i = 0; i < data.Length; ++i)
                {
                    if (data[i].IsDrawOrderDirty)
                    {
                        drawOrderHandler.OnDrawOrderDidChange(this, drawables[i], data[i].DrawOrder);
                    }
                }
            }
        }
        #endregion
    }
}