using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine.Assertions;

namespace UnityEngine.Rendering
{
    public partial class DebugUI
    {
        /// <summary>
        /// Generic field - will be serialized in the editor if it's not read-only
        /// </summary>
        /// <typeparam name="T">The type of data managed by the field.</typeparam>
        public abstract class Field<T> : Widget, IValueField
        {
            /// <summary>
            /// Getter for this field.
            /// </summary>
            public Func<T> getter { get; set; }
            /// <summary>
            /// Setter for this field.
            /// </summary>
            public Action<T> setter { get; set; }

            // This should be an `event` but they don't play nice with object initializers in the
            // version of C# we use.
            /// <summary>
            /// Callback used when the value of the field changes.
            /// </summary>
            public Action<Field<T>, T> onValueChanged;

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            object IValueField.ValidateValue(object value)
            {
                return ValidateValue((T)value);
            }

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            public virtual T ValidateValue(T value)
            {
                return value;
            }

            /// <summary>
            /// Get the value of the field.
            /// </summary>
            /// <returns>Value of the field.</returns>
            object IValueField.GetValue()
            {
                return GetValue();
            }

            /// <summary>
            /// Get the value of the field.
            /// </summary>
            /// <returns>Value of the field.</returns>
            public T GetValue()
            {
                Assert.IsNotNull(getter);
                return getter();
            }

            /// <summary>
            /// Set the value of the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            public void SetValue(object value)
            {
                SetValue((T)value);
            }

            /// <summary>
            /// Set the value of the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            public virtual void SetValue(T value)
            {
                if (setter == null)
                    return;

                var v = ValidateValue(value);

                if (v == null || !v.Equals(getter()))
                {
                    setter(v);
                    onValueChanged?.Invoke(this, v);
                }
            }
        }

        /// <summary>
        /// Boolean field.
        /// </summary>
        public class BoolField : Field<bool> { }
        /// <summary>
        /// An array of checkboxes that Unity displays in a horizontal row.
        /// </summary>
        public class HistoryBoolField : BoolField
        {
            /// <summary>
            /// History getter for this field.
            /// </summary>
            public Func<bool>[] historyGetter { get; set; }
            /// <summary>
            /// Depth of the field's history.
            /// </summary>
            public int historyDepth => historyGetter?.Length ?? 0;
            /// <summary>
            /// Get the value of the field at a certain history index.
            /// </summary>
            /// <param name="historyIndex">Index of the history to query.</param>
            /// <returns>Value of the field at the provided history index.</returns>
            public bool GetHistoryValue(int historyIndex)
            {
                Assert.IsNotNull(historyGetter);
                Assert.IsTrue(historyIndex >= 0 && historyIndex < historyGetter.Length, "out of range historyIndex");
                Assert.IsNotNull(historyGetter[historyIndex]);
                return historyGetter[historyIndex]();
            }
        }

        /// <summary>
        /// A slider for an integer.
        /// </summary>
        public class IntField : Field<int>
        {
            /// <summary>
            /// Minimum value function.
            /// </summary>
            public Func<int> min;
            /// <summary>
            /// Maximum value function.
            /// </summary>
            public Func<int> max;

            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public int incStep = 1;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public int intStepMult = 10;

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            public override int ValidateValue(int value)
            {
                if (min != null) value = Mathf.Max(value, min());
                if (max != null) value = Mathf.Min(value, max());
                return value;
            }
        }

        /// <summary>
        /// A slider for a positive integer.
        /// </summary>
        public class UIntField : Field<uint>
        {
            /// <summary>
            /// Minimum value function.
            /// </summary>
            public Func<uint> min;
            /// <summary>
            /// Maximum value function.
            /// </summary>
            public Func<uint> max;

            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public uint incStep = 1u;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public uint intStepMult = 10u;

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            public override uint ValidateValue(uint value)
            {
                if (min != null) value = (uint)Mathf.Max((int)value, (int)min());
                if (max != null) value = (uint)Mathf.Min((int)value, (int)max());
                return value;
            }
        }

