using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor.EditorTools;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;

namespace UnityEditor.Tilemaps
{
    [Serializable]
    internal class TilemapEditorToolPreferences : ScriptableSingleton<TilemapEditorToolPreferences>
    {
        private static readonly string s_LibraryFolderPath = "Library/GridBrush";
        private static readonly string s_AssetPath = FileUtil.CombinePaths(s_LibraryFolderPath, "TilemapEditorToolPreferences.asset");

        internal class TilemapEditorToolProperties
        {
            public static readonly string defaultToolsEditorPref = "TilemapEditorTool.DefaultTools";

            public static readonly GUIContent defaultToolsLabel = EditorGUIUtility.TrTextContent("Default Tile Palette Tools");
            public static readonly GUIContent addLabel = EditorGUIUtility.TrTextContent("<<", "Add to Defaults");
            public static readonly GUIContent removeLabel = EditorGUIUtility.TrTextContent(">>", "Remove from Defaults");
            public static readonly GUIContent saveLabel = EditorGUIUtility.TrTextContent("Save", "Save Defaults");
            public static readonly GUIContent revertLabel = EditorGUIUtility.TrTextContent("Revert", "Revert Changes");
            public static readonly GUIContent resetLabel = EditorGUIUtility.TrTextContent("Reset", "Reset Defaults");
        }

        internal static void PreferencesGUI()
        {
            using (new SettingsWindow.GUIScope())
            {
                instance.DoPreferencesGUI();
            }
        }

        internal static EditorTool[] CreateDefaultTilePaletteEditorTools()
        {
            return instance.CreateDefaultTilemapEditorTools();
        }

        internal static event Action tilemapEditorToolsChanged;

        private ReorderableList m_DefaultTypes;
        private ReorderableList m_OtherTypes;
        private bool m_Changed;

        private HashSet<Type> m_AllTilemapEditorToolTypes;
        private List<DefaultTilemapEditorTool> m_DefaultTilemapEditorToolTypes;
        private List<DefaultTilemapEditorTool> m_OtherTilemapEditorToolTypes;

        [SerializeField]
        private EditorTool[] s_DefaultTilemapEditorTools;

        private void OnEnable()
        {
            m_DefaultTilemapEditorToolTypes = new List<DefaultTilemapEditorTool>();
            m_OtherTilemapEditorToolTypes = new List<DefaultTilemapEditorTool>();

            LoadDefaultEditorToolTypes();

            m_DefaultTypes = new ReorderableList(m_DefaultTilemapEditorToolTypes, typeof(DefaultTilemapEditorTool), true, false, false, false);
            m_DefaultTypes.drawElementCallback = OnDrawDefaultElement;
            m_DefaultTypes.onReorderCallback = OnReorderDefaultElement;

            m_OtherTypes = new ReorderableList(m_OtherTilemapEditorToolTypes, typeof(DefaultTilemapEditorTool), true, false, false, false);
            m_OtherTypes.drawElementCallback = OnDrawOtherElement;

            m_Changed = false;
        }

        private void InitializeAllTilemapEditorToolTypes()
        {
            m_AllTilemapEditorToolTypes = new HashSet<Type>();
            foreach (var tilemapEditorToolType in TypeCache.GetTypesDerivedFrom(typeof(TilemapEditorTool)))
            {
                if (tilemapEditorToolType.IsAbstract)
                    continue;
                m_AllTilemapEditorToolTypes.Add(tilemapEditorToolType);
            }
        }

        private void LoadDefaultEditorToolTypes()
        {
            InitializeAllTilemapEditorToolTypes();

            m_DefaultTilemapEditorToolTypes.Clear();
            m_OtherTilemapEditorToolTypes.Clear();

            var defaultTools = LoadTilemapEditorToolPreferencesAsset();
            if (defaultTools == null || defaultTools.Count == 0)
            {
                foreach (var type in TilemapEditorTool.s_DefaultToolTypes)
                {
                    m_DefaultTilemapEditorToolTypes.Add(new DefaultTilemapEditorTool { fullTypeName = type.FullName, tilemapEditorToolType = type });
                }
            }
            else
            {
                foreach (var defaultTool in defaultTools)
                {
                    if (!String.IsNullOrWhiteSpace(defaultTool.fullTypeName))
                    {
                        Type toolType = null;
                        foreach (var type in m_AllTilemapEditorToolTypes)
                        {
                            if (type.FullName == defaultTool.fullTypeName)
                            {
                                toolType = type;
                                break;
                            }
                        }
                        m_DefaultTilemapEditorToolTypes.Add(new DefaultTilemapEditorTool { fullTypeName = defaultTool.fullTypeName, tilemapEditorToolType = toolType });
                    }
                }
            }

            HashSet<Type> otherTypes = new HashSet<Type>(m_AllTilemapEditorToolTypes);
            foreach (var defaultType in m_DefaultTilemapEditorToolTypes)
            {
                if (defaultType.tilemapEditorToolType != null)
                    otherTypes.Remove(defaultType.tilemapEditorToolType);
            }

            foreach (var otherType in otherTypes)
            {
                m_OtherTilemapEditorToolTypes.Add(new DefaultTilemapEditorTool
                    {fullTypeName = otherType.FullName, tilemapEditorToolType = otherType});
            }
        }

