using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
namespace UnityEditor.Rendering
{
    /// 
    /// Formats the provided descriptor into a temperature unit slider with contextual slider markers, tooltips, and icons.
    /// 
    class TemperatureSlider : LightUnitSlider
    {
        private Vector3 m_ExponentialConstraints;
        private LightEditor.Settings m_Settings;
        private static Texture2D s_KelvinGradientTexture;
        /// 
        /// Exponential slider modeled to set a f(0.5) value.
        /// ref: https://stackoverflow.com/a/17102320
        /// 
        void PrepareExponentialConstraints(float lo, float mi, float hi)
        {
            // float x = lo;
            // float y = mi;
            // float z = hi;
            //
            // // https://www.desmos.com/calculator/yx2yf4huia
            // m_ExponentialConstraints.x = ((x * z) - (y * y)) / (x - (2 * y) + z);
            // m_ExponentialConstraints.y = ((y - x) * (y - x)) / (x - (2 * y) + z);
            // m_ExponentialConstraints.z = 2 * Mathf.Log((z - y) / (y - x));
            // Warning: These are the coefficients for a system of equation fit for a continuous, monotonic curve that fits a f(0.44) value.
            // f(0.44) is required instead of f(0.5) due to the location of the white in the temperature gradient texture.
            // The equation is solved to get the coefficient for the following constraint for low, mid, hi:
            // f(0)    = 1500
            // f(0.44) = 6500
            // f(1.0)  = 20000
            // If for any reason the constraints are changed, then the function must be refit and the new coefficients found.
            // Note that we can't re-use the original PowerSlider instead due to how it forces a text field, which we don't want in this case.
            m_ExponentialConstraints.x = -3935.53965427f;
            m_ExponentialConstraints.y = 5435.53965427f;
            m_ExponentialConstraints.z = 1.48240556f;
        }
        protected float ValueToSlider(float x) => Mathf.Log((x - m_ExponentialConstraints.x) / m_ExponentialConstraints.y) / m_ExponentialConstraints.z;
        protected float SliderToValue(float x) => m_ExponentialConstraints.x + m_ExponentialConstraints.y * Mathf.Exp(m_ExponentialConstraints.z * x);
        protected override float GetPositionOnSlider(float value, Vector2 valueRange)
        {
            return ValueToSlider(value);
        }
        static Texture2D GetKelvinGradientTexture(LightEditor.Settings settings)
        {
            if (s_KelvinGradientTexture == null)
            {
                var kelvinTexture = (Texture2D)typeof(LightEditor.Settings).GetField("m_KelvinGradientTexture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(settings);
                // This seems to be the only way to gamma-correct the internal gradient tex (aside from drawing it manually).
                var kelvinTextureLinear = new Texture2D(kelvinTexture.width, kelvinTexture.height, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.MipChain);
                kelvinTextureLinear.SetPixels(kelvinTexture.GetPixels());
                kelvinTextureLinear.Apply();
                s_KelvinGradientTexture = kelvinTextureLinear;
            }
            return s_KelvinGradientTexture;
        }
        /// 
        /// Constructs the temperature slider
        /// 
        /// The descriptor
        public TemperatureSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor)
        {
            var halfValue = 6500;
            PrepareExponentialConstraints(m_Descriptor.sliderRange.x, halfValue, m_Descriptor.sliderRange.y);
        }
        /// 
        /// Setups the light editor
        /// 
        /// The light editor from the light
        public void Setup(LightEditor.Settings settings)
        {
            m_Settings = settings;
        }
        /// 
        /// The serialized property for color temperature is stored in the build-in light editor, and we need to use this object to apply the update.
        /// 
        /// The value to update
        /// The preset range
        protected override void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset)
        {
            m_Settings.Update();
            base.SetValueToPreset(value, preset);
            m_Settings.ApplyModifiedProperties();
        }
        /// 
        /// Draws the slider
        /// 
        /// The  to draw the slider.
        /// The current value, and also returns the modified value.
        /// The ranges of the slider.
        protected override void DoSlider(Rect rect, ref float value, Vector2 sliderRange)
        {
            SliderWithTextureNoTextField(rect, ref value, sliderRange, m_Settings);
        }
        // Note: We could use the internal SliderWithTexture, however: the internal slider func forces a text-field (and no ability to opt-out of it).
        void SliderWithTextureNoTextField(Rect rect, ref float value, Vector2 range, LightEditor.Settings settings)
        {
            GUI.DrawTexture(rect, GetKelvinGradientTexture(settings));
            EditorGUI.BeginChangeCheck();
            // Draw the exponential slider that fits 6500K to the white point on the gradient texture.
            var internalValue = GUI.HorizontalSlider(rect, ValueToSlider(value), 0f, 1f, SliderStyles.k_TemperatureBorder, SliderStyles.k_TemperatureThumb);
            // Round to nearest since so much precision is not necessary for kelvin while sliding.
            if (EditorGUI.EndChangeCheck())
            {
                // Map the value back into kelvin.
                value = SliderToValue(internalValue);
                value = Mathf.Round(value);
            }
        }
    }
    /// 
    /// Helper to draw a temperature slider on the inspector
    /// 
    public class TemperatureSliderUIDrawer
    {
        static TemperatureSlider k_TemperatureSlider;
        static TemperatureSliderUIDrawer()
        {
            // Kelvin is not classified internally as a light unit so we handle it independently as well.
            k_TemperatureSlider = new TemperatureSlider(LightUnitSliderDescriptors.TemperatureDescriptor);
        }
        /// 
        /// Draws a temperature slider
        /// 
        /// The light settings
        /// The serialized object
        /// The serialized property
        /// The rect where the slider will be drawn
        public static void Draw(LightEditor.Settings settings, SerializedObject serializedObject, SerializedProperty value, Rect rect)
        {
            k_TemperatureSlider.SetSerializedObject(serializedObject);
            using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel))
            {
                k_TemperatureSlider.Setup(settings);
                float val = value.floatValue;
                k_TemperatureSlider.Draw(rect, value, ref val);
                if (val != value.floatValue)
                    value.floatValue = val;
            }
        }
        /// 
        /// Clamp to the authorized range of the temperature slider
        /// 
        /// The serialized property
        public static void ClampValue(SerializedProperty value)
        {
            value.floatValue = k_TemperatureSlider.ClampValue(value.floatValue);
        }
    }
}