#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Jovian.InspectorTools.EditorTools {
    public static class MyGUI {
        #region Colors

        public static class Colors {
            public static readonly Color Red = new Color(.8f, .6f, .6f);

            public static readonly Color Green = new Color(.4f, .6f, .4f);

            public static readonly Color Blue = new Color(.6f, .6f, .8f);

            public static readonly Color Gray = new Color(.3f, .3f, .3f);

            public static readonly Color Yellow = new Color(.8f, .8f, .2f, .6f);

            public static readonly Color Brown = new Color(.7f, .5f, .2f, .6f);
        }

        #endregion


        #region Characters

        public static class Characters {
            public const string ArrowUp = "▲";

            public const string ArrowDown = "▼";

            public const string ArrowLeft = "◀";

            public const string ArrowRight = "▶";

            public const string ArrowLeftLight = "←";

            public const string ArrowRightLight = "→";

            public const string ArrowTopRightLight = "↘";

            public const string Check = "✓";

            public const string Cross = "×";
        }

        #endregion


        #region Editor Icons

        public static class EditorIcons {
            public static GUIContent Plus => EditorGUIUtility.IconContent("Toolbar Plus");
            public static GUIContent Minus => EditorGUIUtility.IconContent("Toolbar Minus");
            public static GUIContent Refresh => EditorGUIUtility.IconContent("Refresh");

            public static GUIContent ConsoleInfo => EditorGUIUtility.IconContent("console.infoicon.sml");
            public static GUIContent ConsoleWarning => EditorGUIUtility.IconContent("console.warnicon.sml");
            public static GUIContent ConsoleError => EditorGUIUtility.IconContent("console.erroricon.sml");

            public static GUIContent Check => EditorGUIUtility.IconContent("FilterSelectedOnly");
            public static GUIContent Cross => EditorGUIUtility.IconContent("d_winbtn_win_close");

            public static GUIContent Dropdown => EditorGUIUtility.IconContent("icon dropdown");

            public static GUIContent EyeOn => EditorGUIUtility.IconContent("d_VisibilityOn");
            public static GUIContent EyeOff => EditorGUIUtility.IconContent("d_VisibilityOff");
            public static GUIContent Zoom => EditorGUIUtility.IconContent("d_ViewToolZoom");

            public static GUIContent Help => EditorGUIUtility.IconContent("_Help");
            public static GUIContent Favourite => EditorGUIUtility.IconContent("Favorite");
            public static GUIContent Label => EditorGUIUtility.IconContent("FilterByLabel");

            public static GUIContent Settings => EditorGUIUtility.IconContent("d_Settings");
            public static GUIContent SettingsPopup => EditorGUIUtility.IconContent("_Popup");
            public static GUIContent SettingsMixer => EditorGUIUtility.IconContent("Audio Mixer");

            public static GUIContent Circle => EditorGUIUtility.IconContent("TestNormal");
            public static GUIContent CircleYellow => EditorGUIUtility.IconContent("TestInconclusive");
            public static GUIContent CircleDotted => EditorGUIUtility.IconContent("TestIgnored");
            public static GUIContent CircleRed => EditorGUIUtility.IconContent("TestFailed");
        }

        #endregion


        #region Editor Styles

        /// <summary>
        /// HelpBox with centered text alignment
        /// </summary>
        public static GUIStyle HelpBoxStyle {
            get {
                if(_helpBoxStyle != null) return _helpBoxStyle;
                _helpBoxStyle = new GUIStyle(GUI.skin.GetStyle("HelpBox"));
                _helpBoxStyle.alignment = TextAnchor.MiddleCenter;
                return _helpBoxStyle;
            }
        }

        private static GUIStyle _helpBoxStyle;


        /// <summary>
        /// ToolbarButtonStyle is not resizable by default
        /// </summary>
        public static GUIStyle ResizableToolbarButtonStyle {
            get {
                if(_resizableToolbarButtonStyle != null) return _resizableToolbarButtonStyle;
                _resizableToolbarButtonStyle = new GUIStyle(EditorStyles.toolbarButton);
                _resizableToolbarButtonStyle.fixedHeight = 0;
                return _resizableToolbarButtonStyle;
            }
        }

        private static GUIStyle _resizableToolbarButtonStyle;


        /// <summary>
        /// ToolbarButton with Border, Margin and Padding set to 0
        /// </summary>
        public static GUIStyle BorderlessToolbarButtonStyle {
            get {
                if(_borderlessToolbarButton != null) return _borderlessToolbarButton;
                _borderlessToolbarButton = new GUIStyle(ResizableToolbarButtonStyle);
                var emptyOffset = new RectOffset();
                _borderlessToolbarButton.border = emptyOffset;
                _borderlessToolbarButton.margin = emptyOffset;
                _borderlessToolbarButton.padding = emptyOffset;
                return _borderlessToolbarButton;
            }
        }

        private static GUIStyle _borderlessToolbarButton;


        /// <summary>
        /// Style for a toggle button
        /// </summary>
        public static GUIStyle ButtonToggledStyle(bool toggled) {
            if(!toggled) return EditorStyles.miniButton;

            if(_buttonToggledStyle != null) return _buttonToggledStyle;
            _buttonToggledStyle = new GUIStyle(EditorStyles.miniButton);
            _buttonToggledStyle.normal.background = _buttonToggledStyle.active.background;
            return _buttonToggledStyle;
        }
        private static GUIStyle _buttonToggledStyle;


        /// <summary>
        /// MiniButtonLeft/Middle/Right style based on array index
        /// </summary>
        /// <param name="index"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static GUIStyle MiniButtonStyle(int index, Array collection) {
            if(collection.Length == 1) return EditorStyles.miniButton;
            if(index == 0) return EditorStyles.miniButtonLeft;
            if(index == collection.Length - 1) return EditorStyles.miniButtonRight;
            return EditorStyles.miniButtonMid;
        }


        #endregion


        #region Draw Coloured lines and boxes

        /// <summary>
        /// Draw Separator within GuiLayout
        /// </summary>
        public static void Separator() {
            var color = GUI.color;

            EditorGUILayout.Space();
            var spaceRect = EditorGUILayout.GetControlRect();
            var separatorRectPosition = new Vector2(spaceRect.position.x, spaceRect.position.y + spaceRect.height / 2);
            var separatorRect = new Rect(separatorRectPosition, new Vector2(spaceRect.width, 1));

            GUI.color = Color.white;
            GUI.Box(separatorRect, GUIContent.none);
            GUI.color = color;
        }

        /// <summary>
        /// Draw Line within GUILayout
        /// </summary>
        public static void DrawLine(Color color, bool withSpace = false) {
            if(withSpace) EditorGUILayout.Space();

            var defaultBackgroundColor = GUI.backgroundColor;
            GUI.backgroundColor = color;
            GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1));
            GUI.backgroundColor = defaultBackgroundColor;

            if(withSpace) EditorGUILayout.Space();
        }

        /// <summary>
        /// Draw line within Rect and get Rect back with offset
        /// </summary>
        public static Rect DrawLine(Color color, Rect rect) {
            var h = rect.height;
            var defaultBackgroundColor = GUI.backgroundColor;
            GUI.backgroundColor = color;
            rect.y += 5;
            rect.height = 1;
            GUI.Box(rect, "");
            rect.y += 5;
            GUI.backgroundColor = defaultBackgroundColor;
            rect.height = h;
            return rect;
        }

        /// <summary>
        /// Draw Rect filled with Color
        /// </summary>
        public static void DrawColouredRect(Rect rect, Color color) {
            var defaultBackgroundColor = GUI.backgroundColor;
            GUI.backgroundColor = color;
            GUI.Box(rect, "");
            GUI.backgroundColor = defaultBackgroundColor;
        }

        /// <summary>
        /// Draw background Line within GUILayout
        /// </summary>
        public static void DrawBackgroundLine(Color color, int yOffset = 0) {
            var defColor = GUI.color;
            GUI.color = color;
            var rect = GUILayoutUtility.GetLastRect();
            rect.center = new Vector2(rect.center.x, rect.center.y + 6 + yOffset);
            rect.height = 17;
            GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
            GUI.color = defColor;
        }

        /// <summary>
        /// Draw background Line of height
        /// </summary>
        public static void DrawBackgroundBox(Color color, int height, int yOffset = 0) {
            var defColor = GUI.color;
            GUI.color = color;
            var rect = GUILayoutUtility.GetLastRect();
            rect.center = new Vector2(rect.center.x, rect.center.y + 6 + yOffset);
            rect.height = height;
            GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
            GUI.color = defColor;
        }

        #endregion


        #region Property Field

        /// <summary>
        /// Make a field for SerializedProperty and check if changed
        /// </summary>
        public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) {
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(property, options);
            return EditorGUI.EndChangeCheck();
        }

        /// <summary>
        /// Make a field for SerializedProperty and check if changed
        /// </summary>
        public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) {
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(property, label, options);
            return EditorGUI.EndChangeCheck();
        }

        #endregion


        #region SerializedProperty Manipulation Buttons

        /// <summary>
        /// Move array element at index on index-1 position
        /// </summary>
        public static void MoveArrayElementUpButton(this SerializedProperty property, int elementAt) {
            var guiState = GUI.enabled;
            if(elementAt <= 0) GUI.enabled = false;

            if(UpButton()) {
                EditorApplication.delayCall += () => {
                    property.MoveArrayElement(elementAt, elementAt - 1);
                    property.serializedObject.ApplyModifiedProperties();
                };
            }

            GUI.enabled = guiState;
        }


        /// <summary>
        /// Move array element at index on index+1 position
        /// </summary>
        public static void MoveArrayElementDownButton(this SerializedProperty property, int elementAt) {
            var guiState = GUI.enabled;
            if(elementAt >= property.arraySize - 1) GUI.enabled = false;

            if(DownButton()) {
                EditorApplication.delayCall += () => {
                    property.MoveArrayElement(elementAt, elementAt + 1);
                    property.serializedObject.ApplyModifiedProperties();
                };
            }

            GUI.enabled = guiState;
        }

        /// <summary>
        /// Add new array element to property and get it as SerializedProperty
        /// </summary>
        public static SerializedProperty NewArrayElementButton(this SerializedProperty property) {
            if(PlusButton()) {
                return property.NewElement();
            }

            return null;
        }

        /// <summary>
        /// Remove array element at index
        /// </summary>
        public static void RemoveElementButton(this SerializedProperty property, int elementAt) {
            var guiState = GUI.enabled;
            if(elementAt < 0 || elementAt >= property.arraySize - 1) GUI.enabled = false;

            if(CrossButton()) {
                EditorApplication.delayCall += () => {
                    property.DeleteArrayElementAtIndex(elementAt);
                    property.serializedObject.ApplyModifiedProperties();
                };
            }

            GUI.enabled = guiState;
        }

        #endregion


        #region Drop Area

        /// <summary>
        /// Drag-and-Drop Area to catch objects of specific type
        /// </summary>
        /// <typeparam name="T">Asset type to catch</typeparam>
        /// <param name="areaText">Label to display</param>
        /// <param name="height">Height of the Drop Area</param>
        /// <param name="allowExternal">Allow to drag external files and import as unity assets</param>
        /// <param name="externalImportFolder">Path relative to Assets folder</param>
        /// <returns>Received objects. Null if none received</returns>
        public static T[] DropArea<T>(string areaText, float height, bool allowExternal = false,
            string externalImportFolder = null) where T : Object {
            Event currentEvent = Event.current;
            Rect dropArea = GUILayoutUtility.GetRect(0.0f, height, GUILayout.ExpandWidth(true));
            var style = new GUIStyle(GUI.skin.box);
            style.alignment = TextAnchor.MiddleCenter;
            GUI.Box(dropArea, areaText, style);

            bool dragEvent = currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform;
            if(!dragEvent) return null;
            bool overDropArea = dropArea.Contains(currentEvent.mousePosition);
            if(!overDropArea) return null;

            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
            if(currentEvent.type != EventType.DragPerform) return null;

            DragAndDrop.AcceptDrag();
            Event.current.Use();

            List<T> result = new List<T>();

            bool anyExternal = DragAndDrop.paths.Length > 0 &&
                               DragAndDrop.paths.Length > DragAndDrop.objectReferences.Length;
            if(allowExternal && anyExternal) {
                var folderToLoad = "/";
                if(!string.IsNullOrEmpty(externalImportFolder)) {
                    folderToLoad = "/" + externalImportFolder.Replace("Assets/", "").Trim('/', '\\') + "/";
                }

                List<string> importedFiles = new List<string>();

                foreach(string externalPath in DragAndDrop.paths) {
                    if(externalPath.Length == 0) continue;
                    try {
                        var filename = Path.GetFileName(externalPath);
                        var relativePath = folderToLoad + filename;
                        Directory.CreateDirectory(Application.dataPath + folderToLoad);
                        FileUtil.CopyFileOrDirectory(externalPath, Application.dataPath + relativePath);
                        importedFiles.Add("Assets" + relativePath);
                    }
                    catch(Exception ex) {
                        Debug.LogException(ex);
                    }
                }

                AssetDatabase.Refresh();

                foreach(var importedFile in importedFiles) {
                    var asset = AssetDatabase.LoadAssetAtPath<T>(importedFile);
                    if(asset != null) {
                        result.Add(asset);
                        Debug.Log("Asset imported at path: " + importedFile);
                    }
                    else AssetDatabase.DeleteAsset(importedFile);
                }
            }
            else {
                foreach(Object dragged in DragAndDrop.objectReferences) {
                    var validObject = dragged as T ?? AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GetAssetPath(dragged));

                    if(validObject != null) result.Add(validObject);
                }
            }

            return result.Count > 0 ? result.OrderBy(o => o.name).ToArray() : null;
        }


        /// <summary>
        /// Drag-and-Drop Area to get paths of received objects
        /// </summary>
        /// <param name="areaText">Label to display</param>
        /// <param name="height">Height of the Drop Area</param>
        /// <returns>Received paths</returns>
        public static string[] DropAreaPaths(string areaText, float height) {
            Event currentEvent = Event.current;
            Rect dropArea = GUILayoutUtility.GetRect(0.0f, height, GUILayout.ExpandWidth(true));
            var style = new GUIStyle(GUI.skin.box);
            style.alignment = TextAnchor.MiddleCenter;
            GUI.Box(dropArea, areaText, style);

            bool dragEvent = currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform;
            if(!dragEvent) return null;
            bool overDropArea = dropArea.Contains(currentEvent.mousePosition);
            if(!overDropArea) return null;

            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
            if(currentEvent.type != EventType.DragPerform) return null;

            DragAndDrop.AcceptDrag();
            Event.current.Use();

            return DragAndDrop.paths;
        }

        #endregion


        #region Browse Buttons

        /// <summary>
        /// Creates a filepath textfield with a browse button. Opens the open file panel.
        /// </summary>
        public static string BrowsFileLabel(string name, float labelWidth, string path, string extension) {
            EditorGUILayout.BeginHorizontal();
            GUILayout.Label(name, GUILayout.MaxWidth(labelWidth));
            string filepath = EditorGUILayout.TextField(path);
            if(GUILayout.Button("Browse")) {
                filepath = EditorUtility.OpenFilePanel(name, path, extension);
            }

            EditorGUILayout.EndHorizontal();
            return filepath;
        }

        /// <summary>
        /// Creates a folder path textfield with a browse button. Opens the save folder panel.
        /// </summary>
        public static string BrowseFolderLabel(string name, float labelWidth, string path) {
            EditorGUILayout.BeginHorizontal();
            string filepath = EditorGUILayout.TextField(name, path, GUILayout.MaxWidth(labelWidth));
            if(GUILayout.Button("Browse", GUILayout.MaxWidth(60))) {
                filepath = EditorUtility.SaveFolderPanel(name, path, "Folder");
            }

            EditorGUILayout.EndHorizontal();
            return filepath;
        }

        #endregion


        #region Predefined Buttons

        /// <summary>
        /// Display Button with ArrowUI
        /// </summary>
        public static bool UpButton() {
            return GUILayout.Button(Characters.ArrowUp, EditorStyles.toolbarButton, GUILayout.Width(18));
        }

        public static bool DownButton() {
            return GUILayout.Button(Characters.ArrowDown, EditorStyles.toolbarButton, GUILayout.Width(18));
        }

        public static bool PlusButton() {
            return GUILayout.Button("+", EditorStyles.toolbarButton, GUILayout.Width(18));
        }


        public static bool CrossButton() {
            return GUILayout.Button(Characters.Cross, EditorStyles.toolbarButton, GUILayout.Width(18));
        }

        #endregion


        /// <summary>
        /// Creates a toolbar that is filled in from an Enum. Useful for setting tool modes.
        /// </summary>
        public static Enum EnumToolbar(Enum selected) {
            string[] toolbar = Enum.GetNames(selected.GetType());
            Array values = Enum.GetValues(selected.GetType());

            for(int i = 0; i < toolbar.Length; i++) {
                string toolName = toolbar[i];
                toolName = toolName.Replace("_", " ");
                toolbar[i] = toolName;
            }

            int selectedIndex = 0;
            while(selectedIndex < values.Length) {
                if(selected.ToString() == values.GetValue(selectedIndex).ToString()) {
                    break;
                }

                selectedIndex++;
            }

            selectedIndex = GUILayout.Toolbar(selectedIndex, toolbar);
            return (Enum)values.GetValue(selectedIndex);
        }


        /// <summary>
        /// Creates an array foldout like in inspectors
        /// </summary>
        public static string[] ArrayFoldout(string label, string[] array, ref bool foldout) {
            EditorGUILayout.BeginVertical();

            EditorGUIUtility.labelWidth = 0;
            EditorGUIUtility.fieldWidth = 0;
            foldout = EditorGUILayout.Foldout(foldout, label);
            string[] newArray = array;
            if(foldout) {
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.Space();
                EditorGUILayout.BeginVertical();
                int arraySize = EditorGUILayout.IntField("Size", array.Length);
                if(arraySize != array.Length)
                    newArray = new string[arraySize];
                for(int i = 0; i < arraySize; i++) {
                    string entry = "";
                    if(i < array.Length)
                        entry = array[i];
                    newArray[i] = EditorGUILayout.TextField("Element " + i, entry);
                }

                EditorGUILayout.EndVertical();
                EditorGUILayout.EndHorizontal();
            }

            EditorGUILayout.EndVertical();
            return newArray;
        }
    }
}
#endif