using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace UnityEditor.U2D.Common.Path
{
    [CanEditMultipleObjects]
    [CustomEditor(typeof(ScriptablePath), true)]
    internal class ScriptablePathInspector : Editor
    {
        private static class Contents
        {
            public static readonly GUIContent linearIcon = IconContent("TangentLinear", "TangentLinearPro", "Linear");
            public static readonly GUIContent continuousIcon = IconContent("TangentContinuous", "TangentContinuousPro", "Continuous");
            public static readonly GUIContent brokenIcon = IconContent("TangentBroken", "TangentBrokenPro", "Broken");
            public static readonly GUIContent positionLabel = new GUIContent("Position", "Position of the Control Point");
            public static readonly GUIContent enableSnapLabel = new GUIContent("Snapping", "Snap points using the snap settings");
            public static readonly GUIContent tangentModeLabel = new GUIContent("Tangent Mode");
            public static readonly GUIContent pointLabel = new GUIContent("Point");


            private static GUIContent IconContent(string name, string tooltip = null)
            {
                return new GUIContent(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.common/Path/Editor/Handles/" + name + ".png"), tooltip);
            }

            private static GUIContent IconContent(string personal, string pro, string tooltip)
            {
                if (EditorGUIUtility.isProSkin)
                    return IconContent(pro, tooltip);
                
                return IconContent(personal, tooltip);
            }
        }

        private List<ScriptablePath> m_Paths = null;
        private bool m_Dragged = false;

        protected List<ScriptablePath> paths
        {
            get
            {
                if (m_Paths == null)
                    m_Paths = targets.Select( t => t as ScriptablePath).ToList();
                
                return m_Paths;
            }
        }

        public override void OnInspectorGUI()
        {
            var lwidth = EditorGUIUtility.labelWidth;
            EditorGUIUtility.labelWidth = 96;
            DoTangentModeInspector();
            DoPositionInspector();
            EditorGUIUtility.labelWidth = lwidth;
        }

        protected void DoTangentModeInspector()
        {
            if (!IsAnyShapeType(ShapeType.Spline))
                return;
            
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.PrefixLabel(Contents.tangentModeLabel);

            using (new EditorGUI.DisabledGroupScope(!IsAnyPointSelected()))
            {
                if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Linear), Contents.linearIcon))
                    SetMixedTangentMode(TangentMode.Linear);

                if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Continuous), Contents.continuousIcon))
                    SetMixedTangentMode(TangentMode.Continuous);

                if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Broken), Contents.brokenIcon))
                    SetMixedTangentMode(TangentMode.Broken);
            }

            EditorGUILayout.EndHorizontal();
        }

        protected void DoPositionInspector()
        {
            var showMixedValue = EditorGUI.showMixedValue;
            var wideMode = EditorGUIUtility.wideMode;

            var position = Vector3.zero;
            var isMixed = GetMixedPosition(out position);

            EditorGUI.showMixedValue = isMixed;
            EditorGUIUtility.wideMode = true;

            using (new EditorGUI.DisabledGroupScope(!IsAnyPointSelected()))
            {
                if (GUIUtility.hotControl == 0)
                    m_Dragged = false;

                EditorGUI.BeginChangeCheck();
                Rect rect = EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector2, Contents.positionLabel), EditorStyles.numberField);
                rect.width += 80;
                var delta = EditorGUI.Vector2Field(rect, Contents.positionLabel, position) - (Vector2)position;

                if (EditorGUI.EndChangeCheck())
                {
                    if (m_Dragged == false)
                    {
                        foreach(var path in paths)
                            path.undoObject.RegisterUndo("Point Position");
                        
                        m_Dragged = true;
                    }

                    SetMixedDeltaPosition(delta);
                }
            }

            EditorGUI.showMixedValue = showMixedValue;
            EditorGUIUtility.wideMode = wideMode;
        }

        private bool DoToggle(bool value, GUIContent icon)
        {
            const float kButtonWidth = 33f;
            const float kButtonHeight = 23f;
            var buttonStyle = new GUIStyle("EditModeSingleButton");

            var changed = false;
            using (var check = new EditorGUI.ChangeCheckScope())
            {
                value = GUILayout.Toggle(value, icon, buttonStyle, GUILayout.Width(kButtonWidth), GUILayout.Height(kButtonHeight));
                changed = check.changed;
            }
            
            return value && changed;
        }

        private bool GetToggleStateFromTangentMode(TangentMode mode)
        {
            foreach(var path in paths)
            {
                var selection = path.selection;

                foreach (var index in selection.elements)
                    if (path.GetPoint(index).tangentMode != mode)
                        return false;
            }
            
            return true;
        }

        private void SetMixedTangentMode(TangentMode tangentMode)
        {
            foreach(var path in paths)
            {
                path.undoObject.RegisterUndo("Tangent Mode");

                foreach (var index in path.selection.elements)
                    path.SetTangentMode(index, tangentMode);
            }

            SceneView.RepaintAll();
        }

        private bool GetMixedPosition(out Vector3 position)
        {
            var first = true;
            position = Vector3.zero;
            
            var activeObject = Selection.activeObject as GameObject;
            if (Selection.count > 1 || !activeObject)
                return true;

            foreach(var path in paths)
            {
                MonoBehaviour behavior = path.owner as MonoBehaviour;
                if (!behavior || activeObject != behavior.gameObject)
                    continue;
                var selection = path.selection;
                
                foreach (var index in selection.elements)
                {
                    var controlPoint = path.GetPointLocal(index);

                    if (first)
                    {
                        position  = controlPoint.position;
                        first = false;
                    }
                    else if (position != controlPoint.position)
                    {
                        return true;
                    }
                }
            }
            
            return false;
        }

        private void SetMixedDeltaPosition(Vector3 delta)
        {
            foreach(var path in paths)
            {
                var selection = path.selection;
                var matrix = path.localToWorldMatrix;

                path.localToWorldMatrix = Matrix4x4.identity;

                foreach (var index in selection.elements)
                {
                    var controlPoint = path.GetPoint(index);
                    controlPoint.position += delta;
                    path.SetPoint(index, controlPoint);
                }

                path.localToWorldMatrix = matrix;
            }
        }

        private bool IsAnyShapeType(ShapeType shapeType)
        {
            foreach(var path in paths)
                if (path.shapeType == shapeType)
                    return true;

            return false;
        }

        protected bool IsAnyPointSelected()
        {
            foreach(var path in paths)
                if (path.selection.Count > 0)
                    return true;

            return false;
        }
    }
}