// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.MixedReality.Toolkit.UI;
using Microsoft.MixedReality.Toolkit.Utilities;
using Microsoft.MixedReality.Toolkit.Utilities.Editor;
using UnityEditor;
using UnityEngine;
using PaginationMode = Microsoft.MixedReality.Toolkit.UI.ScrollingObjectCollection.PaginationMode;

namespace Microsoft.MixedReality.Toolkit.Editor
{
    [CustomEditor(typeof(ScrollingObjectCollection))]
    public class ScrollingObjectCollectionInspector : UnityEditor.Editor
    {
        private SerializedProperty cellHeight;
        private SerializedProperty cellWidth;
        private SerializedProperty cellDepth;

        private SerializedProperty canScroll;
        private SerializedProperty scrollDirection;
        private SerializedProperty maskEditMode;
        private SerializedProperty maskEnabled;
        private SerializedProperty colliderEditMode;
        private SerializedProperty tiersPerPage;
        private SerializedProperty cellsPerTier;
        private SerializedProperty useCameraPreRender;

        private SerializedProperty velocityType;
        private SerializedProperty velocityMultiplier;
        private SerializedProperty velocityDampen;
        private SerializedProperty bounceMultiplier;
        private SerializedProperty animationCurve;
        private SerializedProperty animationLength;

        private SerializedProperty disableClippedGameObjects;
        private SerializedProperty disableClippedRenderers;

        private SerializedProperty clickEvent;
        private SerializedProperty touchStartedEvent;
        private SerializedProperty touchEndedEvent;
        private SerializedProperty momentumStartedEvent;
        private SerializedProperty momentumEndedEvent;

        private SerializedProperty handDeltaScrollThreshold;
        private SerializedProperty frontTouchDistance;

        private SerializedProperty releaseThresholdFront;
        private SerializedProperty releaseThresholdBack;
        private SerializedProperty releaseThresholdLeftRight;
        private SerializedProperty releaseThresholdTopBottom;

        private Shader MRTKtmp;

        private ScrollingObjectCollection scrollView;

        private static bool visibleDebugPlanes = false;

        private Color touchPlaneColor = Color.cyan;
        private Color releasePlaneColor = Color.magenta;
        private static GUIStyle labelStyle;
        private Vector3 mouseScreenPosition;
        private float smallestMouseHandleDistance;

        private const string TouchPlaneDescription = "Touch plane";
        private const string LeftReleasePlaneDescription = "Left Release Plane";
        private const string RightReleasePlaneDescription = "Right Release Plane";
        private const string TopReleasePlaneDescription = "Top release Plane";
        private const string BottomReleasePlaneDescription = "Bottom Release Plane";
        private const string BackReleasePlaneDescription = "Back release Plane";
        private const string FrontReleasePlaneDescription = "Front Release Plane";

        private const string ScrollViewDocURL = "https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/ux-building-blocks/scrolling-object-collection";

        protected const string ShowAdvancedPrefKey = "ScrollViewInspectorShowAdvanced";
        protected const string ShowEventsPrefKey = "ScrollViewInspectorShowEvents";
        protected const string ShowPerformanceOptionsPrefKey = "ScrollViewInspectorShowPerformanceOptions";
        protected const string ShowDebugOptionsPrefKey = "ScrollViewInspectorShowDebugOptions";

        private bool ShowDebugPagination;
        private PaginationMode debugPaginationMode;

        private bool animateTransition = true;
        private int paginationMoveNumber = 1;