        /// <summary>
        /// A slider for a float.
        /// </summary>
        public class FloatField : Field<float>
        {
            /// <summary>
            /// Minimum value function.
            /// </summary>
            public Func<float> min;
            /// <summary>
            /// Maximum value function.
            /// </summary>
            public Func<float> max;

            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public float incStep = 0.1f;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public float incStepMult = 10f;
            /// <summary>
            /// Number of decimals.
            /// </summary>
            public int decimals = 3;

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            public override float ValidateValue(float value)
            {
                if (min != null) value = Mathf.Max(value, min());
                if (max != null) value = Mathf.Min(value, max());
                return value;
            }
        }

        /// <summary>
        /// Field that displays <see cref="RenderingLayerMask"/>
        /// </summary>
        public class RenderingLayerField : Field<RenderingLayerMask>, IContainer
        {
            static readonly NameAndTooltip s_RenderingLayerColors = new()
            {
                name = "Layers Color",
                tooltip = "Select the display color for each Rendering Layer"
            };

            private string[] m_RenderingLayersNames = Array.Empty<string>();

            private int m_DefinedRenderingLayersCount = -1;

            private int maxRenderingLayerCount
            {
                get
                {
#if UNITY_EDITOR
                    if (UnityEditor.Rendering.EditorGraphicsSettings.
                        TryGetFirstRenderPipelineSettingsFromInterface<UnityEditor.Rendering.RenderingLayersLimitSettings>(out var settings))
                        return Mathf.Min(settings.maxSupportedRenderingLayers, RenderingLayerMask.GetRenderingLayerCount());
#endif

                    return RenderingLayerMask.GetRenderingLayerCount();
                }
            }

            private void Resize()
            {
                m_DefinedRenderingLayersCount = RenderingLayerMask.GetDefinedRenderingLayerCount();

                // Fill layer names
                m_RenderingLayersNames = new string[maxRenderingLayerCount];
                for (int i = 0; i < maxRenderingLayerCount; i++)
                {
                    var definedLayerName = RenderingLayerMask.RenderingLayerToName(i);
                    if (string.IsNullOrEmpty(definedLayerName))
                        definedLayerName = $"Unused Rendering Layer {i}";
                    m_RenderingLayersNames[i] = definedLayerName;
                }

                // Foldout + Color for each layer
                m_RenderingLayersColors.Clear();
                var layersColor = new DebugUI.Foldout()
                {
                    nameAndTooltip = s_RenderingLayerColors,
                    flags = Flags.EditorOnly,
                    parent = this,
                };
                m_RenderingLayersColors.Add(layersColor);

                for (int i = 0; i < m_RenderingLayersNames.Length; i++)
                {
                    var index = i; // capture the variable for the color field index
                    layersColor.children.Add(new DebugUI.ColorField
                    {
                        displayName = m_RenderingLayersNames[index],
                        getter = () =>
                        {
                            Assert.IsNotNull(getRenderingLayerColor, "Please specify a method for getting the rendering layer color");
                            return getRenderingLayerColor(index);
                        },
                        setter = value =>
                        {
                            Assert.IsNotNull(setRenderingLayerColor, "Please specify a method for setting the rendering layer color");
                            setRenderingLayerColor(value, index);
                        }
                    });
                }

                GenerateQueryPath();
            }

            /// <summary>
            /// Obtains the list of the available rendering layer names
            /// </summary>
            public string[] renderingLayersNames
            {
                get
                {
                    if (m_DefinedRenderingLayersCount != RenderingLayerMask.GetDefinedRenderingLayerCount())
                    {
                        Resize();
                    }

                    return m_RenderingLayersNames;
                }
            }

            private ObservableList<Widget> m_RenderingLayersColors = new ObservableList<Widget>();

            /// <summary>
            /// Gets the list of widgets representing the rendering layer colors.
            /// </summary>
            public ObservableList<Widget> children
            {
                get
                {
                    if (m_DefinedRenderingLayersCount != RenderingLayerMask.GetDefinedRenderingLayerCount())
                    {
                        Resize();
                    }

                    return m_RenderingLayersColors;
                }
            }

            /// <summary>
            /// Obtains the color in a given index
            /// </summary>
            public Func<int, Vector4> getRenderingLayerColor { get; set; }
            /// <summary>
            /// Sets the color for a given index
            /// </summary>
            public Action<Vector4, int> setRenderingLayerColor { get; set; }

