// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.AnimatedValues;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;

namespace UnityEditor
{
    [CustomEditor(typeof(ReflectionProbe))]
    [CanEditMultipleObjects]
    internal class ReflectionProbeEditor : Editor
    {
        static ReflectionProbeEditor s_LastInteractedEditor;
        static HashSet<ReflectionProbe> s_CurrentlyEditedProbes = new HashSet<ReflectionProbe>();

        SerializedProperty m_Mode;
        SerializedProperty m_RefreshMode;
        SerializedProperty m_TimeSlicingMode;
        SerializedProperty m_Resolution;
        SerializedProperty m_ShadowDistance;
        SerializedProperty m_Importance;
        SerializedProperty m_BoxSize;
        SerializedProperty m_BoxOffset;
        SerializedProperty m_CullingMask;
        SerializedProperty m_ClearFlags;
        SerializedProperty m_BackgroundColor;
        SerializedProperty m_HDR;
        SerializedProperty m_BoxProjection;
        SerializedProperty m_IntensityMultiplier;
        SerializedProperty m_BlendDistance;
        SerializedProperty m_CustomBakedTexture;
        SerializedProperty m_RenderDynamicObjects;
        SerializedProperty m_UseOcclusionCulling;

        SerializedProperty[] m_NearAndFarProperties;

        private static Mesh s_SphereMesh;
        private Material m_ReflectiveMaterial;
        private Matrix4x4 m_OldLocalSpace = Matrix4x4.identity;
        private float m_MipLevelPreview = 0.0F;

        private BoxBoundsHandle m_BoundsHandle = new BoxBoundsHandle();

        private Hashtable m_CachedGizmoMaterials = new Hashtable();

        public static void GetResolutionArray(ref int[] resolutionList, ref GUIContent[] resolutionStringList)
        {
            if (Styles.reflectionResolutionValuesArray == null && Styles.reflectionResolutionTextArray == null)
            {
                int cubemapResolution = Mathf.Max(1, ReflectionProbe.minBakedCubemapResolution);

                List<int> envReflectionResolutionValues = new List<int>();
                List<GUIContent> envReflectionResolutionText = new List<GUIContent>();

                do
                {
                    envReflectionResolutionValues.Add(cubemapResolution);
                    envReflectionResolutionText.Add(new GUIContent(cubemapResolution.ToString()));
                    cubemapResolution *= 2;
                }
                while (cubemapResolution <= ReflectionProbe.maxBakedCubemapResolution);

                Styles.reflectionResolutionValuesArray = envReflectionResolutionValues.ToArray();
                Styles.reflectionResolutionTextArray = envReflectionResolutionText.ToArray();
            }

            resolutionList = Styles.reflectionResolutionValuesArray;
            resolutionStringList = Styles.reflectionResolutionTextArray;
        }

        static internal class Styles
        {
            static Styles()
            {
                richTextMiniLabel.richText = true;
            }

            public static GUIStyle richTextMiniLabel = new GUIStyle(EditorStyles.miniLabel);

            public static string bakeButtonText = "Bake";
            public static string[] bakeCustomOptionText = { "Bake as new Cubemap..." };
            public static string[] bakeButtonsText = { "Bake All Reflection Probes" };

