/**
 * 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 System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace Live2D.Cubism.Rendering.Masking
{
    /// 
    /// Controls rendering of Cubism masks.
    /// 
    [ExecuteInEditMode, CubismDontMoveOnReimport]
    public sealed class CubismMaskController : MonoBehaviour, ICubismMaskTextureCommandSource, ICubismUpdatable
    {
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private CubismMaskTexture _maskTexture;
        /// 
        /// Mask texture.
        /// 
        public CubismMaskTexture MaskTexture
        {
            get
            {
                // Fall back to global mask texture.
                if (_maskTexture == null)
                {
                    _maskTexture = CubismMaskTexture.GlobalMaskTexture;
                }
                return _maskTexture;
            }
            set
            {
                // Return early if same value given.
                if (value == _maskTexture)
                {
                    return;
                }
                _maskTexture = value;
                // Try switch mask textures.
                OnDestroy();
                Start();
            }
        }
        /// 
        /// s.
        /// 
        private CubismMaskMaskedJunction[] Junctions { get; set; }
        /// 
        /// True if controller is revived.
        /// 
        private bool IsRevived
        {
            get { return Junctions != null; }
        }
        /// 
        /// Model has update controller component.
        /// 
        [HideInInspector]
        public bool HasUpdateController { get; set; }
        /// 
        /// Makes sure controller is initialized once.
        /// 
        private void TryRevive()
        {
            if (IsRevived)
            {
                return;
            }
            ForceRevive();
        }
        /// 
        /// Initializes .
        /// 
        private void ForceRevive()
        {
            var drawables = this
                .FindCubismModel()
                .Drawables;
            // Find mask pairs.
            var pairs = new MasksMaskedsPairs();
            for (var i = 0; i < drawables.Length; i++)
            {
                if (!drawables[i].IsMasked)
                {
                    continue;
                }
                // Make sure no leftover null-entries are added as mask.
                var masks = Array.FindAll(drawables[i].Masks, mask => mask != null);
                if (masks.Length == 0)
                {
                    continue;
                }
                pairs.Add(drawables[i], masks);
            }
            // Initialize junctions.
            Junctions = new CubismMaskMaskedJunction[pairs.Entries.Count];
            for (var i = 0; i < Junctions.Length; ++i)
            {
                // Create mask renderers for junction.
                var masks = new CubismMaskRenderer[pairs.Entries[i].Masks.Length];
                for (var j = 0; j < masks.Length; ++j)
                {
                    masks[j] = new CubismMaskRenderer()
                        .SetMainRenderer(pairs.Entries[i].Masks[j]);
                }
                // Create junction.
                Junctions[i] = new CubismMaskMaskedJunction()
                    .SetMasks(masks)
                    .SetMaskeds(pairs.Entries[i].Maskeds.ToArray())
                    .SetMaskTexture(MaskTexture);
            }
        }
        /// 
        /// Called by cubism update controller. Order to invoke OnLateUpdate.
        /// 
        public int ExecutionOrder
        {
            get { return CubismUpdateExecutionOrder.CubismMaskController; }
        }
        /// 
        /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
        /// 
        public bool NeedsUpdateOnEditing
        {
            get { return true; }
        }
        /// 
        /// Called by cubism update controller. Updates .
        /// 
        public void OnLateUpdate()
        {
            if (!enabled || !IsRevived)
            {
                return;
            }
            for (var i = 0; i < Junctions.Length; ++i)
            {
                Junctions[i].Update();
            }
        }
        #region Unity Event Handling
        /// 
        /// Initializes instance.
        /// 
        private void Start()
        {
            // Fail silently.
            if (MaskTexture == null)
            {
                return;
            }
            MaskTexture.AddSource(this);
            // Get cubism update controller.
            HasUpdateController = (GetComponent() != null);
        }
        /// 
        /// Called by Unity.
        /// 
        private void LateUpdate()
        {
            if(!HasUpdateController)
            {
                OnLateUpdate();
            }
        }
        /// 
        /// Finalizes instance.
        /// 
        private void OnDestroy()
        {
            if (MaskTexture == null)
            {
                return;
            }
            MaskTexture.RemoveSource(this);
        }
        #endregion
        #region ICubismMaskDrawSource
        /// 
        /// Queries the number of tiles needed by the source.
        /// 
        /// The necessary number of tiles needed.
        int ICubismMaskTextureCommandSource.GetNecessaryTileCount()
        {
            TryRevive();
            return Junctions.Length;
        }
        /// 
        /// Assigns the tiles.
        /// 
        /// Tiles to assign.
        void ICubismMaskTextureCommandSource.SetTiles(CubismMaskTile[] value)
        {
            for (var i = 0; i < Junctions.Length; ++i)
            {
                Junctions[i].SetMaskTile(value[i]);
            }
        }
        /// 
        /// Called when source should instantly draw.
        /// 
        void ICubismMaskCommandSource.AddToCommandBuffer(CommandBuffer buffer)
        {
            for (var i = 0; i < Junctions.Length; ++i)
            {
                Junctions[i].AddToCommandBuffer(buffer);
            }
        }
        #endregion
        #region Mask-Masked Pair
        /// 
        /// Pair of masks and masked drawables.
        /// 
        private struct MasksMaskedsPair
        {
            /// 
            /// Mask drawables.
            /// 
            public CubismRenderer[] Masks;
            /// 
            /// Masked drawables.
            /// 
            public List Maskeds;
        }
        private class MasksMaskedsPairs
        {
            /// 
            /// List of 
            /// 
            public List Entries = new List();
            /// 
            /// Add  to the list.
            /// 
            public void Add(CubismDrawable masked, CubismDrawable[] masks)
            {
                // Try to add masked to existing mask compound.
                for (var i = 0; i < Entries.Count; ++i)
                {
                    var match = (Entries[i].Masks.Length == masks.Length);
                    if (!match)
                    {
                        continue;
                    }
                    for (var j = 0; j < Entries[i].Masks.Length; ++j)
                    {
                        if (Entries[i].Masks[j] != masks[j].GetComponent())
                        {
                            match = false;
                            break;
                        }
                    }
                    if (!match)
                    {
                        continue;
                    }
                    Entries[i].Maskeds.Add(masked.GetComponent());
                    return;
                }
                // Create new pair.
                var renderers = new CubismRenderer[masks.Length];
                for (var i = 0; i < masks.Length; ++i)
                {
                    renderers[i] = masks[i].GetComponent();
                }
                Entries.Add(new MasksMaskedsPair
                {
                    Masks = renderers,
                    Maskeds = new List() { masked.GetComponent() }
                });
            }
        }
        #endregion
    }
}