            internal override void GenerateQueryPath()
            {
                base.GenerateQueryPath();

                int numChildren = children.Count;
                for (int i = 0; i < numChildren; i++)
                    children[i].GenerateQueryPath();
            }
        }

        /// <summary>
        /// Generic <see cref="EnumField"/> that stores enumNames and enumValues
        /// </summary>
        /// <typeparam name="T">The inner type of the field</typeparam>
        public abstract class EnumField<T> : Field<T>
        {
            /// <summary>
            /// List of names of the enumerator entries.
            /// </summary>
            public GUIContent[] enumNames;

            private int[] m_EnumValues;

            /// <summary>
            /// List of values of the enumerator entries.
            /// </summary>
            public int[] enumValues
            {
                get => m_EnumValues;
                set
                {
                    if (value?.Distinct().Count() != value?.Count())
                        Debug.LogWarning($"{displayName} - The values of the enum are duplicated, this might lead to a errors displaying the enum");
                    m_EnumValues = value;
                }
            }


            // Space-delimit PascalCase (https://stackoverflow.com/questions/155303/net-how-can-you-split-a-caps-delimited-string-into-an-array)
            static Regex s_NicifyRegEx = new("([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", RegexOptions.Compiled);

            /// <summary>
            /// Automatically fills the enum names with a given <see cref="Type"/>
            /// </summary>
            /// <param name="enumType">The enum type</param>
            protected void AutoFillFromType(Type enumType)
            {
                if (enumType == null || !enumType.IsEnum)
                    throw new ArgumentException($"{nameof(enumType)} must not be null and it must be an Enum type");

                using (ListPool<GUIContent>.Get(out var tmpNames))
                using (ListPool<int>.Get(out var tmpValues))
                {
                    var enumEntries = enumType.GetFields(BindingFlags.Public | BindingFlags.Static)
                        .Where(fieldInfo => !fieldInfo.IsDefined(typeof(ObsoleteAttribute)) && !fieldInfo.IsDefined(typeof(HideInInspector)));
                    foreach (var fieldInfo in enumEntries)
                    {
                        var description = fieldInfo.GetCustomAttribute<InspectorNameAttribute>();
                        var displayName = new GUIContent(description == null ? s_NicifyRegEx.Replace(fieldInfo.Name, "$1 ") : description.displayName);
                        tmpNames.Add(displayName);
                        tmpValues.Add((int)Enum.Parse(enumType, fieldInfo.Name));
                    }
                    enumNames = tmpNames.ToArray();
                    enumValues = tmpValues.ToArray();
                }
            }
        }

        /// <summary>
        /// A dropdown that contains the values from an enum.
        /// </summary>
        public class EnumField : EnumField<int>
        {
            internal int[] quickSeparators;

            private int[] m_Indexes;
            internal int[] indexes => m_Indexes ??= Enumerable.Range(0, enumNames?.Length ?? 0).ToArray();

            /// <summary>
            /// Get the enumeration value index.
            /// </summary>
            public Func<int> getIndex { get; set; }
            /// <summary>
            /// Set the enumeration value index.
            /// </summary>
            public Action<int> setIndex { get; set; }

            /// <summary>
            /// Current enumeration value index.
            /// </summary>
            public int currentIndex
            {
                get => getIndex();
                set => setIndex(value);
            }

            /// <summary>
            /// Generates enumerator values and names automatically based on the provided type.
            /// </summary>
            public Type autoEnum
            {
                set
                {
                    AutoFillFromType(value);
                    InitQuickSeparators();
                }
            }

            internal void InitQuickSeparators()
            {
                var enumNamesPrefix = enumNames.Select(x =>
                {
                    string[] splitted = x.text.Split('/');
                    if (splitted.Length == 1)
                        return "";
                    else
                        return splitted[0];
                });
                quickSeparators = new int[enumNamesPrefix.Distinct().Count()];
                string lastPrefix = null;
                for (int i = 0, wholeNameIndex = 0; i < quickSeparators.Length; ++i)
                {
                    var currentTestedPrefix = enumNamesPrefix.ElementAt(wholeNameIndex);
                    while (lastPrefix == currentTestedPrefix)
                    {
                        currentTestedPrefix = enumNamesPrefix.ElementAt(++wholeNameIndex);
                    }
                    lastPrefix = currentTestedPrefix;
                    quickSeparators[i] = wholeNameIndex++;
                }
            }

