/**
 * 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 System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace Live2D.Cubism.Rendering.Masking
{
    /// 
    /// Texture for rendering masks.
    /// 
    [CreateAssetMenu(menuName = "Live2D Cubism/Mask Texture")]
    public sealed class CubismMaskTexture : ScriptableObject, ICubismMaskCommandSource
    {
        #region Conversion
        /// 
        /// Converts a  to a .
        /// 
        /// Value to convert.
        public static implicit operator Texture(CubismMaskTexture value)
        {
            return value.RenderTexture;
        }
        #endregion
        /// 
        /// The global mask texture.
        /// 
        public static CubismMaskTexture GlobalMaskTexture
        {
            get { return Resources.Load("Live2D/Cubism/GlobalMaskTexture"); }
        }
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private int _size = 1024;
        /// 
        /// Texture size in pixels.
        /// 
        public int Size
        {
            get { return _size; }
            set
            {
                // Return early if same value given.
                if (value == _size)
                {
                    return;
                }
                // Fail silently if not power-of-two.
                if (!value.IsPowerOfTwo())
                {
                    return;
                }
                // Apply changes.
                _size = value;
                RefreshRenderTexture();
            }
        }
        /// 
        /// Channel count.
        /// 
        public int Channels
        {
            get { return 4; }
        }
        /// 
        ///  backing field.
        /// 
        [SerializeField, HideInInspector]
        private int _subdivisions = 3;
        /// 
        /// Subdivision level.
        /// 
        public int Subdivisions
        {
            get { return _subdivisions; }
            set
            {
                if (value == _subdivisions)
                {
                    return;
                }
                // Apply changes.
                _subdivisions = value;
                RefreshRenderTexture();
            }
        }
        /// 
        /// Tile pool 'allocator'.
        /// 
        private CubismMaskTilePool TilePool { get; set; }
        /// 
        ///  backing field.
        /// 
        private RenderTexture _renderTexture;
        /// 
        ///  to draw on.
        /// 
        private RenderTexture RenderTexture
        {
            get
            {
                if (_renderTexture == null)
                {
                    RefreshRenderTexture();
                }
                return _renderTexture;
            }
            set { _renderTexture = value; }
        }
        /// 
        /// Sources.
        /// 
        private List Sources { get; set; }
        /// 
        /// True if instance is revived.
        /// 
        private bool IsRevived
        {
            get { return TilePool != null; }
        }
        /// 
        /// True if instance contains any sources.
        /// 
        private bool ContainsSources
        {
            get { return Sources != null && Sources.Count > 0; }
        }
        #region Interface For ICubismMaskSources
        /// 
        /// Add source of masks for drawing.
        /// 
        public void AddSource(ICubismMaskTextureCommandSource source)
        {
            // Make sure instance is valid.
            TryRevive();
            // Initialize container if necessary.
            if (Sources == null)
            {
                Sources = new List();
            }
            // Return early if source already exists.
            else if (Sources.FindIndex(i => i.Source == source) != -1)
            {
                return;
            }
            // Register source.
            var item = new SourcesItem
            {
                Source = source,
                Tiles = TilePool.AcquireTiles(source.GetNecessaryTileCount())
            };
            Sources.Add(item);
            // Apply tiles to source.
            source.SetTiles(item.Tiles);
        }
        /// 
        /// Remove source of masks
        /// 
        public void RemoveSource(ICubismMaskTextureCommandSource source)
        {
            // Return early if empty.
            if (!ContainsSources)
            {
                return;
            }
            var itemIndex = Sources.FindIndex(i => i.Source == source);
            // Return if source is invalid.
            if (itemIndex == -1)
            {
                return;
            }
            // Return tiles and deregister source.
            TilePool.ReturnTiles(Sources[itemIndex].Tiles);
            Sources.RemoveAt(itemIndex);
        }
        #endregion
        private void TryRevive()
        {
            // Return early if already revived.
            if (IsRevived)
            {
                return;
            }
            RefreshRenderTexture();
        }
        private void ReinitializeSources()
        {
            // Reallocate tiles if sources exist.
            if (ContainsSources)
            {
                for (var i = 0; i < Sources.Count; ++i)
                {
                    var source = Sources[i];
                    source.Tiles = TilePool.AcquireTiles(source.Source.GetNecessaryTileCount());
                    source.Source.SetTiles(source.Tiles);
                    Sources[i] = source;
                }
            }
        }
        private void RefreshRenderTexture()
        {
            // Recreate render texture.
            RenderTexture = new RenderTexture(Size, Size, 0, RenderTextureFormat.ARGB32);
            // Recreate allocator.
            TilePool = new CubismMaskTilePool(Subdivisions, Channels);
            // Reinitialize sources.
            ReinitializeSources();
        }
        #region Unity Event Handling
        /// 
        /// Initializes instance.
        /// 
        // ReSharper disable once UnusedMember.Local
        private void OnEnable()
        {
            CubismMaskCommandBuffer.AddSource(this);
        }
        /// 
        /// Finalizes instance.
        /// 
        // ReSharper disable once UnusedMember.Local
        private void OnDestroy()
        {
            CubismMaskCommandBuffer.RemoveSource(this);
        }
        #endregion
        #region ICubismMaskCommandSource
        /// 
        /// Called to enqueue source.
        /// 
        /// Buffer to enqueue in.
        void ICubismMaskCommandSource.AddToCommandBuffer(CommandBuffer buffer)
        {
            // Return early if empty.
            if (!ContainsSources)
            {
                return;
            }
            // Enqueue render target.
            buffer.SetRenderTarget(RenderTexture);
            buffer.ClearRenderTarget(false, true, Color.clear);
            // Enqueue sources.
            for (var i = 0; i < Sources.Count; ++i)
            {
                Sources[i].Source.AddToCommandBuffer(buffer);
            }
        }
        #endregion
        #region Source Item
        /// 
        /// Source of masks and its tiles
        /// 
        private struct SourcesItem
        {
            /// 
            /// SourcesItem instance.
            /// 
            public ICubismMaskTextureCommandSource Source;
            /// 
            /// Tiles assigned to the instance.
            /// 
            public CubismMaskTile[] Tiles;
        }
        #endregion
    }
}