            public static GUIContent bakeCustomButtonText = EditorGUIUtility.TrTextContent("Bake", "Bakes Reflection Probe's cubemap, overwriting the existing cubemap texture asset (if any).");
            public static GUIContent runtimeSettingsHeader = EditorGUIUtility.TrTextContent("Runtime Settings", "These settings determine this Probe's priority, blending, intensity, and zone of effect and works in conjunction with the cubemap of this probe when it is rendered.");
            public static GUIContent backgroundColorText = EditorGUIUtility.TrTextContent("Background Color", "Camera clears the screen to this color before rendering.");
            public static GUIContent clearFlagsText = EditorGUIUtility.TrTextContent("Clear Flags", "Specify how to fill empty areas of the cubemap.");
            public static GUIContent intensityText = EditorGUIUtility.TrTextContent("Intensity", "The intensity modifier the Editor applies to this probe's texture in its shader.");
            public static GUIContent resolutionText = EditorGUIUtility.TrTextContent("Resolution", "The resolution of the cubemap.");
            public static GUIContent captureCubemapHeader = EditorGUIUtility.TrTextContent("Cubemap Capture Settings", "Settings that determine how to render this probe's cubemap.");
            public static GUIContent boxProjectionText = EditorGUIUtility.TrTextContent("Box Projection", "When enabled, Unity assumes that the reflected light is originating from the inside of the probe's box, rather than from infinitely far away. This is useful for box-shaped indoor environments.");
            public static GUIContent blendDistanceText = EditorGUIUtility.TrTextContent("Blend Distance", "Area around the probe where it is blended with other probes. Only used in deferred probes.");
            public static GUIContent sizeText = EditorGUIUtility.TrTextContent("Box Size", "The size of the box in which the reflections will be applied to objects. The value is not affected by the Transform of the Game Object.");
            public static GUIContent centerText = EditorGUIUtility.TrTextContent("Box Offset", "The center of the box in which the reflections will be applied to objects. The value is relative to the position of the Game Object.");
            public static GUIContent customCubemapText = EditorGUIUtility.TrTextContent("Cubemap", "Sets a custom cubemap for this probe.");
            public static GUIContent importanceText = EditorGUIUtility.TrTextContent("Importance", "When reflection probes overlap, Unity uses Importance to determine which probe should take priority.");
            public static GUIContent renderDynamicObjects = EditorGUIUtility.TrTextContent("Dynamic Objects", "If enabled dynamic objects are also rendered into the cubemap");
            public static GUIContent timeSlicing = EditorGUIUtility.TrTextContent("Time Slicing", "If enabled this probe will update over several frames, to help reduce the impact on the frame rate");
            public static GUIContent refreshMode = EditorGUIUtility.TrTextContent("Refresh Mode", "Controls how this probe refreshes in the Player");
            public static GUIContent useOcclusionCulling = EditorGUIUtility.TrTextContent("Occlusion Culling", "If this property is enabled, geometries which are blocked from the probe's line of sight are skipped during rendering.");
            public static GUIContent hdrText = EditorGUIUtility.TrTextContent("HDR", "Enable High Dynamic Range rendering.");
            public static GUIContent shadowDistanceText = EditorGUIUtility.TrTextContent("Shadow Distance", "Maximum distance at which Unity renders shadows associated with this probe.");
            public static GUIContent cullingMaskText = EditorGUIUtility.TrTextContent("Culling Mask", "Allows objects on specified layers to be included or excluded in the reflection.");

            public static GUIContent typeText = EditorGUIUtility.TrTextContent("Type", "Specify the lighting setup for this probe: Baked, Custom, or Realtime.");
            public static GUIContent[] reflectionProbeMode = { EditorGUIUtility.TrTextContent("Baked"), EditorGUIUtility.TrTextContent("Custom"), EditorGUIUtility.TrTextContent("Realtime") };
            public static int[] reflectionProbeModeValues = { (int)ReflectionProbeMode.Baked, (int)ReflectionProbeMode.Custom, (int)ReflectionProbeMode.Realtime };

            public static int[] reflectionResolutionValuesArray = null;
            public static GUIContent[] reflectionResolutionTextArray = null;

            public static GUIContent[] clearFlags =
            {
                EditorGUIUtility.TrTextContent("Skybox"),
                EditorGUIUtility.TrTextContent("Solid Color")
            };
            public static int[] clearFlagsValues = { 1, 2 }; // taken from Camera.h

            private static GUIContent customPrivitiveBoundsHandleEditModeButton = new GUIContent(
                EditorGUIUtility.IconContent("EditCollider").image,
                EditorGUIUtility.TrTextContent("Adjust the probe's zone of effect. Holding Alt or Shift and click the control handle to pin the center or scale the volume uniformly.").text
            );
            public static GUIContent[] toolContents =
            {
                customPrivitiveBoundsHandleEditModeButton,
                EditorGUIUtility.TrIconContent("MoveTool", "Move the selected objects.")
            };
            public static EditMode.SceneViewEditMode[] sceneViewEditModes = new[]
            {
                EditMode.SceneViewEditMode.ReflectionProbeBox,
                EditMode.SceneViewEditMode.ReflectionProbeOrigin
            };

            public static string baseSceneEditingToolText = "<color=grey>Probe Scene Editing Mode:</color> ";
            public static GUIContent[] toolNames =
            {
                new GUIContent(baseSceneEditingToolText + "Box Projection Bounds", ""),
                new GUIContent(baseSceneEditingToolText + "Probe Origin", "")
            };
        } // end of class Styles

        // Should match reflection probe gizmo color in GizmoDrawers.cpp!
        internal static Color kGizmoReflectionProbe = new Color(0xFF / 255f, 0xE5 / 255f, 0x94 / 255f, 0x80 / 255f);
        internal static Color kGizmoReflectionProbeDisabled = new Color(0x99 / 255f, 0x89 / 255f, 0x59 / 255f, 0x60 / 255f);
        internal static Color kGizmoHandleReflectionProbe = new Color(0xFF / 255f, 0xE5 / 255f, 0xAA / 255f, 0xFF / 255f);

        private SavedBool m_ShowRuntimeSettings;
        private SavedBool m_ShowCubemapCaptureSettings;

        readonly AnimBool m_ShowProbeModeRealtimeOptions = new AnimBool(); // p.mode == ReflectionProbeMode.Realtime; Will be brought back in 5.1
        readonly AnimBool m_ShowProbeModeCustomOptions = new AnimBool();
        readonly AnimBool m_ShowBoxOptions = new AnimBool();