            /// <summary>
            /// Set the value of the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            public override void SetValue(int value)
            {
                Assert.IsNotNull(setter);
                var validValue = ValidateValue(value);

                // There might be cases that the value does not map the index, look for the correct index
                var newCurrentIndex = Array.IndexOf(enumValues, validValue);

                if (currentIndex != newCurrentIndex && !validValue.Equals(getter()))
                {
                    setter(validValue);
                    onValueChanged?.Invoke(this, validValue);

                    if (newCurrentIndex > -1)
                        currentIndex = newCurrentIndex;
                }
            }
        }

        /// <summary>
        /// A dropdown that contains a list of Unity objects.
        /// </summary>
        public class ObjectPopupField : Field<Object>
        {
            /// <summary>
            /// Callback to obtain the elemtents of the pop up
            /// </summary>
            public Func<IEnumerable<Object>> getObjects { get; set; }
        }

        /// <summary>
        /// A dropdown that contains a list of cameras
        /// </summary>
        public class CameraSelector : ObjectPopupField
        {
            /// <summary>
            /// A dropdown that contains a list of cameras
            /// </summary>
            public CameraSelector()
            {
                displayName = "Camera";
                getObjects = () => cameras;
            }

            private Camera[] m_CamerasArray;
            private List<Camera> m_Cameras = new List<Camera>();

            IEnumerable<Camera> cameras
            {
                get
                {
                    m_Cameras.Clear();

#if UNITY_EDITOR
                    if (UnityEditor.SceneView.lastActiveSceneView != null)
                    {
                        var sceneCamera = UnityEditor.SceneView.lastActiveSceneView.camera;
                        if (sceneCamera != null)
                            m_Cameras.Add(sceneCamera);
                    }
#endif

                    if (m_CamerasArray == null || m_CamerasArray.Length != Camera.allCamerasCount)
                    {
                        m_CamerasArray = new Camera[Camera.allCamerasCount];
                    }

                    Camera.GetAllCameras(m_CamerasArray);

                    foreach (var camera in m_CamerasArray)
                    {
                        if (camera == null)
                            continue;

                        if (camera.cameraType != CameraType.Preview && camera.cameraType != CameraType.Reflection)
                        {
                            if (camera.TryGetComponent<IAdditionalData>(out _))
                                m_Cameras.Add(camera);
                        }
                    }

                    return m_Cameras;
                }
            }
        }

        /// <summary>
        /// Enumerator field with history.
        /// </summary>
        public class HistoryEnumField : EnumField
        {
            /// <summary>
            /// History getter for this field.
            /// </summary>
            public Func<int>[] historyIndexGetter { get; set; }
            /// <summary>
            /// Depth of the field's history.
            /// </summary>
            public int historyDepth => historyIndexGetter?.Length ?? 0;
            /// <summary>
            /// Get the value of the field at a certain history index.
            /// </summary>
            /// <param name="historyIndex">Index of the history to query.</param>
            /// <returns>Value of the field at the provided history index.</returns>
            public int GetHistoryValue(int historyIndex)
            {
                Assert.IsNotNull(historyIndexGetter);
                Assert.IsTrue(historyIndex >= 0 && historyIndex < historyIndexGetter.Length, "out of range historyIndex");
                Assert.IsNotNull(historyIndexGetter[historyIndex]);
                return historyIndexGetter[historyIndex]();
            }
        }

        /// <summary>
        /// Bitfield enumeration field.
        /// </summary>
        public class BitField : EnumField<Enum>
        {
            Type m_EnumType;

            /// <summary>
            /// Generates bitfield values and names automatically based on the provided type.
            /// </summary>
            public Type enumType
            {
                get => m_EnumType;
                set
                {
                    m_EnumType = value;
                    AutoFillFromType(value);
                }
            }
        }

        /// <summary>
        /// Color field.
        /// </summary>
        public class ColorField : Field<Color>
        {
            /// <summary>
            /// HDR color.
            /// </summary>
            public bool hdr = false;
            /// <summary>
            /// Show alpha of the color field.
            /// </summary>
            public bool showAlpha = true;

