using UnityEditor;
using UnityEditor.AI;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Unity.AI.Navigation.Editor
{
    [InitializeOnLoad]
    [EditorWindowTitle(title = "Navigation")]
    internal class NavigationWindow : EditorWindow
    {
        static NavigationWindow s_NavigationWindow;

        // Scene based bake configuration
        SerializedObject m_SettingsObject;

        // Project based configuration
        SerializedObject m_NavMeshProjectSettingsObject;
        SerializedProperty m_Areas;

        SerializedProperty m_AgentTypes;
        SerializedProperty m_SettingNames;

        Vector2 m_ScrollPos = Vector2.zero;
        bool m_Advanced;

        ReorderableList m_AreasList;
        ReorderableList m_AgentTypeList;

        enum Mode
        {
            AgentTypeSettings = 0,
            AreaSettings = 1,
        }

        Mode m_Mode = Mode.AgentTypeSettings;

        static class Styles
        {
            internal static readonly GUIContent k_AgentTypesHeader = EditorGUIUtility.TrTextContent("Agent Types");
            internal static readonly GUIContent k_NameLabel = EditorGUIUtility.TrTextContent("Name");
            internal static readonly GUIContent k_CostLabel = EditorGUIUtility.TrTextContent("Cost");
            internal static readonly GUIContent[] k_ModeToggles =
            {
                EditorGUIUtility.TrTextContent("Agents", "Navmesh agent settings."),
                EditorGUIUtility.TrTextContent("Areas", "Navmesh area settings."),
            };
            internal static readonly GUIStyle k_ContentMargins = new GUIStyle
            {
                padding = new RectOffset(4, 4, 4, 4)
            };
        };

        static NavigationWindow()
        {
            NavMeshEditorHelpers.areaSettingsClicked += OpenAreaSettings;
            NavMeshEditorHelpers.agentTypeSettingsClicked += OpenAgentSettings;
        }

        [MenuItem("Window/AI/Navigation", false, 1)]
        public static void SetupWindow()
        {
            var window = GetWindow<NavigationWindow>();
            window.minSize = new Vector2(300, 360);
        }

        static void OpenAreaSettings()
        {
            SetupWindow();
            if (s_NavigationWindow == null)
                return;

            s_NavigationWindow.m_Mode = Mode.AreaSettings;
            s_NavigationWindow.InitProjectSettings();
            s_NavigationWindow.InitAgentTypes();
        }

        static void OpenAgentSettings(int agentTypeID)
        {
            SetupWindow();
            if (s_NavigationWindow == null)
                return;

            s_NavigationWindow.m_Mode = Mode.AgentTypeSettings;
            s_NavigationWindow.InitProjectSettings();
            s_NavigationWindow.InitAgentTypes();

            s_NavigationWindow.m_AgentTypeList.index = -1;
            for (int i = 0; i < s_NavigationWindow.m_AgentTypes.arraySize; i++)
            {
                SerializedProperty agentType = s_NavigationWindow.m_AgentTypes.GetArrayElementAtIndex(i);
                SerializedProperty idProp = agentType.FindPropertyRelative("agentTypeID");
                if (idProp.intValue == agentTypeID)
                {
                    s_NavigationWindow.m_AgentTypeList.index = i;
                    break;
                }
            }
        }

        public void OnEnable()
        {
            var iconPath = $"{NavMeshComponentsGUIUtility.k_PackageEditorResourcesFolder}NavigationWindowIcon.asset";
            titleContent = EditorGUIUtility.TrTextContentWithIcon("Navigation", iconPath);
            s_NavigationWindow = this;
            EditorApplication.searchChanged += Repaint;

            Repaint();
        }

        void InitProjectSettings()
        {
            if (m_NavMeshProjectSettingsObject == null)
            {
                Object obj = Unsupported.GetSerializedAssetInterfaceSingleton("NavMeshProjectSettings");
                m_NavMeshProjectSettingsObject = new SerializedObject(obj);
            }
        }

        void InitAreas()
        {
            if (m_Areas == null)
            {
                m_Areas = m_NavMeshProjectSettingsObject.FindProperty("areas");
            }

            if (m_AreasList == null)
            {
                m_AreasList = new ReorderableList(m_NavMeshProjectSettingsObject, m_Areas, false, true, false, false);
                m_AreasList.drawElementCallback = DrawAreaListElement;
                m_AreasList.drawHeaderCallback = DrawAreaListHeader;
                m_AreasList.elementHeight =
                    EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
            }
        }

        void InitAgentTypes()
        {
            if (m_AgentTypes == null)
            {
                m_AgentTypes = m_NavMeshProjectSettingsObject.FindProperty("m_Settings");
                m_SettingNames = m_NavMeshProjectSettingsObject.FindProperty("m_SettingNames");
            }

            if (m_AgentTypeList == null)
            {
                m_AgentTypeList =
                    new ReorderableList(m_NavMeshProjectSettingsObject, m_AgentTypes, false, true, true, true);
                m_AgentTypeList.drawElementCallback = DrawAgentTypeListElement;
                m_AgentTypeList.drawHeaderCallback = DrawAgentTypeListHeader;
                m_AgentTypeList.onAddCallback = AddAgentType;
                m_AgentTypeList.onRemoveCallback = RemoveAgentType;
                m_AgentTypeList.elementHeight =
                    EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
            }
        }

        // This code is replicated from the NavMesh debug draw code.
        int Bit(int a, int b)
        {
            return (a & (1 << b)) >> b;
        }

        Color GetAreaColor(int i)
        {
            if (i == 0)
                return new Color(0, 0.75f, 1.0f, 0.5f);
            int r = (Bit(i, 4) + Bit(i, 1) * 2 + 1) * 63;
            int g = (Bit(i, 3) + Bit(i, 2) * 2 + 1) * 63;
            int b = (Bit(i, 5) + Bit(i, 0) * 2 + 1) * 63;
            return new Color(r / 255.0f, g / 255.0f, b / 255.0f, 0.5f);
        }

        public void OnDisable()
        {
            s_NavigationWindow = null;
            EditorApplication.searchChanged -= Repaint;
        }

        void OnSelectionChange()
        {
            m_ScrollPos = Vector2.zero;
        }

        void ModeToggle()
        {
            EditorGUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            m_Mode = (Mode)GUILayout.Toolbar((int)m_Mode, Styles.k_ModeToggles, "LargeButton",
                GUI.ToolbarButtonSize.FitToContents);
            GUILayout.FlexibleSpace();
            EditorGUILayout.EndHorizontal();
        }

        static void GetAreaListRects(Rect rect, out Rect stripeRect, out Rect labelRect, out Rect nameRect,
            out Rect costRect)
        {
            float stripeWidth = EditorGUIUtility.singleLineHeight * 0.8f;
            float labelWidth = EditorGUIUtility.singleLineHeight * 5;
            float costWidth = EditorGUIUtility.singleLineHeight * 4;
            float nameWidth = rect.width - stripeWidth - labelWidth - costWidth;
            float x = rect.x;
            stripeRect = new Rect(x, rect.y, stripeWidth - 4, rect.height);
            x += stripeWidth;
            labelRect = new Rect(x, rect.y, labelWidth - 4, rect.height);
            x += labelWidth;
            nameRect = new Rect(x, rect.y, nameWidth - 4, rect.height);
            x += nameWidth;
            costRect = new Rect(x, rect.y, costWidth, rect.height);
        }

        void DrawAreaListHeader(Rect rect)
        {
            GetAreaListRects(rect, out _, out _, out Rect nameRect, out Rect costRect);
            GUI.Label(nameRect, Styles.k_NameLabel);
            GUI.Label(costRect, Styles.k_CostLabel);
        }

        void DrawAreaListElement(Rect rect, int index, bool selected, bool focused)
        {
            SerializedProperty areaProp = m_Areas.GetArrayElementAtIndex(index);
            if (areaProp == null)
                return;
            SerializedProperty nameProp = areaProp.FindPropertyRelative("name");
            SerializedProperty costProp = areaProp.FindPropertyRelative("cost");
            if (nameProp == null || costProp == null)
                return;

            rect.height -= 2; // nicer looking with selected list row and a text field in it

            bool builtInLayer;
            bool allowChangeName;
            bool allowChangeCost;
            switch (index)
            {
                case 0: // Default
                    builtInLayer = true;
                    allowChangeName = false;
                    allowChangeCost = true;
                    break;
                case 1: // NonWalkable
                    builtInLayer = true;
                    allowChangeName = false;
                    allowChangeCost = false;
                    break;
                case 2: // Jump
                    builtInLayer = true;
                    allowChangeName = false;
                    allowChangeCost = true;
                    break;
                default:
                    builtInLayer = false;
                    allowChangeName = true;
                    allowChangeCost = true;
                    break;
            }

            GetAreaListRects(rect, out Rect stripeRect, out Rect labelRect, out Rect nameRect, out Rect costRect);

            bool oldEnabled = GUI.enabled;
            Color color = GetAreaColor(index);
            Color dimmed = new Color(color.r * 0.1f, color.g * 0.1f, color.b * 0.1f, 0.6f);
            EditorGUI.DrawRect(stripeRect, color);

            EditorGUI.DrawRect(new Rect(stripeRect.x, stripeRect.y, 1, stripeRect.height), dimmed);
            EditorGUI.DrawRect(new Rect(stripeRect.x + stripeRect.width - 1, stripeRect.y, 1, stripeRect.height),
                dimmed);
            EditorGUI.DrawRect(new Rect(stripeRect.x + 1, stripeRect.y, stripeRect.width - 2, 1), dimmed);
            EditorGUI.DrawRect(
                new Rect(stripeRect.x + 1, stripeRect.y + stripeRect.height - 1, stripeRect.width - 2, 1), dimmed);

            if (builtInLayer)
                GUI.Label(labelRect, EditorGUIUtility.TrTempContent("Built-in " + index));
            else
                GUI.Label(labelRect, EditorGUIUtility.TrTempContent("User " + index));

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

            GUI.enabled = oldEnabled && allowChangeName;
            EditorGUI.PropertyField(nameRect, nameProp, GUIContent.none);

            GUI.enabled = oldEnabled && allowChangeCost;
            EditorGUI.PropertyField(costRect, costProp, GUIContent.none);

            GUI.enabled = oldEnabled;

            EditorGUI.indentLevel = oldIndent;
        }

        static void AddAgentType(ReorderableList list)
        {
            UnityEngine.AI.NavMesh.CreateSettings();
            list.index = UnityEngine.AI.NavMesh.GetSettingsCount() - 1;
        }

        void RemoveAgentType(ReorderableList list)
        {
            SerializedProperty agentTypeProp = m_AgentTypes.GetArrayElementAtIndex(list.index);
            if (agentTypeProp == null)
                return;
            SerializedProperty idProp = agentTypeProp.FindPropertyRelative("agentTypeID");
            if (idProp == null)
                return;

            // Cannot delete default.
            if (idProp.intValue == 0)
                return;

            m_SettingNames.DeleteArrayElementAtIndex(list.index);
            ReorderableList.defaultBehaviours.DoRemoveButton(list);
        }

        static void DrawAgentTypeListHeader(Rect rect)
        {
            GUI.Label(rect, Styles.k_AgentTypesHeader);
        }

        void DrawAgentTypeListElement(Rect rect, int index, bool selected, bool focused)
        {
            SerializedProperty agentProp = m_AgentTypes.GetArrayElementAtIndex(index);
            if (agentProp == null)
                return;
            SerializedProperty idProp = agentProp.FindPropertyRelative("agentTypeID");
            if (idProp == null)
                return;

            rect.height -= 2; // nicer looking with selected list row and a text field in it

            bool isDefault = idProp.intValue == 0;
            using (new EditorGUI.DisabledScope(isDefault))
            {
                var settingsName = UnityEngine.AI.NavMesh.GetSettingsNameFromID(idProp.intValue);
                GUI.Label(rect, EditorGUIUtility.TrTempContent(settingsName));
            }
        }

        public void OnGUI()
        {
            EditorGUILayout.Space();
            ModeToggle();
            EditorGUILayout.Space();

            InitProjectSettings();

            m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
            switch (m_Mode)
            {
                case Mode.AreaSettings:
                    AreaSettings();
                    break;
                case Mode.AgentTypeSettings:
                    AgentTypeSettings();
                    break;
            }

            EditorGUILayout.EndScrollView();
        }

        void AreaSettings()
        {
            if (m_Areas == null)
                InitAreas();
            m_NavMeshProjectSettingsObject.Update();

            using (new GUILayout.VerticalScope(Styles.k_ContentMargins))
            {
                m_AreasList.DoLayoutList();
            }

            m_NavMeshProjectSettingsObject.ApplyModifiedProperties();
        }

        void AgentTypeSettings()
        {
            if (m_AgentTypes == null)
                InitAgentTypes();
            m_NavMeshProjectSettingsObject.Update();

            if (m_AgentTypeList.index < 0)
                m_AgentTypeList.index = 0;

            using (new GUILayout.VerticalScope(Styles.k_ContentMargins))
            {
                m_AgentTypeList.DoLayoutList();
            }

            if (m_AgentTypeList.index >= 0 && m_AgentTypeList.index < m_AgentTypes.arraySize)
            {
                SerializedProperty nameProp = m_SettingNames.GetArrayElementAtIndex(m_AgentTypeList.index);
                SerializedProperty selectedAgentType = m_AgentTypes.GetArrayElementAtIndex(m_AgentTypeList.index);

                SerializedProperty radiusProp = selectedAgentType.FindPropertyRelative("agentRadius");
                SerializedProperty heightProp = selectedAgentType.FindPropertyRelative("agentHeight");
                SerializedProperty stepHeightProp = selectedAgentType.FindPropertyRelative("agentClimb");
                SerializedProperty maxSlopeProp = selectedAgentType.FindPropertyRelative("agentSlope");
                SerializedProperty ledgeDropHeightProp = selectedAgentType.FindPropertyRelative("ledgeDropHeight");
                SerializedProperty jumpDistanceProp = selectedAgentType.FindPropertyRelative("maxJumpAcrossDistance");

                const float kDiagramHeight = 120.0f;
                Rect agentDiagramRect = EditorGUILayout.GetControlRect(false, kDiagramHeight);
                NavMeshEditorHelpers.DrawAgentDiagram(agentDiagramRect, radiusProp.floatValue, heightProp.floatValue,
                    stepHeightProp.floatValue, maxSlopeProp.floatValue);

                EditorGUILayout.PropertyField(nameProp, EditorGUIUtility.TrTempContent("Name"));
                EditorGUILayout.PropertyField(radiusProp, EditorGUIUtility.TrTempContent("Radius"));
                EditorGUILayout.PropertyField(heightProp, EditorGUIUtility.TrTempContent("Height"));
                EditorGUILayout.PropertyField(stepHeightProp, EditorGUIUtility.TrTempContent("Step Height"));

                const float kMaxSlopeAngle = 60.0f;
                EditorGUILayout.Slider(maxSlopeProp, 0.0f, kMaxSlopeAngle, EditorGUIUtility.TrTextContent("Max Slope"));

                EditorGUILayout.Space();
                EditorGUILayout.LabelField(EditorGUIUtility.TrTempContent("Generated Links"), EditorStyles.boldLabel);
                EditorGUILayout.PropertyField(ledgeDropHeightProp, EditorGUIUtility.TrTempContent("Drop Height"));
                EditorGUILayout.PropertyField(jumpDistanceProp, EditorGUIUtility.TrTempContent("Jump Distance"));
            }

            EditorGUILayout.Space();

            m_NavMeshProjectSettingsObject.ApplyModifiedProperties();
        }
    }
}