        private TextureInspector m_CubemapEditor = null;

        static bool IsReflectionProbeEditMode(EditMode.SceneViewEditMode editMode)
        {
            return editMode == EditMode.SceneViewEditMode.ReflectionProbeBox ||
                editMode == EditMode.SceneViewEditMode.ReflectionProbeOrigin;
        }

        bool sceneViewEditing
        {
            get { return IsReflectionProbeEditMode(EditMode.editMode) && EditMode.IsOwner(this); }
        }

        public void OnEnable()
        {
            m_Mode = serializedObject.FindProperty("m_Mode");
            m_RefreshMode = serializedObject.FindProperty("m_RefreshMode");
            m_TimeSlicingMode = serializedObject.FindProperty("m_TimeSlicingMode");

            m_Resolution = serializedObject.FindProperty("m_Resolution");
            m_NearAndFarProperties = new[] { serializedObject.FindProperty("m_NearClip"), serializedObject.FindProperty("m_FarClip") };
            m_ShadowDistance = serializedObject.FindProperty("m_ShadowDistance");
            m_Importance = serializedObject.FindProperty("m_Importance");
            m_BoxSize = serializedObject.FindProperty("m_BoxSize");
            m_BoxOffset = serializedObject.FindProperty("m_BoxOffset");
            m_CullingMask = serializedObject.FindProperty("m_CullingMask");
            m_ClearFlags = serializedObject.FindProperty("m_ClearFlags");
            m_BackgroundColor = serializedObject.FindProperty("m_BackGroundColor");
            m_HDR = serializedObject.FindProperty("m_HDR");
            m_BoxProjection = serializedObject.FindProperty("m_BoxProjection");
            m_IntensityMultiplier = serializedObject.FindProperty("m_IntensityMultiplier");
            m_BlendDistance = serializedObject.FindProperty("m_BlendDistance");
            m_CustomBakedTexture = serializedObject.FindProperty("m_CustomBakedTexture");
            m_RenderDynamicObjects = serializedObject.FindProperty("m_RenderDynamicObjects");
            m_UseOcclusionCulling = serializedObject.FindProperty("m_UseOcclusionCulling");

            ReflectionProbe p = target as ReflectionProbe;
            m_ShowProbeModeRealtimeOptions.valueChanged.AddListener(Repaint);
            m_ShowProbeModeCustomOptions.valueChanged.AddListener(Repaint);
            m_ShowBoxOptions.valueChanged.AddListener(Repaint);
            m_ShowProbeModeRealtimeOptions.value = p.mode == ReflectionProbeMode.Realtime;
            m_ShowProbeModeCustomOptions.value = p.mode == ReflectionProbeMode.Custom;
            m_ShowBoxOptions.value = true;

            m_BoundsHandle.handleColor = kGizmoHandleReflectionProbe;
            m_BoundsHandle.wireframeColor = Color.clear;

            m_ShowRuntimeSettings = new SavedBool("ReflectionProbeEditor.ShowRuntimeSettings", true);
            m_ShowCubemapCaptureSettings = new SavedBool("ReflectionProbeEditor.ShowCubemapCaptureSettings", true);

            UpdateOldLocalSpace();
            SceneView.beforeSceneGui += OnPreSceneGUICallback;

            for (int i = 0; i < targets.Length; ++i)
            {
                s_CurrentlyEditedProbes.Add((ReflectionProbe)targets[i]);
            }
        }

        public void OnDisable()
        {
            SceneView.beforeSceneGui -= OnPreSceneGUICallback;

            DestroyImmediate(m_ReflectiveMaterial);
            DestroyImmediate(m_CubemapEditor);

            foreach (Material mat in m_CachedGizmoMaterials.Values)
                DestroyImmediate(mat);
            m_CachedGizmoMaterials.Clear();

            for (int i = 0; i < targets.Length; ++i)
            {
                s_CurrentlyEditedProbes.Remove((ReflectionProbe)targets[i]);
            }
        }

        private bool IsCollidingWithOtherProbes(string targetPath, ReflectionProbe targetProbe, out ReflectionProbe collidingProbe)
        {
            ReflectionProbe[] probes = FindObjectsOfType<ReflectionProbe>().ToArray();
            collidingProbe = null;
            foreach (var probe in probes)
            {
                if (probe == targetProbe || probe.customBakedTexture == null)
                    continue;
                string path = AssetDatabase.GetAssetPath(probe.customBakedTexture);
                if (path == targetPath)
                {
                    collidingProbe = probe;
                    return true;
                }
            }
            return false;
        }

