using System;
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using UnityEditor.ShaderGraph;
using UnityEditor.ShortcutManagement;
using UnityEditorInternal;
using UnityEditor.EditorTools;
using UnityEditor.Rendering.Utilities;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.RenderPipelines.Core;
using UnityEngine.Rendering.Universal;
using static UnityEditorInternal.EditMode;

namespace UnityEditor.Rendering.Universal
{
    [CustomEditor(typeof(DecalProjector))]
    [CanEditMultipleObjects]
    partial class DecalProjectorEditor : Editor
    {
        const float k_Limit = 100000f;
        const float k_LimitInv = 1f / k_Limit;

        static readonly GUIContent k_NewDecalMaterialButtonText = EditorGUIUtility.TrTextContent("New", "Creates a new Decal material.");
        static readonly string k_NewDecalText = "URP Decal";
        static readonly string k_NewSGDecalText = "ShaderGraph Decal";
        static readonly string k_DefaultDecalShaderGraphTemplatePath = "Packages/com.unity.render-pipelines.universal/Shaders/Decal.shadergraph";

        static Color fullColor
        {
            get
            {
                Color c = s_LastColor;
                c.a = 1f;
                return c;
            }
        }
        static Color s_LastColor;
        static void UpdateColorsInHandlesIfRequired()
        {
            Color c = DecalPreferences.decalGizmoColor;
            if (c != s_LastColor)
            {
                if (s_BoxHandle != null && !s_BoxHandle.Equals(null))
                    s_BoxHandle = null;

                if (s_uvHandles != null && !s_uvHandles.Equals(null))
                    s_uvHandles.baseColor = c;

                s_LastColor = c;
            }
        }

        MaterialEditor m_MaterialEditor = null;
        SerializedProperty m_MaterialProperty;
        SerializedProperty m_DrawDistanceProperty;
        SerializedProperty m_FadeScaleProperty;
        SerializedProperty m_StartAngleFadeProperty;
        SerializedProperty m_EndAngleFadeProperty;
        SerializedProperty m_UVScaleProperty;
        SerializedProperty m_UVBiasProperty;
        SerializedProperty m_ScaleMode;
        SerializedProperty m_Size;
        SerializedProperty[] m_SizeValues;
        SerializedProperty m_Offset;
        SerializedProperty[] m_OffsetValues;
        SerializedProperty m_FadeFactor;
        SerializedProperty m_RenderingLayerMask;

        int layerMask => (target as Component).gameObject.layer;
        bool layerMaskHasMultipleValues
        {
            get
            {
                if (targets.Length < 2)
                    return false;
                int layerMask = (targets[0] as Component).gameObject.layer;
                for (int index = 1; index < targets.Length; ++index)
                {
                    if ((targets[index] as Component).gameObject.layer != layerMask)
                        return true;
                }
                return false;
            }
        }

        static HierarchicalBox s_BoxHandle;
        static HierarchicalBox boxHandle
        {
            get
            {
                if (s_BoxHandle == null || s_BoxHandle.Equals(null))
                {
                    Color c = fullColor;
                    s_BoxHandle = new HierarchicalBox(s_LastColor, new[] { c, c, c, c, c, c });
                    s_BoxHandle.SetBaseColor(s_LastColor);
                    s_BoxHandle.monoHandle = false;
                }
                return s_BoxHandle;
            }
        }

        static DisplacableRectHandles s_uvHandles;
        static DisplacableRectHandles uvHandles
        {
            get
            {
                if (s_uvHandles == null || s_uvHandles.Equals(null))
                    s_uvHandles = new DisplacableRectHandles(s_LastColor);
                return s_uvHandles;
            }
        }

        static readonly BoxBoundsHandle s_AreaLightHandle =
            new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y };

        const SceneViewEditMode k_EditShapeWithoutPreservingUV = (SceneViewEditMode)90;
        const SceneViewEditMode k_EditShapePreservingUV = (SceneViewEditMode)91;
        const SceneViewEditMode k_EditUVAndPivot = (SceneViewEditMode)92;
        static readonly SceneViewEditMode[] k_EditVolumeModes = new SceneViewEditMode[]
        {
            k_EditShapeWithoutPreservingUV,
            k_EditShapePreservingUV,
            k_EditUVAndPivot,
        };

        static Func<Vector3, Quaternion, Vector3> s_DrawPivotHandle;

        static List<DecalProjectorEditor> s_Instances = new List<DecalProjectorEditor>();

