using UnityEngine;

namespace UnityEditor.ShaderGraph
{
    internal class LabelWidthScope : GUI.Scope
    {
        float m_previewLabelWidth;
        internal LabelWidthScope(int labelPadding = 10, int labelWidth = 251)
        {
            m_previewLabelWidth = EditorGUIUtility.labelWidth;
            EditorGUIUtility.labelWidth = labelWidth;
            GUILayout.BeginHorizontal();
            GUILayout.Space(labelPadding);
            GUILayout.BeginVertical();
        }

        protected override void CloseScope()
        {
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
            EditorGUIUtility.labelWidth = m_previewLabelWidth;
        }
    }

    static class ShaderGraphPreferences
    {
        static class Keys
        {
            internal const string variantLimit = "UnityEditor.ShaderGraph.VariantLimit";
            internal const string autoAddRemoveBlocks = "UnityEditor.ShaderGraph.AutoAddRemoveBlocks";
            internal const string allowDeprecatedBehaviors = "UnityEditor.ShaderGraph.AllowDeprecatedBehaviors";
            internal const string zoomStepSize = "UnityEditor.ShaderGraph.ZoomStepSize";
            internal const string graphTemplateWorkflow = "UnityEditor.ShaderGraph.GraphTemplateWorkflow";
            internal const string openNewGraphOnCreation = "UnityEditor.ShaderGraph.OpenNewGraphOnCreation";
            internal const string newNodesPreview = "UnityEditor.ShaderGraph.NewNodesPreview";
        }

        // Note that GUIContent in this class is used to generate search keywords for the preferences window
        class Labels
        {
            public static GUIContent previewVariantLimit = new GUIContent("Preview Variant Limit");
            public static GUIContent autoAddRemoveBlocks = new GUIContent("Automatically Add and Remove Block Nodes");
            public static GUIContent allowDeprecatedBehaviors = new GUIContent("Enable Deprecated Nodes");
            public static GUIContent zoomStepSize = new GUIContent("Zoom Step Size", "Default is 0.5");
            public static GUIContent graphTemplateWorkflow = new GUIContent("Graph Template Workflow",
                "When creating a new Shadergraph asset from specific menu items, determine if a reference should use a"
                + " variant of the newly created material sub-asset or the shader itself.");
            public static GUIContent openNewGraphOnCreation = new GUIContent("Open new Shader Graphs automatically",
                "Choose whether new ShaderGraph assets should automatically open for editing.");
            public static GUIContent newNodesPreview = new GUIContent("Expand Node Preview on Node creation",
                "Choose whether newly added Nodes' Previews should be expanded.");
        }

        static bool m_Loaded = false;
        internal delegate void PreferenceChangedDelegate();

        internal static PreferenceChangedDelegate onVariantLimitChanged;
        static int m_PreviewVariantLimit = 2048;
        internal static int previewVariantLimit
        {
            get { return m_PreviewVariantLimit; }
            set
            {
                if (onVariantLimitChanged != null)
                    onVariantLimitChanged();
                TrySave(ref m_PreviewVariantLimit, value, Keys.variantLimit);
            }
        }

        static bool m_AutoAddRemoveBlocks = true;
        internal static bool autoAddRemoveBlocks
        {
            get => m_AutoAddRemoveBlocks;
            set => TrySave(ref m_AutoAddRemoveBlocks, value, Keys.autoAddRemoveBlocks);
        }

        internal static PreferenceChangedDelegate onAllowDeprecatedChanged;
        static bool m_AllowDeprecatedBehaviors = false;
        internal static bool allowDeprecatedBehaviors
        {
            get => m_AllowDeprecatedBehaviors;
            set
            {
                TrySave(ref m_AllowDeprecatedBehaviors, value, Keys.allowDeprecatedBehaviors);
                if (onAllowDeprecatedChanged != null)
                {
                    onAllowDeprecatedChanged();
                }
            }
        }