            // Editor-only
            /// <summary>
            /// Show the color picker.
            /// </summary>
            public bool showPicker = true;

            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public float incStep = 0.025f;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public float incStepMult = 5f;
            /// <summary>
            /// Number of decimals.
            /// </summary>
            public int decimals = 3;

            /// <summary>
            /// Function used to validate the value when updating the field.
            /// </summary>
            /// <param name="value">Input value.</param>
            /// <returns>Validated value.</returns>
            public override Color ValidateValue(Color value)
            {
                if (!hdr)
                {
                    value.r = Mathf.Clamp01(value.r);
                    value.g = Mathf.Clamp01(value.g);
                    value.b = Mathf.Clamp01(value.b);
                    value.a = Mathf.Clamp01(value.a);
                }

                return value;
            }
        }

        /// <summary>
        /// Vector2 field.
        /// </summary>
        public class Vector2Field : Field<Vector2>
        {
            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public float incStep = 0.025f;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public float incStepMult = 10f;
            /// <summary>
            /// Number of decimals.
            /// </summary>
            public int decimals = 3;
        }

        /// <summary>
        /// Vector3 field.
        /// </summary>
        public class Vector3Field : Field<Vector3>
        {
            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public float incStep = 0.025f;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public float incStepMult = 10f;
            /// <summary>
            /// Number of decimals.
            /// </summary>
            public int decimals = 3;
        }

        /// <summary>
        /// Vector4 field.
        /// </summary>
        public class Vector4Field : Field<Vector4>
        {
            // Runtime-only
            /// <summary>
            /// Step increment.
            /// </summary>
            public float incStep = 0.025f;
            /// <summary>
            /// Step increment multiplier.
            /// </summary>
            public float incStepMult = 10f;
            /// <summary>
            /// Number of decimals.
            /// </summary>
            public int decimals = 3;
        }

        /// <summary>
        /// A field for selecting a Unity object.
        /// </summary>
        public class ObjectField : Field<Object>
        {
            /// <summary>
            /// Object type.
            /// </summary>
            public Type type = typeof(Object);
        }

        /// <summary>
        /// A list of fields for selecting Unity objects.
        /// </summary>
        public class ObjectListField : Field<Object[]>
        {
            /// <summary>
            /// Objects type.
            /// </summary>
            public Type type = typeof(Object);
        }

        /// <summary>
        /// A read-only message box with an icon.
        /// </summary>
        public class MessageBox : Widget
        {
            /// <summary>
            /// Label style defines text color and background.
            /// </summary>
            public enum Style
            {
                /// <summary>
                /// Info category
                /// </summary>
                Info,
                /// <summary>
                /// Warning category
                /// </summary>
                Warning,
                /// <summary>
                /// Error category
                /// </summary>
                Error
            }

            /// <summary>
            /// Style used to render displayName.
            /// </summary>
            public Style style = Style.Info;

            /// <summary>
            /// Message Callback to feed the new message to the widget
            /// </summary>
            public Func<string> messageCallback = null;

            /// <summary>
            /// This obtains the message from the display name or from the message callback if it is not null
            /// </summary>
            public string message => messageCallback == null ? displayName : messageCallback();
        }

        /// <summary>
        /// Widget that will show into the Runtime UI only
        /// Warning the user if the Runtime Debug Shaders variants are being stripped from the build.
        /// </summary>
        public class RuntimeDebugShadersMessageBox : MessageBox
        {
            /// <summary>
            /// Constructs a <see cref="RuntimeDebugShadersMessageBox"/>
            /// </summary>
            public RuntimeDebugShadersMessageBox()
            {
                displayName =
                    "Warning: the debug shader variants are missing. Ensure that the \"Strip Runtime Debug Shaders\" option is disabled in the SRP Graphics Settings.";
                style = DebugUI.MessageBox.Style.Warning;
                isHiddenCallback = () =>
                {
#if !UNITY_EDITOR
                    if (GraphicsSettings.TryGetRenderPipelineSettings<ShaderStrippingSetting>(out var shaderStrippingSetting))
                        return !shaderStrippingSetting.stripRuntimeDebugShaders;
#endif
                    return true;
                };
            }
        }
    }
}