        static DecalProjectorEditor FindEditorFromSelection()
        {
            GameObject[] selection = Selection.gameObjects;
            DecalProjector[] selectionTargets = Selection.GetFiltered<DecalProjector>(SelectionMode.Unfiltered);

            foreach (DecalProjectorEditor editor in s_Instances)
            {
                if (selectionTargets.Length != editor.targets.Length)
                    continue;
                bool allOk = true;
                foreach (DecalProjector selectionTarget in selectionTargets)
                {
                    if (!Array.Find(editor.targets, t => t == selectionTarget))
                    {
                        allOk = false;
                        break;
                    }
                }
                if (!allOk)
                    continue;
                return editor;
            }
            return null;
        }

        private void OnEnable()
        {
            s_Instances.Add(this);

            // Create an instance of the MaterialEditor
            UpdateMaterialEditor();

            // Fetch serialized properties
            m_MaterialProperty = serializedObject.FindProperty("m_Material");
            m_DrawDistanceProperty = serializedObject.FindProperty("m_DrawDistance");
            m_FadeScaleProperty = serializedObject.FindProperty("m_FadeScale");
            m_StartAngleFadeProperty = serializedObject.FindProperty("m_StartAngleFade");
            m_EndAngleFadeProperty = serializedObject.FindProperty("m_EndAngleFade");
            m_UVScaleProperty = serializedObject.FindProperty("m_UVScale");
            m_UVBiasProperty = serializedObject.FindProperty("m_UVBias");
            m_ScaleMode = serializedObject.FindProperty("m_ScaleMode");
            m_Size = serializedObject.FindProperty("m_Size");
            m_SizeValues = new[]
            {
                m_Size.FindPropertyRelative("x"),
                m_Size.FindPropertyRelative("y"),
                m_Size.FindPropertyRelative("z"),
            };
            m_Offset = serializedObject.FindProperty("m_Offset");
            m_OffsetValues = new[]
            {
                m_Offset.FindPropertyRelative("x"),
                m_Offset.FindPropertyRelative("y"),
                m_Offset.FindPropertyRelative("z"),
            };
            m_FadeFactor = serializedObject.FindProperty("m_FadeFactor");
            m_RenderingLayerMask = serializedObject.FindProperty("m_RenderingLayerMask");

            ReinitSavedRatioSizePivotPosition();
        }

        private void OnDisable()
        {
            s_Instances.Remove(this);
        }

        private void OnDestroy() =>
            DestroyImmediate(m_MaterialEditor);

        public bool HasFrameBounds()
        {
            return true;
        }

        public Bounds OnGetFrameBounds()
        {
            DecalProjector decalProjector = target as DecalProjector;

            return new Bounds(decalProjector.transform.position, boxHandle.size);
        }

        public void UpdateMaterialEditor()
        {
            int validMaterialsCount = 0;
            for (int index = 0; index < targets.Length; ++index)
            {
                DecalProjector decalProjector = (targets[index] as DecalProjector);
                if ((decalProjector != null) && (decalProjector.material != null))
                    validMaterialsCount++;
            }
            // Update material editor with the new material
            UnityEngine.Object[] materials = new UnityEngine.Object[validMaterialsCount];
            validMaterialsCount = 0;
            for (int index = 0; index < targets.Length; ++index)
            {
                DecalProjector decalProjector = (targets[index] as DecalProjector);

                if ((decalProjector != null) && (decalProjector.material != null))
                    materials[validMaterialsCount++] = (targets[index] as DecalProjector).material;
            }
            m_MaterialEditor = (MaterialEditor)CreateEditor(materials);
        }

        void OnSceneGUI()
        {
            //called on each targets
            DrawHandles();
        }

