using System;
using System.Collections.Generic;
using UnityEngine;

namespace Coffee.UIExtensions
{
    public interface IParameterTexture
    {
        int parameterIndex { get; set; }

        ParameterTexture ptex { get; }
    }

    /// <summary>
    /// Parameter texture.
    /// </summary>
    [System.Serializable]
    public class ParameterTexture
    {

        //################################
        // Public Members.
        //################################

        /// <summary>
        /// Initializes a new instance of the <see cref="Coffee.UIExtensions.ParameterTexture"/> class.
        /// </summary>
        /// <param name="channels">Channels.</param>
        /// <param name="instanceLimit">Instance limit.</param>
        /// <param name="propertyName">Property name.</param>
        public ParameterTexture(int channels, int instanceLimit, string propertyName)
        {
            _propertyName = propertyName;
            _channels = ((channels - 1) / 4 + 1) * 4;
            _instanceLimit = ((instanceLimit - 1) / 2 + 1) * 2;
            _data = new byte[_channels * _instanceLimit];

            _stack = new Stack<int>(_instanceLimit);
            for (int i = 1; i < _instanceLimit + 1; i++)
            {
                _stack.Push(i);
            }
        }


        /// <summary>
        /// Register the specified target.
        /// </summary>
        /// <param name="target">Target.</param>
        public void Register(IParameterTexture target)
        {
            Initialize();
            if (target.parameterIndex <= 0 && 0 < _stack.Count)
            {
                target.parameterIndex = _stack.Pop();
                //				Debug.LogFormat("<color=green>@@@ Register {0} : {1}</color>", target, target.parameterIndex);
            }
        }

        /// <summary>
        /// Unregister the specified target.
        /// </summary>
        /// <param name="target">Target.</param>
        public void Unregister(IParameterTexture target)
        {
            if (0 < target.parameterIndex)
            {
                //				Debug.LogFormat("<color=red>@@@ Unregister {0} : {1}</color>", target, target.parameterIndex);
                _stack.Push(target.parameterIndex);
                target.parameterIndex = 0;
            }
        }

        /// <summary>
        /// Sets the data.
        /// </summary>
        /// <param name="target">Target.</param>
        /// <param name="channelId">Channel identifier.</param>
        /// <param name="value">Value.</param>
        public void SetData(IParameterTexture target, int channelId, byte value)
        {
            int index = (target.parameterIndex - 1) * _channels + channelId;
            if (0 < target.parameterIndex && _data[index] != value)
            {
                _data[index] = value;
                _needUpload = true;
            }
        }

        /// <summary>
        /// Sets the data.
        /// </summary>
        /// <param name="target">Target.</param>
        /// <param name="channelId">Channel identifier.</param>
        /// <param name="value">Value.</param>
        public void SetData(IParameterTexture target, int channelId, float value)
        {
            SetData(target, channelId, (byte)(Mathf.Clamp01(value) * 255));
        }

        /// <summary>
        /// Registers the material.
        /// </summary>
        /// <param name="mat">Mat.</param>
        public void RegisterMaterial(Material mat)
        {
            if (_propertyId == 0)
            {
                _propertyId = Shader.PropertyToID(_propertyName);
            }
            if (mat)
            {
                mat.SetTexture(_propertyId, _texture);
            }
        }

        /// <summary>
        /// Gets the index of the normalized.
        /// </summary>
        /// <returns>The normalized index.</returns>
        /// <param name="target">Target.</param>
        public float GetNormalizedIndex(IParameterTexture target)
        {
            return ((float)target.parameterIndex - 0.5f) / _instanceLimit;
        }


        //################################
        // Private Members.
        //################################

        Texture2D _texture;
        bool _needUpload;
        int _propertyId;
        readonly string _propertyName;
        readonly int _channels;
        readonly int _instanceLimit;
        readonly byte[] _data;
        readonly Stack<int> _stack;
        static List<Action> updates;

        /// <summary>
        /// Initialize this instance.
        /// </summary>
        void Initialize()
        {
#if UNITY_EDITOR
            if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
            {
                return;
            }
#endif
            if (updates == null)
            {
                updates = new List<Action>();
                Canvas.willRenderCanvases += () =>
                {
                    var count = updates.Count;
                    for (int i = 0; i < count; i++)
                    {
                        updates[i].Invoke();
                    }
                };
            }

            if (!_texture)
            {
                bool isLinear = QualitySettings.activeColorSpace == ColorSpace.Linear;
                _texture = new Texture2D(_channels / 4, _instanceLimit, TextureFormat.RGBA32, false, isLinear);
                _texture.filterMode = FilterMode.Point;
                _texture.wrapMode = TextureWrapMode.Clamp;

                updates.Add(UpdateParameterTexture);
                _needUpload = true;
            }
        }

        void UpdateParameterTexture()
        {
            if (_needUpload && _texture)
            {
                _needUpload = false;
                _texture.LoadRawTextureData(_data);
                _texture.Apply(false, false);
            }
        }
    }
}