        private void BakeCustomReflectionProbe(ReflectionProbe probe, bool usePreviousAssetPath)
        {
            string path = "";
            if (usePreviousAssetPath)
                path = AssetDatabase.GetAssetPath(probe.customBakedTexture);

            string targetExtension = probe.hdr ? "exr" : "png";
            if (string.IsNullOrEmpty(path) || Path.GetExtension(path) != "." + targetExtension)
            {
                // We use the path of the active scene as the target path
                string targetPath = FileUtil.GetPathWithoutExtension(SceneManager.GetActiveScene().path);
                if (string.IsNullOrEmpty(targetPath))
                    targetPath = "Assets";
                else if (Directory.Exists(targetPath) == false)
                    Directory.CreateDirectory(targetPath);

                string fileName = probe.name + (probe.hdr ? "-reflectionHDR" : "-reflection") + "." + targetExtension;
                fileName = Path.GetFileNameWithoutExtension(AssetDatabase.GenerateUniqueAssetPath(Path.Combine(targetPath, fileName)));

                path = EditorUtility.SaveFilePanelInProject("Save reflection probe's cubemap.", fileName, targetExtension, "", targetPath);
                if (string.IsNullOrEmpty(path))
                    return;

                ReflectionProbe collidingProbe;
                if (IsCollidingWithOtherProbes(path, probe, out collidingProbe))
                {
                    if (!EditorUtility.DisplayDialog("Cubemap is used by other reflection probe",
                        string.Format("'{0}' path is used by the game object '{1}', do you really want to overwrite it?",
                            path, collidingProbe.name), "Yes", "No"))
                    {
                        return;
                    }
                }
            }

            EditorUtility.DisplayProgressBar("Reflection Probes", "Baking " + path, 0.5f);
            if (!Lightmapping.BakeReflectionProbe(probe, path))
                Debug.LogError("Failed to bake reflection probe to " + path);
            EditorUtility.ClearProgressBar();
        }

        private void OnBakeCustomButton(object data)
        {
            int mode = (int)data;

            ReflectionProbe p = target as ReflectionProbe;
            if (mode == 0)
                BakeCustomReflectionProbe(p, false);
        }

        private void OnBakeButton(object data)
        {
            int mode = (int)data;
            if (mode == 0)
                Lightmapping.BakeAllReflectionProbesSnapshots();
        }

        ReflectionProbe reflectionProbeTarget
        {
            get { return (ReflectionProbe)target; }
        }

        void DoBakeButton()
        {
            if (reflectionProbeTarget.mode == ReflectionProbeMode.Realtime)
            {
                EditorGUILayout.HelpBox("Baking of this reflection probe should be initiated from the scripting API because the type is 'Realtime'", MessageType.Info);

                if (!QualitySettings.realtimeReflectionProbes)
                    EditorGUILayout.HelpBox("Realtime reflection probes are disabled in Quality Settings", MessageType.Warning);
                return;
            }

            if (reflectionProbeTarget.mode == ReflectionProbeMode.Baked && Lightmapping.GetLightingSettingsOrDefaultsFallback().autoGenerate)
            {
                EditorGUILayout.HelpBox("Baking of this reflection probe is automatic because this probe's type is 'Baked' and the Lighting window is using 'Auto Baking'. The cubemap created is stored in the GI cache.", MessageType.Info);
                return;
            }

            GUILayout.BeginHorizontal();

            switch (reflectionProbeMode)
            {
                case ReflectionProbeMode.Custom:
                    if (EditorGUI.ButtonWithDropdownList(Styles.bakeCustomButtonText, Styles.bakeCustomOptionText, OnBakeCustomButton))
                    {
                        BakeCustomReflectionProbe(reflectionProbeTarget, true);
                        GUIUtility.ExitGUI();
                    }
                    break;

                case ReflectionProbeMode.Baked:
                    using (new EditorGUI.DisabledScope(!reflectionProbeTarget.enabled))
                    {
                        // Bake button in non-continous mode
                        if (EditorGUI.ButtonWithDropdownList(Styles.bakeButtonText, Styles.bakeButtonsText, OnBakeButton))
                        {
                            Lightmapping.BakeReflectionProbeSnapshot(reflectionProbeTarget);
                            GUIUtility.ExitGUI();
                        }
                    }

                    break;

                case ReflectionProbeMode.Realtime:
                    // Not showing bake button in realtime
                    break;
            }

            GUILayout.EndHorizontal();
        }