        private EditorTool[] CreateDefaultTilemapEditorTools()
        {
            if (m_DefaultTilemapEditorToolTypes == null)
                LoadDefaultEditorToolTypes();

            // Create Tools based on the user's preferences
            List<EditorTool> editorTools = new List<EditorTool>();
            foreach (var defaultTool in m_DefaultTilemapEditorToolTypes)
            {
                if (defaultTool.tilemapEditorToolType != null)
                {
                    var tool = ScriptableObject.CreateInstance(defaultTool.tilemapEditorToolType) as EditorTool;
                    if (tool != null)
                    {
                        tool.hideFlags = HideFlags.HideAndDontSave;
                        editorTools.Add(tool);
                    }
                }
            }

            // Ensure that there are always tools for the Tile Palette
            if (editorTools.Count == 0)
            {
                foreach (var defaultToolType in TilemapEditorTool.s_DefaultToolTypes)
                {
                    editorTools.Add(ScriptableObject.CreateInstance(defaultToolType) as EditorTool);
                }
            }

            ClearExistingDefaultTilemapEditorTools();
            s_DefaultTilemapEditorTools = editorTools.ToArray();

            return s_DefaultTilemapEditorTools;
        }

        internal void UpdateTilemapEditorToolsChange()
        {
            tilemapEditorToolsChanged?.Invoke();
        }

        private void OnDrawDefaultElement(Rect rect, int i, bool isactive, bool isfocused)
        {
            if (i < 0 || i >= m_DefaultTilemapEditorToolTypes.Count)
                return;

            var type = m_DefaultTilemapEditorToolTypes[i];
            DoDrawElement(rect, type);
        }

        private void OnDrawOtherElement(Rect rect, int i, bool isactive, bool isfocused)
        {
            if (i < 0 || i >= m_OtherTilemapEditorToolTypes.Count)
                return;

            var type = m_OtherTilemapEditorToolTypes[i];
            DoDrawElement(rect, type);
        }

        private void DoDrawElement(Rect rect, DefaultTilemapEditorTool defaultType)
        {
            if (defaultType.tilemapEditorToolType == null)
            {
                EditorGUI.LabelField(rect, GUIContent.Temp(String.Format("[{0}] {1}", "Invalid", defaultType.fullTypeName)));
            }
            else
            {
                if (defaultType.toolInstance == null)
                    defaultType.toolInstance = (TilemapEditorTool)ScriptableObject.CreateInstance(defaultType.tilemapEditorToolType);
                if (defaultType.toolInstance != null && defaultType.toolInstance.toolbarIcon != null)
                {
                    EditorGUI.LabelField(rect, defaultType.toolInstance.toolbarIcon);
                    var size = GUI.skin.label.CalcSize(defaultType.toolInstance.toolbarIcon);
                    rect.xMin += size.x;
                }
                EditorGUI.LabelField(rect, GUIContent.Temp(defaultType.tilemapEditorToolType.Name, defaultType.tilemapEditorToolType.FullName));
            }
        }

        private void OnReorderDefaultElement(ReorderableList list)
        {
            m_Changed = true;
        }