        void DrawBoxTransformationHandles(DecalProjector decalProjector)
        {
            Vector3 scale = decalProjector.effectiveScale;
            using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, scale)))
            {
                Vector3 centerStart = decalProjector.pivot;
                boxHandle.center = centerStart;
                boxHandle.size = decalProjector.size;

                Vector3 boundsSizePreviousOS = boxHandle.size;
                Vector3 boundsMinPreviousOS = boxHandle.size * -0.5f + boxHandle.center;

                EditorGUI.BeginChangeCheck();
                boxHandle.DrawHandle();
                if (EditorGUI.EndChangeCheck())
                {
                    // Adjust decal transform if handle changed.
                    Undo.RecordObject(decalProjector, "Decal Projector Change");

                    bool xChangeIsValid = scale.x != 0f;
                    bool yChangeIsValid = scale.y != 0f;
                    bool zChangeIsValid = scale.z != 0f;

                    // Preserve serialized state for axes with scale 0.
                    decalProjector.size = new Vector3(
                        xChangeIsValid ? boxHandle.size.x : decalProjector.size.x,
                        yChangeIsValid ? boxHandle.size.y : decalProjector.size.y,
                        zChangeIsValid ? boxHandle.size.z : decalProjector.size.z);
                    decalProjector.pivot = new Vector3(
                        xChangeIsValid ? boxHandle.center.x : decalProjector.pivot.x,
                        yChangeIsValid ? boxHandle.center.y : decalProjector.pivot.y,
                        zChangeIsValid ? boxHandle.center.z : decalProjector.pivot.z);

                    Vector3 boundsSizeCurrentOS = boxHandle.size;
                    Vector3 boundsMinCurrentOS = boxHandle.size * -0.5f + boxHandle.center;

                    if (editMode == k_EditShapePreservingUV)
                    {
                        // Treat decal projector bounds as a crop tool, rather than a scale tool.
                        // Compute a new uv scale and bias terms to pin decal projection pixels in world space, irrespective of projector bounds.
                        // Preserve serialized state for axes with scale 0.
                        Vector2 uvScale = decalProjector.uvScale;
                        Vector2 uvBias = decalProjector.uvBias;
                        if (xChangeIsValid)
                        {
                            uvScale.x *= Mathf.Max(k_LimitInv, boundsSizeCurrentOS.x) / Mathf.Max(k_LimitInv, boundsSizePreviousOS.x);
                            uvBias.x += (boundsMinCurrentOS.x - boundsMinPreviousOS.x) / Mathf.Max(k_LimitInv, boundsSizeCurrentOS.x) * uvScale.x;
                        }
                        if (yChangeIsValid)
                        {
                            uvScale.y *= Mathf.Max(k_LimitInv, boundsSizeCurrentOS.y) / Mathf.Max(k_LimitInv, boundsSizePreviousOS.y);
                            uvBias.y += (boundsMinCurrentOS.y - boundsMinPreviousOS.y) / Mathf.Max(k_LimitInv, boundsSizeCurrentOS.y) * uvScale.y;
                        }
                        decalProjector.uvScale = uvScale;
                        decalProjector.uvBias = uvBias;
                    }

                    if (PrefabUtility.IsPartOfNonAssetPrefabInstance(decalProjector))
                    {
                        PrefabUtility.RecordPrefabInstancePropertyModifications(decalProjector);
                    }
                }
            }
        }

        void DrawPivotHandles(DecalProjector decalProjector)
        {
            Vector3 scale = decalProjector.effectiveScale;
            Vector3 scaledPivot = Vector3.Scale(decalProjector.pivot, scale);
            Vector3 scaledSize = Vector3.Scale(decalProjector.size, scale);

            using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(Vector3.zero, decalProjector.transform.rotation, Vector3.one)))
            {
                EditorGUI.BeginChangeCheck();
                Vector3 newPosition = ProjectedTransform.DrawHandles(decalProjector.transform.position, .5f * scaledSize.z - scaledPivot.z, decalProjector.transform.rotation);
                if (EditorGUI.EndChangeCheck())
                {
                    Undo.RecordObjects(new UnityEngine.Object[] { decalProjector, decalProjector.transform }, "Decal Projector Change");

                    scaledPivot += Quaternion.Inverse(decalProjector.transform.rotation) * (decalProjector.transform.position - newPosition);
                    decalProjector.pivot = new Vector3(
                        scale.x != 0f ? scaledPivot.x / scale.x : decalProjector.pivot.x,
                        scale.y != 0f ? scaledPivot.y / scale.y : decalProjector.pivot.y,
                        scale.z != 0f ? scaledPivot.z / scale.z : decalProjector.pivot.z);
                    decalProjector.transform.position = newPosition;

                    ReinitSavedRatioSizePivotPosition();
                }
            }
        }

        void DrawUVHandles(DecalProjector decalProjector)
        {
            Vector3 scale = decalProjector.effectiveScale;
            Vector3 scaledPivot = Vector3.Scale(decalProjector.pivot, scale);
            Vector3 scaledSize = Vector3.Scale(decalProjector.size, scale);

            using (new Handles.DrawingScope(Matrix4x4.TRS(decalProjector.transform.position + decalProjector.transform.rotation * (scaledPivot - .5f * scaledSize), decalProjector.transform.rotation, scale)))
            {
                Vector2 uvScale = decalProjector.uvScale;
                Vector2 uvBias = decalProjector.uvBias;

                Vector2 uvSize = new Vector2(
                    (uvScale.x > k_Limit || uvScale.x < -k_Limit) ? 0f : decalProjector.size.x / uvScale.x,
                    (uvScale.y > k_Limit || uvScale.y < -k_Limit) ? 0f : decalProjector.size.y / uvScale.y
                );
                Vector2 uvCenter = uvSize * .5f - new Vector2(uvBias.x * uvSize.x, uvBias.y * uvSize.y);

                uvHandles.center = uvCenter;
                uvHandles.size = uvSize;

                EditorGUI.BeginChangeCheck();
                uvHandles.DrawHandle();
                if (EditorGUI.EndChangeCheck())
                {
                    Undo.RecordObject(decalProjector, "Decal Projector Change");

                    for (int channel = 0; channel < 2; channel++)
                    {
                        // Preserve serialized state for axes with the scaled size 0.
                        if (scaledSize[channel] != 0f)
                        {
                            float handleSize = uvHandles.size[channel];
                            float minusNewUVStart = .5f * handleSize - uvHandles.center[channel];
                            float decalSize = decalProjector.size[channel];
                            float limit = k_LimitInv * decalSize;
                            if (handleSize > limit || handleSize < -limit)
                            {
                                uvScale[channel] = decalSize / handleSize;
                                uvBias[channel] = minusNewUVStart / handleSize;
                            }
                            else
                            {
                                // TODO: Decide if uvHandles.size should ever have negative value. It can't currently.
                                uvScale[channel] = k_Limit * Mathf.Sign(handleSize);
                                uvBias[channel] = k_Limit * minusNewUVStart / decalSize;
                            }
                        }
                    }

                    decalProjector.uvScale = uvScale;
                    decalProjector.uvBias = uvBias;
                }
            }
        }

        void DrawHandles()
        {
            DecalProjector decalProjector = target as DecalProjector;

            if (editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV)
                DrawBoxTransformationHandles(decalProjector);
            else if (editMode == k_EditUVAndPivot)
            {
                DrawPivotHandles(decalProjector);
                DrawUVHandles(decalProjector);
            }
        }

        [DrawGizmo(GizmoType.Selected | GizmoType.Active)]
        static void DrawGizmosSelected(DecalProjector decalProjector, GizmoType gizmoType)
        {
            float lod = Gizmos.CalculateLOD(decalProjector.transform.position, decalProjector.size.magnitude * 0.25f);

            // skip drawing anything if it will be too small or behind the camera on screen
            if (lod < 0.1f)
                return;

            UpdateColorsInHandlesIfRequired();

            const float k_DotLength = 5f;

            // Draw them with scale applied to size and pivot instead of the matrix to keep the proportions of the arrow and lines.
            using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, Vector3.one)))
            {
                Vector3 scale = decalProjector.effectiveScale;
                Vector3 scaledPivot = Vector3.Scale(decalProjector.pivot, scale);
                Vector3 scaledSize = Vector3.Scale(decalProjector.size, scale);

                boxHandle.center = scaledPivot;
                boxHandle.size = scaledSize;
                bool isVolumeEditMode = editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV;
                bool isPivotEditMode = editMode == k_EditUVAndPivot;
                if (lod > 0.5f)
                {
                    boxHandle.DrawHull(isVolumeEditMode);
                }
                else
                    Handles.DrawWireCube(scaledPivot, scaledSize); // simplify the drawing if too small on screen

                if (lod == 1.0f) // only draw when big enough on screen to be useable
                {
                    Vector3 pivot = Vector3.zero;
                    Vector3 projectedPivot = new Vector3(0, 0, scaledPivot.z - .5f * scaledSize.z);

                    if (isPivotEditMode)
                    {
                        Handles.DrawDottedLines(new[] { projectedPivot, pivot }, k_DotLength);
                    }
                    else
                    {
                        float arrowSize = scaledSize.z * 0.25f;
                        Handles.ArrowHandleCap(0, projectedPivot, Quaternion.identity, arrowSize, EventType.Repaint);
                    }

                    //draw UV and bolder edges
                    using (new Handles.DrawingScope(Matrix4x4.TRS(decalProjector.transform.position + decalProjector.transform.rotation * new Vector3(scaledPivot.x, scaledPivot.y, scaledPivot.z - .5f * scaledSize.z), decalProjector.transform.rotation, Vector3.one)))
                    {
                        Vector2 UVSize = new Vector2(
                            (decalProjector.uvScale.x > k_Limit || decalProjector.uvScale.x < -k_Limit) ? 0f : scaledSize.x / decalProjector.uvScale.x,
                            (decalProjector.uvScale.y > k_Limit || decalProjector.uvScale.y < -k_Limit) ? 0f : scaledSize.y / decalProjector.uvScale.y
                        );
                        Vector2 UVCenter = UVSize * .5f - new Vector2(decalProjector.uvBias.x * UVSize.x, decalProjector.uvBias.y * UVSize.y) - (Vector2)scaledSize * .5f;

                        uvHandles.center = UVCenter;
                        uvHandles.size = UVSize;
                        uvHandles.DrawRect(dottedLine: true, screenSpaceSize: k_DotLength);

                        uvHandles.center = default;
                        uvHandles.size = scaledSize;
                        uvHandles.DrawRect(dottedLine: false, thickness: 3f);
                    }
                }
            }
        }

        static Func<Bounds> GetBoundsGetter(DecalProjector decalProjector)
        {
            return () =>
            {
                var bounds = new Bounds();
                var decalTransform = decalProjector.transform;
                bounds.Encapsulate(decalTransform.position);
                return bounds;
            };
        }

        // Temporarily save ratio between  size and pivot position while editing in inspector.
        // null or NaN is used to say that there is no saved ratio.
        // Aim is to keep proportion while sliding the value to 0 in Inspector and then go back to something else.
        // Current solution only works for the life of this editor, but is enough in most cases.
        // Which means if you go to there, selection something else and go back on it, pivot position is thus null.
        Dictionary<DecalProjector, Vector3> ratioSizePivotPositionSaved = null;

        void ReinitSavedRatioSizePivotPosition()
        {
            ratioSizePivotPositionSaved = null;
        }

        void UpdateSize(int axe, float newSize)
        {
            void UpdateSizeOfOneTarget(DecalProjector currentTarget)
            {
                //lazy init on demand as targets array cannot be accessed from OnSceneGUI so in edit mode.
                if (ratioSizePivotPositionSaved == null)
                {
                    ratioSizePivotPositionSaved = new Dictionary<DecalProjector, Vector3>();
                    foreach (DecalProjector projector in targets)
                        ratioSizePivotPositionSaved[projector] = new Vector3(float.NaN, float.NaN, float.NaN);
                }

                // Save old ratio if not registered
                // Either or are NaN or no one, check only first
                Vector3 saved = ratioSizePivotPositionSaved[currentTarget];
                if (float.IsNaN(saved[axe]))
                {
                    float oldSize = currentTarget.m_Size[axe];
                    saved[axe] = Mathf.Abs(oldSize) <= Mathf.Epsilon ? 0f : currentTarget.m_Offset[axe] / oldSize;
                    ratioSizePivotPositionSaved[currentTarget] = saved;
                }

                currentTarget.m_Size[axe] = newSize;
                currentTarget.m_Offset[axe] = saved[axe] * newSize;

                // refresh DecalProjector to update projection
                currentTarget.OnValidate();
            }

            // Manually register Undo as we work directly on the target
            Undo.RecordObjects(targets, "Change DecalProjector Size or Depth");

            // Apply any change on target first
            serializedObject.ApplyModifiedProperties();

            // update each target
            foreach (DecalProjector decalProjector in targets)
            {
                UpdateSizeOfOneTarget(decalProjector);

                // Fix for UUM-29105 (Changes made to Decal Project Prefab in the Inspector are not saved)
                // This editor doesn't use serializedObject to modify the target objects, explicitly mark the prefab
                // asset dirty to ensure the new data is saved.
                if (PrefabUtility.IsPartOfPrefabAsset(decalProjector))
                    EditorUtility.SetDirty(decalProjector);
            }

            // update again serialize object to register change in targets
            serializedObject.Update();

            // change was not tracked by SerializeReference so force repaint the scene views and game views
            UnityEditorInternal.InternalEditorUtility.RepaintAllViews();

            // strange: we need to force it throu serialization to update multiple differente value state (value are right but still detected as different)
            if (m_SizeValues[axe].hasMultipleDifferentValues)
                m_SizeValues[axe].floatValue = newSize;
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            bool materialChanged = false;
            bool isDefaultMaterial = false;
            bool isValidDecalMaterial = true;

            bool isDecalSupported = DecalProjector.isSupported;
            if (!isDecalSupported)
                EditorUtils.FeatureHelpBox("The current renderer has no Decal Renderer Feature added.", MessageType.Warning);

            EditorGUI.BeginChangeCheck();
            {
                EditorGUILayout.PropertyField(m_ScaleMode, k_ScaleMode);

                bool negativeScale = false;
                foreach (var target in targets)
                {
                    var decalProjector = target as DecalProjector;

                    float combinedScale = decalProjector.transform.lossyScale.x * decalProjector.transform.lossyScale.y * decalProjector.transform.lossyScale.z;
                    negativeScale |= combinedScale < 0 && decalProjector.scaleMode == DecalScaleMode.InheritFromHierarchy;
                }
                if (negativeScale)
                {
                    EditorGUILayout.HelpBox("Does not work with negative odd scaling (When there are odd number of scale components)", MessageType.Warning);
                }

                var widthRect = EditorGUILayout.GetControlRect();
                EditorGUI.BeginProperty(widthRect, k_WidthContent, m_SizeValues[0]);
                EditorGUI.BeginChangeCheck();
                float newSizeX = EditorGUI.FloatField(widthRect, k_WidthContent, m_SizeValues[0].floatValue);
                if (EditorGUI.EndChangeCheck())
                    UpdateSize(0, Mathf.Max(0, newSizeX));
                EditorGUI.EndProperty();

                var heightRect = EditorGUILayout.GetControlRect();
                EditorGUI.BeginProperty(heightRect, k_HeightContent, m_SizeValues[1]);
                EditorGUI.BeginChangeCheck();
                float newSizeY = EditorGUI.FloatField(heightRect, k_HeightContent, m_SizeValues[1].floatValue);
                if (EditorGUI.EndChangeCheck())
                    UpdateSize(1, Mathf.Max(0, newSizeY));
                EditorGUI.EndProperty();

                var projectionRect = EditorGUILayout.GetControlRect();
                EditorGUI.BeginProperty(projectionRect, k_ProjectionDepthContent, m_SizeValues[2]);
                EditorGUI.BeginChangeCheck();
                float newSizeZ = EditorGUI.FloatField(projectionRect, k_ProjectionDepthContent, m_SizeValues[2].floatValue);
                if (EditorGUI.EndChangeCheck())
                    UpdateSize(2, Mathf.Max(0, newSizeZ));
                EditorGUI.EndProperty();

                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_Offset, k_Offset);
                if (EditorGUI.EndChangeCheck())
                    ReinitSavedRatioSizePivotPosition();

                EditorGUILayout.Space();

                EditorGUI.BeginChangeCheck();
                MaterialFieldWithButton(m_MaterialProperty, k_MaterialContent);
                materialChanged = EditorGUI.EndChangeCheck();

                EditorGUILayout.PropertyField(m_RenderingLayerMask, k_RenderingLayerMaskContent);

                foreach (var target in targets)
                {
                    var decalProjector = target as DecalProjector;
                    var mat = decalProjector.material;

                    isDefaultMaterial |= decalProjector.material == DecalProjector.defaultMaterial;
                    isValidDecalMaterial &= decalProjector.IsValid();
                }

                if (m_MaterialEditor && !isValidDecalMaterial)
                {
                    CoreEditorUtils.DrawFixMeBox("Decal only work with Decal Material. Use default material or create from decal shader graph sub target.", () =>
                    {
                        m_MaterialProperty.objectReferenceValue = DecalProjector.defaultMaterial;
                        materialChanged = true;
                    });
                }

                EditorGUI.indentLevel++;
                EditorGUILayout.PropertyField(m_UVScaleProperty, k_UVScaleContent);
                EditorGUILayout.PropertyField(m_UVBiasProperty, k_UVBiasContent);
                EditorGUILayout.PropertyField(m_FadeFactor, k_OpacityContent);
                EditorGUI.indentLevel--;

                bool angleFadeSupport = false;
                foreach (var decalProjector in targets)
                {
                    var mat = (decalProjector as DecalProjector).material;
                    if (mat == null)
                        continue;
                    angleFadeSupport = mat.HasProperty("_DecalAngleFadeSupported");
                }

                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_DrawDistanceProperty, k_DistanceContent);
                if (EditorGUI.EndChangeCheck() && m_DrawDistanceProperty.floatValue < 0f)
                    m_DrawDistanceProperty.floatValue = 0f;

                EditorGUI.indentLevel++;
                EditorGUILayout.PropertyField(m_FadeScaleProperty, k_FadeScaleContent);
                EditorGUI.indentLevel--;

                using (new EditorGUI.DisabledScope(!angleFadeSupport))
                {
                    float angleFadeMinValue = m_StartAngleFadeProperty.floatValue;
                    float angleFadeMaxValue = m_EndAngleFadeProperty.floatValue;
                    EditorGUI.BeginChangeCheck();
                    EditorGUILayout.MinMaxSlider(k_AngleFadeContent, ref angleFadeMinValue, ref angleFadeMaxValue, 0.0f, 180.0f);
                    if (EditorGUI.EndChangeCheck())
                    {
                        m_StartAngleFadeProperty.floatValue = angleFadeMinValue;
                        m_EndAngleFadeProperty.floatValue = angleFadeMaxValue;
                    }
                }

                if (!angleFadeSupport && isValidDecalMaterial)
                {
                    EditorGUILayout.HelpBox($"Decal Angle Fade is not enabled in Shader. In ShaderGraph enable Angle Fade option.", MessageType.Info);
                }

                EditorGUILayout.Space();
            }
            if (EditorGUI.EndChangeCheck())
                serializedObject.ApplyModifiedProperties();

            if (materialChanged)
                UpdateMaterialEditor();

            if (layerMaskHasMultipleValues || layerMask != (target as Component).gameObject.layer)
            {
                foreach (var decalProjector in targets)
                {
                    (decalProjector as DecalProjector).OnValidate();
                }
            }

            if (m_MaterialEditor != null)
            {
                // We need to prevent the user to edit default decal materials
                if (isValidDecalMaterial)
                {
                    using (new DecalProjectorScope())
                    {
                        using (new EditorGUI.DisabledGroupScope(isDefaultMaterial))
                        {
                            // Draw the material's foldout and the material shader field
                            // Required to call m_MaterialEditor.OnInspectorGUI ();
                            m_MaterialEditor.DrawHeader();

                            // Draw the material properties
                            // Works only if the foldout of m_MaterialEditor.DrawHeader () is open
                            m_MaterialEditor.OnInspectorGUI();
                        }
                    }
                }
            }
        }

        internal void MaterialFieldWithButton(SerializedProperty prop, GUIContent label)
        {
            const int k_NewFieldWidth = 70;

            var rect = EditorGUILayout.GetControlRect();
            rect.xMax -= k_NewFieldWidth + 2;

            EditorGUI.PropertyField(rect, prop, label);

            var newFieldRect = rect;
            newFieldRect.x = rect.xMax + 2;
            newFieldRect.width = k_NewFieldWidth;

            if (!EditorGUI.DropdownButton(newFieldRect, k_NewDecalMaterialButtonText, FocusType.Keyboard))
                return;

            GenericMenu menu = new GenericMenu();

            menu.AddItem(new GUIContent(k_NewDecalText), false, () => CreateDefaultDecalMaterial(targets));
            menu.AddItem(new GUIContent(k_NewSGDecalText), false, () => CreateDecalMaterialFromTemplate(targets, k_DefaultDecalShaderGraphTemplatePath));

            // For later introduction of SG Filtered Template Browser
            //menu.AddItem(new GUIContent(k_NewSGDecalFromTemplateText), false, () => CreateDecalMaterialFromTemplate(targets));

            menu.DropDown(newFieldRect);
        }

        static void CreateDecalMaterialFromTemplate(UnityEngine.Object[] decalProjectors, string templatePath = null)
        {
            CreateShaderGraph.CreateGraphAndMaterialFromTemplate((material) =>
            {
                SetDecalMaterial(decalProjectors, material);
            },
            templatePath,
            $"New {k_NewSGDecalText}");
        }

        static void CreateDefaultDecalMaterial(UnityEngine.Object[] decalProjectors)
        {
            string materialName = "New " + k_NewDecalText;

            AssetCreationUtil.CreateMaterial(
                materialName,
                (material) =>
                {
                    SetDecalMaterial(decalProjectors, material);
                },
                DecalProjector.defaultMaterial.shader
            );
        }

        static void SetDecalMaterial(UnityEngine.Object[] decalProjectors, Material material)
        {
            var selection = new List<GameObject>();
            foreach (DecalProjector decalProjector in decalProjectors)
            {
                decalProjector.material = material;
                EditorUtility.SetDirty(decalProjector);
                selection.Add(decalProjector.gameObject);
            }
            Selection.objects = selection.ToArray();
        }

        [Shortcut("URP/Decal: Handle changing size stretching UV", typeof(SceneView), KeyCode.Keypad1, ShortcutModifiers.Action)]
        static void EnterEditModeWithoutPreservingUV(ShortcutArguments args)
        {
            //If editor is not there, then the selected GameObject does not contains a DecalProjector
            DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent<DecalProjector>();
            if (activeDecalProjector == null || activeDecalProjector.Equals(null))
                return;

            ChangeEditMode(k_EditShapeWithoutPreservingUV, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection());
        }

        [Shortcut("URP/Decal: Handle changing size cropping UV", typeof(SceneView), KeyCode.Keypad2, ShortcutModifiers.Action)]
        static void EnterEditModePreservingUV(ShortcutArguments args)
        {
            //If editor is not there, then the selected GameObject does not contains a DecalProjector
            DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent<DecalProjector>();
            if (activeDecalProjector == null || activeDecalProjector.Equals(null))
                return;

            ChangeEditMode(k_EditShapePreservingUV, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection());
        }

        [Shortcut("URP/Decal: Handle changing pivot position and UVs", typeof(SceneView), KeyCode.Keypad3, ShortcutModifiers.Action)]
        static void EnterEditModePivotPreservingUV(ShortcutArguments args)
        {
            //If editor is not there, then the selected GameObject does not contains a DecalProjector
            DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent<DecalProjector>();
            if (activeDecalProjector == null || activeDecalProjector.Equals(null))
                return;

            ChangeEditMode(k_EditUVAndPivot, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection());
        }

        [Shortcut("URP/Decal: Handle swap between cropping and stretching UV", typeof(SceneView), KeyCode.Keypad4, ShortcutModifiers.Action)]
        static void SwappingEditUVMode(ShortcutArguments args)
        {
            //If editor is not there, then the selected GameObject does not contains a DecalProjector
            DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent<DecalProjector>();
            if (activeDecalProjector == null || activeDecalProjector.Equals(null))
                return;

            SceneViewEditMode targetMode = SceneViewEditMode.None;
            switch (editMode)
            {
                case k_EditShapePreservingUV:
                case k_EditUVAndPivot:
                    targetMode = k_EditShapeWithoutPreservingUV;
                    break;
                case k_EditShapeWithoutPreservingUV:
                    targetMode = k_EditShapePreservingUV;
                    break;
            }
            if (targetMode != SceneViewEditMode.None)
                ChangeEditMode(targetMode, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection());
        }

        [Shortcut("URP/Decal: Stop Editing", typeof(SceneView), KeyCode.Keypad0, ShortcutModifiers.Action)]
        static void ExitEditMode(ShortcutArguments args)
        {
            //If editor is not there, then the selected GameObject does not contains a DecalProjector
            DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent<DecalProjector>();
            if (activeDecalProjector == null || activeDecalProjector.Equals(null))
                return;

            QuitEditMode();
        }

        [EditorTool(Description, typeof(DecalProjector), toolPriority = (int)Mode)]
        internal class DecalProjectorModifyScaleTool : GenericEditorTool<DecalProjector>
        {
            private const string Description = DecalProjectorEditor.k_EditShapeWithoutPreservingUVTooltip;
            private const EditMode.SceneViewEditMode Mode = DecalProjectorEditor.k_EditShapeWithoutPreservingUV;
            private const string IconName = "ScaleTool";

            protected DecalProjectorModifyScaleTool() : base(Description, Mode, IconName) { }
        }

        [EditorTool(Description, typeof(DecalProjector), toolPriority = (int)Mode)]
        internal class DecalProjectorEditShapeTool : GenericEditorTool<DecalProjector>
        {
            private const string Description = DecalProjectorEditor.k_EditShapePreservingUVTooltip;
            private const EditMode.SceneViewEditMode Mode = DecalProjectorEditor.k_EditShapePreservingUV;
            private const string IconName = "RectTool";

            protected DecalProjectorEditShapeTool() : base(Description, Mode, IconName) { }
        }

        [EditorTool(Description, typeof(DecalProjector), toolPriority = (int)Mode)]
        internal class DecalProjectorEditTool : GenericEditorTool<DecalProjector>
        {
            private const string Description = DecalProjectorEditor.k_EditUVTooltip;
            private const EditMode.SceneViewEditMode Mode = DecalProjectorEditor.k_EditUVAndPivot;
            private const string IconName = "MoveTool";

            protected DecalProjectorEditTool() : base(Description, Mode, IconName) { }
        }
    }
}