        void DoToolbar()
        {
            // Show the master tool selector
            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            GUI.changed = false;
            var oldEditMode = EditMode.editMode;

            EditorGUI.BeginChangeCheck();
            EditMode.DoInspectorToolbar(Styles.sceneViewEditModes, Styles.toolContents, this);
            if (EditorGUI.EndChangeCheck())
                s_LastInteractedEditor = this;

            if (oldEditMode != EditMode.editMode)
            {
                switch (EditMode.editMode)
                {
                    case EditMode.SceneViewEditMode.ReflectionProbeOrigin:
                        UpdateOldLocalSpace();
                        break;
                }
                if (Toolbar.get != null)
                    Toolbar.get.Repaint();
            }

            GUILayout.FlexibleSpace();
            GUILayout.EndHorizontal();

            // Info box for tools
            GUILayout.BeginVertical(EditorStyles.helpBox);
            string helpText = Styles.baseSceneEditingToolText;
            if (sceneViewEditing)
            {
                int index = ArrayUtility.IndexOf(Styles.sceneViewEditModes, EditMode.editMode);
                if (index >= 0)
                    helpText = Styles.toolNames[index].text;
            }
            GUILayout.Label(helpText, Styles.richTextMiniLabel);
            GUILayout.EndVertical();
            EditorGUILayout.Space();
        }

        ReflectionProbeMode reflectionProbeMode
        {
            get { return reflectionProbeTarget.mode; }
        }


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

            if (targets.Length == 1)
                DoToolbar();

            m_ShowProbeModeRealtimeOptions.target = reflectionProbeMode == ReflectionProbeMode.Realtime;
            m_ShowProbeModeCustomOptions.target = reflectionProbeMode == ReflectionProbeMode.Custom;

            // Bake/custom/realtime type
            EditorGUILayout.IntPopup(m_Mode, Styles.reflectionProbeMode, Styles.reflectionProbeModeValues, Styles.typeText);

            // We cannot show multiple different type controls
            if (!m_Mode.hasMultipleDifferentValues)
            {
                EditorGUI.indentLevel++;
                {
                    // Custom cubemap UI (Bake button and manual cubemap assignment)
                    if (EditorGUILayout.BeginFadeGroup(m_ShowProbeModeCustomOptions.faded))
                    {
                        EditorGUILayout.PropertyField(m_RenderDynamicObjects, Styles.renderDynamicObjects);

                        EditorGUI.BeginChangeCheck();
                        EditorGUI.showMixedValue = m_CustomBakedTexture.hasMultipleDifferentValues;
                        var newCubemap = EditorGUILayout.ObjectField(Styles.customCubemapText, m_CustomBakedTexture.objectReferenceValue, typeof(Texture), false);
                        EditorGUI.showMixedValue = false;
                        if (EditorGUI.EndChangeCheck())
                            m_CustomBakedTexture.objectReferenceValue = newCubemap;
                    }
                    EditorGUILayout.EndFadeGroup();

                    // Realtime UI
                    if (EditorGUILayout.BeginFadeGroup(m_ShowProbeModeRealtimeOptions.faded))
                    {
                        EditorGUILayout.PropertyField(m_RefreshMode, Styles.refreshMode);
                        EditorGUILayout.PropertyField(m_TimeSlicingMode, Styles.timeSlicing);

                        EditorGUILayout.Space();
                    }
                    EditorGUILayout.EndFadeGroup();
                }
                EditorGUI.indentLevel--;
            }

            EditorGUILayout.Space();

            m_ShowRuntimeSettings.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowRuntimeSettings.value, Styles.runtimeSettingsHeader);

            if (m_ShowRuntimeSettings.value)
            {
                EditorGUI.indentLevel++;

                EditorGUILayout.PropertyField(m_Importance, Styles.importanceText);
                EditorGUILayout.PropertyField(m_IntensityMultiplier, Styles.intensityText);

                // Only take graphic Tier settings into account when built-in render pipeline is active.
                if (GraphicsSettings.currentRenderPipeline == null && Rendering.EditorGraphicsSettings.GetCurrentTierSettings().reflectionProbeBoxProjection == false)
                {
                    using (new EditorGUI.DisabledScope(true))
                    {
                        EditorGUILayout.Toggle(Styles.boxProjectionText, false);
                    }
                }
                else
                {
                    EditorGUILayout.PropertyField(m_BoxProjection, Styles.boxProjectionText);
                }

                bool isDeferredRenderingPath = SceneView.IsUsingDeferredRenderingPath();
                bool isDeferredReflections = isDeferredRenderingPath && (UnityEngine.Rendering.GraphicsSettings.GetShaderMode(BuiltinShaderType.DeferredReflections) != BuiltinShaderMode.Disabled);
                using (new EditorGUI.DisabledScope(!isDeferredReflections && !SupportedRenderingFeatures.active.reflectionProbesBlendDistance))
                {
                    EditorGUILayout.PropertyField(m_BlendDistance, Styles.blendDistanceText);
                }

                // Bounds editing (box projection bounds + the bounds that objects use to check if they should be affected by this reflection probe)
                if (EditorGUILayout.BeginFadeGroup(m_ShowBoxOptions.faded))
                {
                    EditorGUI.BeginChangeCheck();
                    EditorGUILayout.PropertyField(m_BoxSize, Styles.sizeText);
                    EditorGUILayout.PropertyField(m_BoxOffset, Styles.centerText);

                    if (EditorGUI.EndChangeCheck())
                    {
                        Vector3 center = m_BoxOffset.vector3Value;
                        Vector3 size = m_BoxSize.vector3Value;
                        if (ValidateAABB(ref center, ref size))
                        {
                            m_BoxOffset.vector3Value = center;
                            m_BoxSize.vector3Value = size;
                        }
                    }
                }
                EditorGUILayout.EndFadeGroup();

                EditorGUI.indentLevel--;
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            m_ShowCubemapCaptureSettings.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowCubemapCaptureSettings.value, Styles.captureCubemapHeader);