        private void DoPreferencesGUI()
        {
            if (m_DefaultTypes == null)
                LoadDefaultEditorToolTypes();

            EditorGUILayout.LabelField(TilemapEditorToolProperties.defaultToolsLabel);

            EditorGUILayout.BeginHorizontal();

            m_DefaultTypes.DoLayoutList();

            EditorGUILayout.BeginVertical(GUILayout.Width(100));
            if (GUILayout.Button(TilemapEditorToolProperties.addLabel))
            {
                var otherIndex = m_OtherTypes.index;
                if (0 <= otherIndex && otherIndex < m_OtherTilemapEditorToolTypes.Count)
                {
                    var otherType = m_OtherTilemapEditorToolTypes[otherIndex];
                    var defaultIndex = m_DefaultTypes.index;
                    m_DefaultTilemapEditorToolTypes.Insert(defaultIndex + 1, otherType);
                    m_OtherTilemapEditorToolTypes.RemoveAt(otherIndex);

                    m_DefaultTypes.index = defaultIndex + 1;
                    m_OtherTypes.index = -1;

                    m_Changed = true;
                }
            }
            if (GUILayout.Button(TilemapEditorToolProperties.removeLabel))
            {
                var defaultIndex = m_DefaultTypes.index;
                if (0 <= defaultIndex && defaultIndex < m_DefaultTilemapEditorToolTypes.Count)
                {
                    var defaultType = m_DefaultTilemapEditorToolTypes[defaultIndex];
                    if (defaultType.tilemapEditorToolType != null)
                    {
                        var otherIndex = m_OtherTypes.index;
                        m_OtherTilemapEditorToolTypes.Insert(otherIndex + 1, defaultType);
                        m_OtherTypes.index = otherIndex + 1;
                    }
                    m_DefaultTilemapEditorToolTypes.RemoveAt(defaultIndex);
                    m_DefaultTypes.index = -1;

                    m_Changed = true;
                }
            }

            EditorGUILayout.EndVertical();

            m_OtherTypes.DoLayoutList();

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            using (new EditorGUI.DisabledScope(!m_Changed))
            {
                using (new EditorGUI.DisabledScope(m_DefaultTilemapEditorToolTypes.Count == 0))
                {
                    if (GUILayout.Button(TilemapEditorToolProperties.saveLabel))
                    {
                        SaveTilemapEditorToolPreferences(m_DefaultTilemapEditorToolTypes);
                        m_Changed = false;
                        GridPaintingState.RepaintGridPaintPaletteWindow();
                    }
                }
                if (GUILayout.Button(TilemapEditorToolProperties.revertLabel))
                {
                    LoadDefaultEditorToolTypes();
                    m_DefaultTypes.index = -1;
                    m_OtherTypes.index = -1;
                    m_Changed = false;
                }
            }
            if (GUILayout.Button(TilemapEditorToolProperties.resetLabel))
            {
                ResetTilemapEditorToolPreferences();
                m_DefaultTypes.index = -1;
                m_OtherTypes.index = -1;
                m_Changed = false;
                GridPaintingState.RepaintGridPaintPaletteWindow();
            }
            EditorGUILayout.EndHorizontal();
        }

        internal void SaveTilemapEditorToolPreferences(List<DefaultTilemapEditorTool> defaultTools)
        {
            SaveTilemapEditorToolPreferencesAsset(defaultTools);
            ClearExistingDefaultTilemapEditorTools();
            LoadDefaultEditorToolTypes();
            DeactivateToolIfNotInDefault();
            UpdateTilemapEditorToolsChange();
        }

        internal void ResetTilemapEditorToolPreferences()
        {
            DeleteTilemapEditorToolPreferencesAsset();
            ClearExistingDefaultTilemapEditorTools();
            LoadDefaultEditorToolTypes();
            DeactivateToolIfNotInDefault();
            UpdateTilemapEditorToolsChange();
        }

        private static void DeleteTilemapEditorToolPreferencesAsset()
        {
            if (File.Exists(s_AssetPath))
            {
                FileUtil.DeleteFileOrDirectory(s_AssetPath);
            }
        }

        private static void SaveTilemapEditorToolPreferencesAsset(List<DefaultTilemapEditorTool> defaultTools)
        {
            if (defaultTools == null)
                return;

            foreach (var defaultTool in defaultTools)
            {
                if (defaultTool.tilemapEditorToolType != null &&
                    !(defaultTool.tilemapEditorToolType.IsSubclassOf(typeof(TilemapEditorTool))))
                {
                    return;
                }
            }

            if (!Directory.Exists(s_LibraryFolderPath))
            {
                Directory.CreateDirectory(s_LibraryFolderPath);
            }

            var saveAsset = ScriptableObject.CreateInstance<TilemapEditorToolPreferencesAsset>();
            saveAsset.m_UserDefaultTools = defaultTools;
            InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { saveAsset }, s_AssetPath, true);
        }

        private static void ClearExistingDefaultTilemapEditorTools()
        {
            if (instance.s_DefaultTilemapEditorTools == null)
                return;

            for (int i = 0; i < instance.s_DefaultTilemapEditorTools.Length; ++i)
            {
                instance.s_DefaultTilemapEditorTools[i] = null;
            }
        }

        private List<DefaultTilemapEditorTool> LoadTilemapEditorToolPreferencesAsset()
        {
            if (!File.Exists(s_AssetPath))
                return null;

            var serializedObjects = InternalEditorUtility.LoadSerializedFileAndForget(s_AssetPath);
            if (serializedObjects != null && serializedObjects.Length > 0)
            {
                var defaultTools = serializedObjects[0] as TilemapEditorToolPreferencesAsset;
                if (defaultTools != null)
                    return defaultTools.m_UserDefaultTools;
            }
            return null;
        }

        private void DeactivateToolIfNotInDefault()
        {
            var activeToolType = ToolManager.activeToolType;
            if (activeToolType == null ||
                !activeToolType.IsSubclassOf(typeof(TilemapEditorTool)))
            {
                return;
            }

            if (s_DefaultTilemapEditorTools == null || s_DefaultTilemapEditorTools[0] == null)
                CreateDefaultTilemapEditorTools();

            foreach (var defaultToolType in s_DefaultTilemapEditorTools)
            {
                if (activeToolType == defaultToolType.GetType())
                    return;
            }

            ToolManager.SetActiveTool(typeof(ViewModeTool));
        }
    }
}