        private void OnEnable()
        {
            cellHeight = serializedObject.FindProperty("cellHeight");
            cellWidth = serializedObject.FindProperty("cellWidth");
            cellDepth = serializedObject.FindProperty("cellDepth");

            cellsPerTier = serializedObject.FindProperty("cellsPerTier");
            canScroll = serializedObject.FindProperty("canScroll");
            scrollDirection = serializedObject.FindProperty("scrollDirection");
            maskEditMode = serializedObject.FindProperty("maskEditMode");
            maskEnabled = serializedObject.FindProperty("maskEnabled");
            colliderEditMode = serializedObject.FindProperty("colliderEditMode");
            tiersPerPage = serializedObject.FindProperty("tiersPerPage");
            useCameraPreRender = serializedObject.FindProperty("useOnPreRender");

            handDeltaScrollThreshold = serializedObject.FindProperty("handDeltaScrollThreshold");

            velocityType = serializedObject.FindProperty("typeOfVelocity");
            velocityMultiplier = serializedObject.FindProperty("velocityMultiplier");
            velocityDampen = serializedObject.FindProperty("velocityDampen");
            bounceMultiplier = serializedObject.FindProperty("bounceMultiplier");
            animationCurve = serializedObject.FindProperty("paginationCurve");
            animationLength = serializedObject.FindProperty("animationLength");

            disableClippedGameObjects = serializedObject.FindProperty("disableClippedGameObjects");
            disableClippedRenderers = serializedObject.FindProperty("disableClippedRenderers");

            clickEvent = serializedObject.FindProperty("OnClick");
            touchStartedEvent = serializedObject.FindProperty("OnTouchStarted");
            touchEndedEvent = serializedObject.FindProperty("OnTouchEnded");
            momentumStartedEvent = serializedObject.FindProperty("OnMomentumStarted");
            momentumEndedEvent = serializedObject.FindProperty("OnMomentumEnded");

            // Serialized properties for visualization
            releaseThresholdFront = serializedObject.FindProperty("releaseThresholdFront");
            releaseThresholdBack = serializedObject.FindProperty("releaseThresholdBack");
            releaseThresholdLeftRight = serializedObject.FindProperty("releaseThresholdLeftRight");
            releaseThresholdTopBottom = serializedObject.FindProperty("releaseThresholdTopBottom");
            frontTouchDistance = serializedObject.FindProperty("frontTouchDistance");

            scrollView = (ScrollingObjectCollection)target;
            MRTKtmp = Shader.Find("Mixed Reality Toolkit/TextMeshPro");

            if (labelStyle == null)
            {
                labelStyle = new GUIStyle();
                labelStyle.normal.textColor = Color.white;
            }
        }

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

            using (var check = new EditorGUI.ChangeCheckScope())
            {
                EditorGUILayout.Space();

                DrawGeneralSection();
                DrawPaginationSection();
                DrawAdvancedSettingsSection();
                DrawEventsSection();

                serializedObject.ApplyModifiedProperties();

                if (check.changed)
                {
                    scrollView.UpdateContent();

                    foreach (var renderer in scrollView.GetComponentsInChildren<Renderer>(true))
                    {
                        if (renderer.sharedMaterial == null)
                        {
                            continue;
                        }

                        if (!CheckForStandardShader(renderer))
                        {
                            Debug.LogWarning(renderer.name + " has a renderer that is not using " + StandardShaderUtility.MrtkStandardShaderName + ". This will result in unexpected results with ScrollingObjectCollection.");
                        }
                    }
                }
            }
        }

        private void DrawEventsSection()
        {
            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Events", ShowEventsPrefKey, MixedRealityStylesUtility.BoldFoldoutStyle))
            {
                using (new EditorGUI.IndentLevelScope())
                {
                    EditorGUILayout.PropertyField(clickEvent);
                    EditorGUILayout.PropertyField(touchStartedEvent);
                    EditorGUILayout.PropertyField(touchEndedEvent);
                    EditorGUILayout.PropertyField(momentumStartedEvent);
                    EditorGUILayout.PropertyField(momentumEndedEvent);
                    EditorGUILayout.Space();
                }
            }
        }

        private void DrawPaginationSection()
        {
            EditorGUILayout.LabelField("Pagination", EditorStyles.boldLabel);

            using (new EditorGUI.IndentLevelScope())
            {
                EditorGUILayout.PropertyField(cellsPerTier);
                EditorGUILayout.PropertyField(tiersPerPage);

                // Draw cell dimension fields
                Rect rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
                EditorGUI.PrefixLabel(rect, new GUIContent("Page Cell"));

                int oldIndent = EditorGUI.indentLevel;
                EditorGUI.indentLevel = 0;

                EditorGUIUtility.labelWidth = 0;
                rect.x += EditorGUIUtility.labelWidth;
                rect.width = (rect.width - EditorGUIUtility.labelWidth - 5f) / 3f;
                EditorGUIUtility.labelWidth = Mathf.Min(rect.width * 0.39f, 80f);

                EditorGUI.PropertyField(rect, cellWidth, new GUIContent("Width"));
                rect.x += rect.width + 2f;
                EditorGUI.PropertyField(rect, cellHeight, new GUIContent("Height"));

                rect.x += rect.width + 2f;
                EditorGUI.PropertyField(rect, cellDepth, new GUIContent("Depth"));

                // Reseting layout
                EditorGUIUtility.labelWidth = 0;
                EditorGUI.indentLevel = oldIndent;
                EditorGUILayout.Space();
            }
        }