            if (m_ShowCubemapCaptureSettings.value)
            {
                EditorGUI.indentLevel++;

                int[] reflectionResolutionValuesArray = null;
                GUIContent[] reflectionResolutionTextArray = null;
                GetResolutionArray(ref reflectionResolutionValuesArray, ref reflectionResolutionTextArray);

                EditorGUILayout.IntPopup(m_Resolution, reflectionResolutionTextArray, reflectionResolutionValuesArray, Styles.resolutionText, GUILayout.MinWidth(40));
                EditorGUILayout.PropertyField(m_HDR, Styles.hdrText);
                EditorGUILayout.PropertyField(m_ShadowDistance, Styles.shadowDistanceText);
                EditorGUILayout.IntPopup(m_ClearFlags, Styles.clearFlags, Styles.clearFlagsValues, Styles.clearFlagsText);
                EditorGUILayout.PropertyField(m_BackgroundColor, Styles.backgroundColorText);
                EditorGUILayout.PropertyField(m_CullingMask, Styles.cullingMaskText);
                EditorGUILayout.PropertyField(m_UseOcclusionCulling, Styles.useOcclusionCulling);
                EditorGUILayout.PropertiesField(EditorGUI.s_ClipingPlanesLabel, m_NearAndFarProperties, EditorGUI.s_NearAndFarLabels, EditorGUI.kNearFarLabelsWidth);

                EditorGUI.indentLevel--;
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            EditorGUILayout.Space();

            if (targets.Length == 1)
            {
                ReflectionProbe probe = (ReflectionProbe)target;
                if (probe.mode == ReflectionProbeMode.Custom && probe.customBakedTexture != null)
                {
                    Cubemap cubemap = probe.customBakedTexture as Cubemap;
                    if (cubemap && cubemap.mipmapCount == 1)
                        EditorGUILayout.HelpBox("No mipmaps in the cubemap, Smoothness value in Standard shader will be ignored.", MessageType.Warning);
                }
            }

            DoBakeButton();
            EditorGUILayout.Space();
            serializedObject.ApplyModifiedProperties();
        }

        internal override Bounds GetWorldBoundsOfTarget(Object targetObject)
        {
            return ((ReflectionProbe)targetObject).bounds;
        }

        bool ValidPreviewSetup()
        {
            ReflectionProbe p = (ReflectionProbe)target;
            return (p != null && p.texture != null);
        }

        void CreateTextureInspector(Texture texture, ref Editor previousEditor)
        {
            switch (texture)
            {
                case Cubemap _:
                    Editor.CreateCachedEditor(texture, typeof(CubemapInspector), ref previousEditor);
                    break;
                case CustomRenderTexture _:
                    Editor.CreateCachedEditor(texture, typeof(CustomRenderTextureEditor), ref previousEditor);
                    break;
                case RenderTexture _:
                    Editor.CreateCachedEditor(texture, typeof(RenderTextureEditor), ref previousEditor);
                    break;
                default:
                    Editor.CreateCachedEditor(texture, typeof(TextureInspector), ref previousEditor);
                    break;
            }
        }

        public override bool HasPreviewGUI()
        {
            if (targets.Length > 1)
                return false;  // We only handle one preview for reflection probes

            // Ensure valid cube map editor (if possible)
            if (ValidPreviewSetup())
            {
                Editor editor = m_CubemapEditor;
                CreateTextureInspector(((ReflectionProbe)target).texture, ref editor);
                m_CubemapEditor = editor as TextureInspector;
            }

            // If having one probe selected we always want preview (to prevent preview window from popping)
            return true;
        }

        public override void OnPreviewSettings()
        {
            if (!ValidPreviewSetup())
                return;

            m_CubemapEditor.mipLevel = m_MipLevelPreview;

            EditorGUI.BeginChangeCheck();
            m_CubemapEditor.OnPreviewSettings();
            // Need to repaint, because mipmap value changes affect reflection probe preview in the scene
            if (EditorGUI.EndChangeCheck())
            {
                EditorApplication.SetSceneRepaintDirty();
                m_MipLevelPreview = m_CubemapEditor.mipLevel;
            }
        }