        internal static PreferenceChangedDelegate onZoomStepSizeChanged;
        const float defaultZoomStepSize = 0.5f;
        static float m_ZoomStepSize = defaultZoomStepSize;
        internal static float zoomStepSize
        {
            get => m_ZoomStepSize;
            set
            {
                TrySave(ref m_ZoomStepSize, value, Keys.zoomStepSize);
                if (onZoomStepSizeChanged != null)
                {
                    onZoomStepSizeChanged();
                }
            }
        }

        internal enum GraphTemplateWorkflow { MaterialVariant, Material }
        const GraphTemplateWorkflow defaultGraphTemplateWorkflow = GraphTemplateWorkflow.MaterialVariant;
        static GraphTemplateWorkflow m_GraphTemplateWorkflow = defaultGraphTemplateWorkflow;
        static GraphTemplateWorkflow graphTemplateWorkflow
        {
            get => m_GraphTemplateWorkflow;
            set => TrySave(ref m_GraphTemplateWorkflow, value, Keys.graphTemplateWorkflow);
        }

        internal static GraphTemplateWorkflow GetOrPromptGraphTemplateWorkflow()
        {
            if (!EditorPrefs.HasKey(Keys.graphTemplateWorkflow))
            {
                bool userPref = EditorUtility.DisplayDialog("Shader Graph Preferences", "Should the new Material be made a variant of the Shader Graph sub asset, or a Material using the Shader?\nThis can be changed later in Editor Preferences.", "Material Variant", "Material");
                TrySave(ref m_GraphTemplateWorkflow, userPref ? GraphTemplateWorkflow.MaterialVariant : GraphTemplateWorkflow.Material, Keys.graphTemplateWorkflow, true);
            }
            return m_GraphTemplateWorkflow;
        }

        static bool m_OpenNewGraphOnCreation = true;
        static bool openNewGraphOnCreation
        {
            get => m_OpenNewGraphOnCreation;
            set => TrySave(ref m_OpenNewGraphOnCreation, value, Keys.openNewGraphOnCreation);
        }

        static bool m_NewNodesPreview = true;
        internal static bool newNodesPreview
        {
            get => m_NewNodesPreview;
            private set => TrySave(ref m_NewNodesPreview, value, Keys.newNodesPreview);
        }

        internal static bool GetOrPromptOpenNewGraphOnCreation()
        {
            if (!EditorPrefs.HasKey(Keys.openNewGraphOnCreation))
            {
                bool userPref = EditorUtility.DisplayDialog("Shader Graph Preferences", "Should Shader Graph assets open upon creation?\nThis can be changed later in Editor Preferences.", "Yes", "No");
                TrySave(ref m_OpenNewGraphOnCreation, userPref, Keys.openNewGraphOnCreation, true);
            }
            return m_OpenNewGraphOnCreation;
        }

        static ShaderGraphPreferences()
        {
            Load();
        }

        [SettingsProvider]
        static SettingsProvider PreferenceGUI()
        {
            return new SettingsProvider("Preferences/Shader Graph", SettingsScope.User)
            {
                guiHandler = searchContext => OpenGUI(),
                keywords = SettingsProvider.GetSearchKeywordsFromGUIContentProperties<Labels>(),
            };
        }

