using UnityEngine;
using UnityEngine.PostProcessing;

namespace UnityEditor.PostProcessing
{
    using Settings = BloomModel.Settings;

    [PostProcessingModelEditor(typeof(BloomModel))]
    public class BloomModelEditor : PostProcessingModelEditor
    {
        struct BloomSettings
        {
            public SerializedProperty intensity;
            public SerializedProperty threshold;
            public SerializedProperty softKnee;
            public SerializedProperty radius;
            public SerializedProperty antiFlicker;
        }

        struct LensDirtSettings
        {
            public SerializedProperty texture;
            public SerializedProperty intensity;
        }

        BloomSettings m_Bloom;
        LensDirtSettings m_LensDirt;

        public override void OnEnable()
        {
            m_Bloom = new BloomSettings
            {
                intensity = FindSetting((Settings x) => x.bloom.intensity),
                threshold = FindSetting((Settings x) => x.bloom.threshold),
                softKnee = FindSetting((Settings x) => x.bloom.softKnee),
                radius = FindSetting((Settings x) => x.bloom.radius),
                antiFlicker = FindSetting((Settings x) => x.bloom.antiFlicker)
            };

            m_LensDirt = new LensDirtSettings
            {
                texture = FindSetting((Settings x) => x.lensDirt.texture),
                intensity = FindSetting((Settings x) => x.lensDirt.intensity)
            };
        }

        public override void OnInspectorGUI()
        {
            EditorGUILayout.Space();
            PrepareGraph();
            DrawGraph();
            EditorGUILayout.Space();

            EditorGUILayout.PropertyField(m_Bloom.intensity);
            EditorGUILayout.PropertyField(m_Bloom.threshold, EditorGUIHelper.GetContent("Threshold (Gamma)"));
            EditorGUILayout.PropertyField(m_Bloom.softKnee);
            EditorGUILayout.PropertyField(m_Bloom.radius);
            EditorGUILayout.PropertyField(m_Bloom.antiFlicker);

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Dirt", EditorStyles.boldLabel);
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(m_LensDirt.texture);
            EditorGUILayout.PropertyField(m_LensDirt.intensity);
            EditorGUI.indentLevel--;
        }

        #region Graph

        float m_GraphThreshold;
        float m_GraphKnee;
        float m_GraphIntensity;

        // Number of vertices in curve
        const int k_CurveResolution = 48;

        // Vertex buffers
        Vector3[] m_RectVertices = new Vector3[4];
        Vector3[] m_LineVertices = new Vector3[2];
        Vector3[] m_CurveVertices = new Vector3[k_CurveResolution];

        Rect m_RectGraph;
        float m_RangeX;
        float m_RangeY;

        float ResponseFunction(float x)
        {
            var rq = Mathf.Clamp(x - m_GraphThreshold + m_GraphKnee, 0, m_GraphKnee * 2);
            rq = rq * rq * 0.25f / m_GraphKnee;
            return Mathf.Max(rq, x - m_GraphThreshold) * m_GraphIntensity;
        }

        // Transform a point into the graph rect
        Vector3 PointInRect(float x, float y)
        {
            x = Mathf.Lerp(m_RectGraph.x, m_RectGraph.xMax, x / m_RangeX);
            y = Mathf.Lerp(m_RectGraph.yMax, m_RectGraph.y, y / m_RangeY);
            return new Vector3(x, y, 0);
        }

        // Draw a line in the graph rect
        void DrawLine(float x1, float y1, float x2, float y2, float grayscale)
        {
            m_LineVertices[0] = PointInRect(x1, y1);
            m_LineVertices[1] = PointInRect(x2, y2);
            Handles.color = Color.white * grayscale;
            Handles.DrawAAPolyLine(2.0f, m_LineVertices);
        }

        // Draw a rect in the graph rect
        void DrawRect(float x1, float y1, float x2, float y2, float fill, float line)
        {
            m_RectVertices[0] = PointInRect(x1, y1);
            m_RectVertices[1] = PointInRect(x2, y1);
            m_RectVertices[2] = PointInRect(x2, y2);
            m_RectVertices[3] = PointInRect(x1, y2);

            Handles.DrawSolidRectangleWithOutline(
                m_RectVertices,
                fill < 0 ? Color.clear : Color.white * fill,
                line < 0 ? Color.clear : Color.white * line
                );
        }

        // Update internal state with a given bloom instance
        public void PrepareGraph()
        {
            var bloom = (BloomModel)target;
            m_RangeX = 5f;
            m_RangeY = 2f;

            m_GraphThreshold = bloom.settings.bloom.thresholdLinear;
            m_GraphKnee = bloom.settings.bloom.softKnee * m_GraphThreshold + 1e-5f;

            // Intensity is capped to prevent sampling errors
            m_GraphIntensity = Mathf.Min(bloom.settings.bloom.intensity, 10f);
        }

        // Draw the graph at the current position
        public void DrawGraph()
        {
            using (new GUILayout.HorizontalScope())
            {
                GUILayout.Space(EditorGUI.indentLevel * 15f);
                m_RectGraph = GUILayoutUtility.GetRect(128, 80);
            }

            // Background
            DrawRect(0, 0, m_RangeX, m_RangeY, 0.1f, 0.4f);

            // Soft-knee range
            DrawRect(m_GraphThreshold - m_GraphKnee, 0, m_GraphThreshold + m_GraphKnee, m_RangeY, 0.25f, -1);

            // Horizontal lines
            for (var i = 1; i < m_RangeY; i++)
                DrawLine(0, i, m_RangeX, i, 0.4f);

            // Vertical lines
            for (var i = 1; i < m_RangeX; i++)
                DrawLine(i, 0, i, m_RangeY, 0.4f);

            // Label
            Handles.Label(
                PointInRect(0, m_RangeY) + Vector3.right,
                "Brightness Response (linear)", EditorStyles.miniLabel
                );

            // Threshold line
            DrawLine(m_GraphThreshold, 0, m_GraphThreshold, m_RangeY, 0.6f);

            // Response curve
            var vcount = 0;
            while (vcount < k_CurveResolution)
            {
                var x = m_RangeX * vcount / (k_CurveResolution - 1);
                var y = ResponseFunction(x);
                if (y < m_RangeY)
                {
                    m_CurveVertices[vcount++] = PointInRect(x, y);
                }
                else
                {
                    if (vcount > 1)
                    {
                        // Extend the last segment to the top edge of the rect.
                        var v1 = m_CurveVertices[vcount - 2];
                        var v2 = m_CurveVertices[vcount - 1];
                        var clip = (m_RectGraph.y - v1.y) / (v2.y - v1.y);
                        m_CurveVertices[vcount - 1] = v1 + (v2 - v1) * clip;
                    }
                    break;
                }
            }

            if (vcount > 1)
            {
                Handles.color = Color.white * 0.9f;
                Handles.DrawAAPolyLine(2.0f, vcount, m_CurveVertices);
            }
        }

        #endregion
    }
}