        public override void OnPreviewGUI(Rect position, GUIStyle style)
        {
            // Fix for case 939947 where we didn't get the Layout event if the texture was null when changing color
            if (!ValidPreviewSetup() && Event.current.type != EventType.ExecuteCommand)
            {
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                Color prevColor = GUI.color;
                GUI.color = new Color(1, 1, 1, 0.5f);
                GUILayout.Label("Reflection Probe not baked/ready yet");
                GUI.color = prevColor;
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
                return;
            }

            ReflectionProbe p = target as ReflectionProbe;
            if (p != null && p.texture != null && targets.Length == 1)
            {
                Editor editor = m_CubemapEditor;
                CreateTextureInspector(p.texture, ref editor);
                m_CubemapEditor = editor as TextureInspector;
            }

            if (m_CubemapEditor != null)
            {
                m_CubemapEditor.SetCubemapIntensity(GetProbeIntensity((ReflectionProbe)target));
                m_CubemapEditor.OnPreviewGUI(position, style);
            }
        }

        private static Mesh sphereMesh
        {
            get { return s_SphereMesh ?? (s_SphereMesh = Resources.GetBuiltinResource(typeof(Mesh), "New-Sphere.fbx") as Mesh); }
        }

        private Material reflectiveMaterial
        {
            get
            {
                if (m_ReflectiveMaterial == null)
                {
                    m_ReflectiveMaterial = (Material)Instantiate(EditorGUIUtility.Load("Previews/PreviewCubemapMaterial.mat"));
                    m_ReflectiveMaterial.hideFlags = HideFlags.HideAndDontSave;
                }
                return m_ReflectiveMaterial;
            }
        }

        private float GetProbeIntensity(ReflectionProbe p)
        {
            if (p == null || p.texture == null)
                return 1.0f;

            float intensity = p.intensity;
            if (TextureUtil.GetTextureColorSpaceString(p.texture) == "Linear")
                intensity = Mathf.LinearToGammaSpace(intensity);
            return intensity;
        }

        // Draw Reflection probe preview sphere
        private void OnPreSceneGUICallback(SceneView sceneView)
        {
            if (Event.current.type != EventType.Repaint)
                return;

            foreach (var t in targets)
            {
                ReflectionProbe p = (ReflectionProbe)t;
                if (!reflectiveMaterial)
                    return;

                if (StageUtility.IsGizmoCulledBySceneCullingMasksOrFocusedScene(p.gameObject, Camera.current))
                    return;

                Matrix4x4 m = new Matrix4x4();

                // @TODO: use MaterialPropertyBlock instead - once it actually works!
                // Tried to use MaterialPropertyBlock in 5.4.0b2, but would get incorrectly set parameters when using with Graphics.DrawMesh
                if (!m_CachedGizmoMaterials.ContainsKey(p))
                    m_CachedGizmoMaterials.Add(p, Instantiate(reflectiveMaterial));

                Material mat = m_CachedGizmoMaterials[p] as Material;
                if (!mat)
                    return;
                {
                    // Get mip level
                    float mipLevel = 0.0F;
                    TextureInspector cubemapEditor = m_CubemapEditor;
                    if (cubemapEditor)
                        mipLevel = cubemapEditor.GetMipLevelForRendering();

                    mat.SetTexture("_MainTex", p.texture);
                    mat.SetMatrix("_CubemapRotation", Matrix4x4.identity);
                    mat.SetFloat("_Mip", mipLevel);
                    mat.SetFloat("_Alpha", 0.0f);
                    mat.SetFloat("_Intensity", GetProbeIntensity(p));
                    mat.SetVector("_MainTex_HDR", p.textureHDRDecodeValues);

                    if (PlayerSettings.colorSpace == ColorSpace.Linear)
                    {
                        mat.SetInt("_ColorspaceIsGamma", 0);
                    }
                    else
                    {
                        mat.SetInt("_ColorspaceIsGamma", 1);
                    }

                    // draw a preview sphere that scales with overall GO scale, but always uniformly
                    var scale = p.transform.lossyScale.magnitude * 0.5f;
                    m.SetTRS(p.transform.position, Quaternion.identity, new Vector3(scale, scale, scale));
                    Graphics.DrawMesh(sphereMesh, m, mat, 0, SceneView.currentDrawingSceneView.camera, 0);
                }
            }
        }

        // Ensures that probe's AABB encapsulates probe's position
        // Returns true, if center or size was modified
        private bool ValidateAABB(ref Vector3 center, ref Vector3 size)
        {
            ReflectionProbe p = (ReflectionProbe)target;

            Matrix4x4 localSpace = GetLocalSpace(p);
            Vector3 localTransformPosition = localSpace.inverse.MultiplyPoint3x4(p.transform.position);

            Bounds b = new Bounds(center, size);

            if (b.Contains(localTransformPosition)) return false;

            b.Encapsulate(localTransformPosition);

            center = b.center;
            size = b.size;
            return true;
        }