        private void DrawGeneralSection()
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
                InspectorUIUtility.RenderDocumentationButton(ScrollViewDocURL);
            }
            using (new EditorGUI.IndentLevelScope())
            {
                EditorGUILayout.PropertyField(scrollDirection);
                EditorGUILayout.Space();
            }
        }

        private void DrawAdvancedSettingsSection()
        {
            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Advanced settings", ShowAdvancedPrefKey, MixedRealityStylesUtility.BoldFoldoutStyle))
            {
                using (new EditorGUI.IndentLevelScope(2))
                {
                    EditorGUILayout.PropertyField(maskEditMode);
                    EditorGUILayout.PropertyField(colliderEditMode);
                    EditorGUILayout.Space();

                    EditorGUILayout.PropertyField(canScroll);
                    EditorGUILayout.Space();

                    EditorGUILayout.PropertyField(useCameraPreRender);
                    EditorGUILayout.Space();

                    EditorGUILayout.PropertyField(animationCurve);
                    EditorGUILayout.PropertyField(animationLength);
                    EditorGUILayout.Space();

                    EditorGUILayout.PropertyField(handDeltaScrollThreshold);
                    EditorGUILayout.PropertyField(frontTouchDistance);
                    EditorGUILayout.Space();

                    DrawTouchReleaseThresholdsSection();
                    EditorGUILayout.Space();

                    DrawVelocitySection();
                    EditorGUILayout.Space();

                    DrawPeformanceSection();
                    EditorGUILayout.Space();

                    DrawDebugSection();
                    EditorGUILayout.Space();
                }
            }
        }

        private void DrawDebugSection()
        {
            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Debug Options", ShowDebugOptionsPrefKey, MixedRealityStylesUtility.BoldFoldoutStyle))
            {
                using (new EditorGUI.IndentLevelScope())
                {
                    using (var check = new EditorGUI.ChangeCheckScope())
                    {
                        EditorGUILayout.PropertyField(maskEnabled);
                        if (check.changed)
                        {
                            scrollView.MaskEnabled = maskEnabled.boolValue;
                            EditorUtility.SetDirty(target);
                        }
                    }

                    using (new EditorGUI.DisabledGroupScope(EditorApplication.isPlaying))
                    {
                        visibleDebugPlanes = EditorGUILayout.Toggle("Show Threshold Planes", visibleDebugPlanes);
                        EditorGUILayout.Space();
                    }
                }

                using (new EditorGUI.IndentLevelScope())
                {
                    using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlaying))
                    {
                        if (ShowDebugPagination = EditorGUILayout.Foldout(ShowDebugPagination, new GUIContent("Debug Pagination", "Pagination is only available during playmode."), MixedRealityStylesUtility.BoldFoldoutStyle))
                        {
                            using (new EditorGUI.IndentLevelScope())
                            {
                                animateTransition = EditorGUILayout.Toggle(new GUIContent("Animate", "Toggling will use animation to move scroller to new position."), animateTransition);

                                using (new EditorGUILayout.HorizontalScope())
                                {
                                    debugPaginationMode = (PaginationMode)EditorGUILayout.EnumPopup(new GUIContent("Pagination Mode"), debugPaginationMode, GUILayout.Width(400.0f));
                                    paginationMoveNumber = EditorGUILayout.IntField(paginationMoveNumber);

                                    if (GUILayout.Button("Move"))
                                    {
                                        switch (debugPaginationMode)
                                        {
                                            case PaginationMode.ByTier:
                                            default:
                                                scrollView.MoveByTiers(paginationMoveNumber, animateTransition);
                                                break;
                                            case PaginationMode.ByPage:
                                                scrollView.MoveByPages(paginationMoveNumber, animateTransition);
                                                break;
                                            case PaginationMode.ToCellIndex:
                                                scrollView.MoveToIndex(paginationMoveNumber, animateTransition);
                                                break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void DrawVelocitySection()
        {
            EditorGUILayout.LabelField("Velocity", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(velocityType);

            if (velocityType.enumValueIndex <= 1)
            {
                EditorGUILayout.PropertyField(velocityMultiplier);
                EditorGUILayout.PropertyField(velocityDampen);
                EditorGUILayout.PropertyField(bounceMultiplier);
            }
        }

        private void DrawPeformanceSection()
        {
            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Performance Options", ShowPerformanceOptionsPrefKey, MixedRealityStylesUtility.BoldFoldoutStyle))
            {
                EditorGUILayout.PropertyField(disableClippedGameObjects);
                EditorGUILayout.PropertyField(disableClippedRenderers);
            }
        }

        private void DrawTouchReleaseThresholdsSection()
        {
            Rect rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
            EditorGUI.PrefixLabel(rect, new GUIContent("Release threshold", "Withdraw amount, in meters, from the scroll view boundaries that triggers a touch release."), EditorStyles.boldLabel);

            int oldIndent = EditorGUI.indentLevel;
            EditorGUI.indentLevel = 0;

            rect.x += EditorGUIUtility.labelWidth;
            rect.width = (rect.width - EditorGUIUtility.labelWidth - 3f) / 2f;
            EditorGUIUtility.labelWidth = Mathf.Min(rect.width * 0.55f, 80f);

            EditorGUI.PropertyField(rect, releaseThresholdFront, new GUIContent("Front"));
            rect.x += rect.width + 3f;
            EditorGUI.PropertyField(rect, releaseThresholdLeftRight, new GUIContent("Left-Right"));
            rect.x += rect.width + 3f;

            rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight);
            EditorGUIUtility.labelWidth = 0;
            rect.x += EditorGUIUtility.labelWidth;
            rect.width = (rect.width - EditorGUIUtility.labelWidth - 3f) / 2f;
            EditorGUIUtility.labelWidth = Mathf.Min(rect.width * 0.55f, 80f);

            EditorGUI.PropertyField(rect, releaseThresholdBack, new GUIContent("Back"));
            rect.x += rect.width + 3f;
            EditorGUI.PropertyField(rect, releaseThresholdTopBottom, new GUIContent("Top-Bottom"));

            // Reseting layout
            EditorGUIUtility.labelWidth = 0;
            EditorGUI.indentLevel = oldIndent;
        }

        [DrawGizmo(GizmoType.Selected)]
        private void OnSceneGUI()
        {
            if (Event.current.type == EventType.Repaint)
            {
                if (visibleDebugPlanes)
                {
                    GetMouseViewportPosition();

                    smallestMouseHandleDistance = float.PositiveInfinity;

                    DrawAllDebugPlanes();
                }
            }
        }

        /// <summary>
        /// Draws the touch plane used for scroll engage detection.
        /// </summary>
        private void DrawTouchPlane()
        {
            Vector3 planeLocalPosition = Vector3.forward * frontTouchDistance.floatValue * -1.0f;
            Vector3 widthHalfExtent = scrollView.transform.right * scrollView.ClippingObject.transform.lossyScale.x / 2;
            Vector3 heightHalfExtent = scrollView.transform.up * scrollView.ClippingObject.transform.lossyScale.y / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.forward * -1.0f);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, touchPlaneColor, TouchPlaneDescription);
        }

        /// <summary>
        /// Draws the front release plane used for scroll engage release detection.
        /// </summary>
        private void DrawFrontReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.forward * releaseThresholdFront.floatValue * -1.0f;
            Vector3 widthHalfExtent = scrollView.transform.right * scrollView.ClippingObject.transform.lossyScale.x / 2;
            Vector3 heightHalfExtent = scrollView.transform.up * scrollView.ClippingObject.transform.lossyScale.y / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.forward);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, FrontReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the back release plane used for scroll engage release detection.
        /// </summary>
        private void DrawBackReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.forward * releaseThresholdBack.floatValue;
            Vector3 widthHalfExtent = scrollView.transform.right * scrollView.ClippingObject.transform.lossyScale.x / 2;
            Vector3 heightHalfExtent = scrollView.transform.up * scrollView.ClippingObject.transform.lossyScale.y / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.forward * -1.0f);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, BackReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the right release plane used for scroll engage release detection.
        /// </summary>
        private void DrawRightReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.right * releaseThresholdLeftRight.floatValue;
            Vector3 widthHalfExtent = scrollView.transform.forward * scrollView.ClippingObject.transform.lossyScale.z / 2;
            Vector3 heightHalfExtent = scrollView.transform.up * scrollView.ClippingObject.transform.lossyScale.y / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.right * -1.0f);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, RightReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the left release plane used for scroll engage release detection.
        /// </summary>
        private void DrawLeftReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.right * releaseThresholdLeftRight.floatValue * -1.0f;
            Vector3 widthHalfExtent = scrollView.transform.forward * scrollView.ClippingObject.transform.lossyScale.z / 2;
            Vector3 heightHalfExtent = scrollView.transform.up * scrollView.ClippingObject.transform.lossyScale.y / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.right);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, LeftReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the top release plane used for scroll engage release detection.
        /// </summary>
        private void DrawTopReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.up * releaseThresholdTopBottom.floatValue;
            Vector3 widthHalfExtent = scrollView.transform.right * scrollView.ClippingObject.transform.lossyScale.x / 2;
            Vector3 heightHalfExtent = scrollView.transform.forward * scrollView.ClippingObject.transform.lossyScale.z / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.up * -1.0f);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, TopReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the top release plane used for scroll engage release detection.
        /// </summary>
        private void DrawBottomReleasePlane()
        {
            Vector3 planeLocalPosition = Vector3.up * releaseThresholdTopBottom.floatValue * -1.0f;
            Vector3 widthHalfExtent = scrollView.transform.right * scrollView.ClippingObject.transform.lossyScale.x / 2;
            Vector3 heightHalfExtent = scrollView.transform.forward * scrollView.ClippingObject.transform.lossyScale.z / 2;

            Quaternion handleRotation = Quaternion.LookRotation(scrollView.transform.up);

            DrawPlaneAndHandle(planeLocalPosition, widthHalfExtent, heightHalfExtent, handleRotation, releasePlaneColor, BottomReleasePlaneDescription);
        }

        /// <summary>
        /// Draws the scroll interaction debug planes.
        /// </summary>
        private void DrawPlaneAndHandle(Vector3 planeLocalPosition, Vector3 widthHalfExtent, Vector3 hightHalfExtent, Quaternion handleRotation, Color color, string labelText)
        {
            Vector3 planePosition = scrollView.ClippingObject.transform.position + scrollView.ClippingObject.transform.TransformDirection(planeLocalPosition);

            Vector3[] points = new Vector3[4];
            points[0] = planePosition - widthHalfExtent + hightHalfExtent;
            points[1] = planePosition + widthHalfExtent + hightHalfExtent;
            points[2] = planePosition + widthHalfExtent - hightHalfExtent;
            points[3] = planePosition - widthHalfExtent - hightHalfExtent;

            float handleSize = HandleUtility.GetHandleSize(planePosition) * 0.30f;

            // Draw handle and plane
            Handles.color = color;
            Handles.ArrowHandleCap(0, planePosition, handleRotation, handleSize, EventType.Repaint);
            Handles.DrawSolidRectangleWithOutline(points, Color.Lerp(color, Color.clear, 0.5f), color);

            // Draw label if handle has smallest vieport distance from the mouse pointer
            Vector3 handleViewportPosition = SceneView.currentDrawingSceneView.camera.WorldToScreenPoint(planePosition);
            handleViewportPosition = SceneView.currentDrawingSceneView.camera.ScreenToViewportPoint(handleViewportPosition);

            float mouseHandleDistance = Vector2.Distance(mouseScreenPosition, handleViewportPosition);

            // Check if mouse is hovering circle centered on plane center. Keep circle ratio as a multiple of the current unity handle size.
            if (mouseHandleDistance < handleSize * 10.0f && mouseHandleDistance < smallestMouseHandleDistance)
            {
                smallestMouseHandleDistance = mouseHandleDistance;

                Handles.Label(planePosition + (Vector3.up * handleSize * 3.0f), new GUIContent(labelText), labelStyle);
                Handles.DrawDottedLine(planePosition + (Vector3.up * handleSize * 2.0f) + (Vector3.right * handleSize), planePosition, 5f);
            }
        }

        private void DrawAllDebugPlanes()
        {
            DrawTouchPlane();
            DrawFrontReleasePlane();
            DrawBackReleasePlane();
            DrawRightReleasePlane();
            DrawLeftReleasePlane();
            DrawTopReleasePlane();
            DrawBottomReleasePlane();
        }

        /// <summary>
        /// Simple check for the use of MRTK standard shader.
        /// </summary>
        /// <param name="rends"><see cref="Renderer[]"/> to check for the MRTK standard shader.</param>
        /// <returns>true when render is using the MRTK standard shader.</returns>
        private bool CheckForStandardShader(Renderer renderer)
        {
            return StandardShaderUtility.IsUsingMrtkStandardShader(renderer.sharedMaterial) || renderer.sharedMaterial.shader == MRTKtmp;
        }

        private void GetMouseViewportPosition()
        {
            mouseScreenPosition = HandleUtility.GUIPointToScreenPixelCoordinate(Event.current.mousePosition);
            mouseScreenPosition = SceneView.currentDrawingSceneView.camera.ScreenToViewportPoint(mouseScreenPosition);
        }
    }
}