        static void OpenGUI()
        {
            if (!m_Loaded)
                Load();

            using (var scope = new LabelWidthScope(10, 300))
            {
                var actualLimit = ShaderGraphProjectSettings.instance.overrideShaderVariantLimit
                    ? ShaderGraphProjectSettings.instance.shaderVariantLimit
                    : ShaderGraphProjectSettings.defaultVariantLimit;
                var willPreviewVariantBeIgnored = ShaderGraphPreferences.previewVariantLimit > actualLimit || ShaderGraphProjectSettings.instance.overrideShaderVariantLimit;

                var variantLimitLabel = willPreviewVariantBeIgnored
                    ? new GUIContent(Labels.previewVariantLimit.text, EditorGUIUtility.IconContent("console.infoicon").image, $"The Preview Variant Limit is higher than the Shader Variant Limit in Project Settings: {actualLimit}. The Preview Variant Limit will be ignored.")
                    : Labels.previewVariantLimit;

                EditorGUI.BeginChangeCheck();
                var variantLimitValue = EditorGUILayout.DelayedIntField(variantLimitLabel, previewVariantLimit);
                variantLimitValue = Mathf.Max(0, variantLimitValue);
                if (EditorGUI.EndChangeCheck())
                {
                    previewVariantLimit = variantLimitValue;
                }

                EditorGUI.BeginChangeCheck();
                var autoAddRemoveBlocksValue = EditorGUILayout.Toggle(Labels.autoAddRemoveBlocks, autoAddRemoveBlocks);
                if (EditorGUI.EndChangeCheck())
                {
                    autoAddRemoveBlocks = autoAddRemoveBlocksValue;
                }

                EditorGUI.BeginChangeCheck();
                var allowDeprecatedBehaviorsValue = EditorGUILayout.Toggle(Labels.allowDeprecatedBehaviors, allowDeprecatedBehaviors);
                if (EditorGUI.EndChangeCheck())
                {
                    allowDeprecatedBehaviors = allowDeprecatedBehaviorsValue;
                }

                EditorGUI.BeginChangeCheck();
                var zoomStepSizeValue = EditorGUILayout.Slider(Labels.zoomStepSize.text, zoomStepSize, 0.0f, 1f);
                if (EditorGUI.EndChangeCheck())
                {
                    zoomStepSize = zoomStepSizeValue;
                }

                EditorGUI.BeginChangeCheck();
                var graphTemplateWorkflowValue = EditorGUILayout.EnumPopup(Labels.graphTemplateWorkflow, graphTemplateWorkflow);
                if (EditorGUI.EndChangeCheck())
                {
                    graphTemplateWorkflow = (GraphTemplateWorkflow)graphTemplateWorkflowValue;
                }

                EditorGUI.BeginChangeCheck();
                var openNewGraphOnCreationValue = EditorGUILayout.Toggle(Labels.openNewGraphOnCreation, openNewGraphOnCreation);
                if (EditorGUI.EndChangeCheck())
                {
                    openNewGraphOnCreation = openNewGraphOnCreationValue;
                }

                EditorGUI.BeginChangeCheck();
                var newNodesPreviewValue = EditorGUILayout.Toggle(Labels.newNodesPreview, newNodesPreview);
                if (EditorGUI.EndChangeCheck())
                {
                    newNodesPreview = newNodesPreviewValue;
                }
            }
        }

        static void Load()
        {
            m_PreviewVariantLimit = EditorPrefs.GetInt(Keys.variantLimit, 128);
            m_AutoAddRemoveBlocks = EditorPrefs.GetBool(Keys.autoAddRemoveBlocks, true);
            m_AllowDeprecatedBehaviors = EditorPrefs.GetBool(Keys.allowDeprecatedBehaviors, false);
            m_ZoomStepSize = EditorPrefs.GetFloat(Keys.zoomStepSize, defaultZoomStepSize);
            m_GraphTemplateWorkflow = (GraphTemplateWorkflow)EditorPrefs.GetInt(Keys.graphTemplateWorkflow, (int)defaultGraphTemplateWorkflow);
            m_OpenNewGraphOnCreation = EditorPrefs.GetBool(Keys.openNewGraphOnCreation, true);
            m_NewNodesPreview = EditorPrefs.GetBool(Keys.newNodesPreview, true);
            m_Loaded = true;
        }

        static void TrySave<T>(ref T field, T newValue, string key, bool forceSave = false)
        {
            if (field.Equals(newValue) && !forceSave)
                return;

            if (typeof(T) == typeof(float))
                EditorPrefs.SetFloat(key, (float)(object)newValue);
            else if (typeof(T) == typeof(int))
                EditorPrefs.SetInt(key, (int)(object)newValue);
            else if (typeof(T) == typeof(bool))
                EditorPrefs.SetBool(key, (bool)(object)newValue);
            else if (typeof(T) == typeof(string))
                EditorPrefs.SetString(key, (string)(object)newValue);
            else if (typeof(T).IsEnum)
                EditorPrefs.SetInt(key, (int)(object)newValue);

            field = newValue;
        }
    }
}