        [DrawGizmo(GizmoType.Active)]
        static void RenderBoxGizmo(ReflectionProbe reflectionProbe, GizmoType gizmoType)
        {
            if (s_LastInteractedEditor == null)
                return;

            if (s_LastInteractedEditor.sceneViewEditing && EditMode.editMode == EditMode.SceneViewEditMode.ReflectionProbeBox)
            {
                Color oldColor = Gizmos.color;
                Gizmos.color = kGizmoReflectionProbe;

                Gizmos.matrix = GetLocalSpace(reflectionProbe);
                Gizmos.DrawCube(reflectionProbe.center, -1f * reflectionProbe.size);
                Gizmos.matrix = Matrix4x4.identity;
                Gizmos.color = oldColor;
            }
        }

        [DrawGizmo(GizmoType.Selected)]
        static void RenderBoxOutline(ReflectionProbe reflectionProbe, GizmoType gizmoType)
        {
            if (!s_CurrentlyEditedProbes.Contains(reflectionProbe))
                return;

            Color oldColor = Gizmos.color;
            Gizmos.color = reflectionProbe.isActiveAndEnabled ? kGizmoReflectionProbe : kGizmoReflectionProbeDisabled;

            Gizmos.matrix = GetLocalSpace(reflectionProbe);
            Gizmos.DrawWireCube(reflectionProbe.center, reflectionProbe.size);
            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.color = oldColor;
        }

        public static bool IsSceneGUIEnabled()
        {
            return IsReflectionProbeEditMode(EditMode.editMode);
        }

        public void OnSceneGUI()
        {
            if (!sceneViewEditing)
                return;

            switch (EditMode.editMode)
            {
                case EditMode.SceneViewEditMode.ReflectionProbeBox:
                    DoBoxEditing();
                    break;
                case EditMode.SceneViewEditMode.ReflectionProbeOrigin:
                    DoOriginEditing();
                    break;
            }
        }

        void UpdateOldLocalSpace()
        {
            m_OldLocalSpace = GetLocalSpace((ReflectionProbe)target);
        }

        void DoOriginEditing()
        {
            ReflectionProbe p = (ReflectionProbe)target;
            Vector3 transformPosition = p.transform.position;
            Vector3 size = p.size;

            EditorGUI.BeginChangeCheck();
            Vector3 newPostion = Handles.PositionHandle(transformPosition, GetLocalSpaceRotation(p));

            bool changed = EditorGUI.EndChangeCheck();

            if (changed || m_OldLocalSpace != GetLocalSpace((ReflectionProbe)target))
            {
                Vector3 localNewPosition = m_OldLocalSpace.inverse.MultiplyPoint3x4(newPostion);

                Bounds b = new Bounds(p.center, size);
                localNewPosition = b.ClosestPoint(localNewPosition);

                Undo.RecordObject(p.transform, "Modified Reflection Probe Origin");
                p.transform.position = m_OldLocalSpace.MultiplyPoint3x4(localNewPosition);

                Undo.RecordObject(p, "Modified Reflection Probe Origin");
                p.center = GetLocalSpace(p).inverse.MultiplyPoint3x4(m_OldLocalSpace.MultiplyPoint3x4(p.center));

                EditorUtility.SetDirty(target);

                UpdateOldLocalSpace();
            }
        }

        static Matrix4x4 GetLocalSpace(ReflectionProbe probe)
        {
            Vector3 t = probe.transform.position;
            return Matrix4x4.TRS(t, GetLocalSpaceRotation(probe), Vector3.one);
        }

        static Quaternion GetLocalSpaceRotation(ReflectionProbe probe)
        {
            bool supportsRotation = (SupportedRenderingFeatures.active.reflectionProbeModes & SupportedRenderingFeatures.ReflectionProbeModes.Rotation) != 0;
            if (supportsRotation)
                return probe.transform.rotation;
            else
                return Quaternion.identity;
        }

        void DoBoxEditing()
        {
            // Drawing of the probe box is done from GizmoDrawers.cpp,
            // here we only want to show the box editing handles when needed.
            ReflectionProbe p = (ReflectionProbe)target;

            using (new Handles.DrawingScope(GetLocalSpace(p)))
            {
                m_BoundsHandle.center = p.center;
                m_BoundsHandle.size = p.size;

                EditorGUI.BeginChangeCheck();
                m_BoundsHandle.DrawHandle();
                if (EditorGUI.EndChangeCheck())
                {
                    Undo.RecordObject(p, "Modified Reflection Probe AABB");
                    Vector3 center = m_BoundsHandle.center;
                    Vector3 size = m_BoundsHandle.size;
                    ValidateAABB(ref center, ref size);
                    p.center = center;
                    p.size = size;
                    EditorUtility.SetDirty(target);
                }
            }
        }
    }
}
