namespace Hive.Unity.Editor
{
    using UnityEditor;
    using UnityEngine;
    using System;
    using System.Linq;
    using System.Collections.Generic;

    public class HiveGUI
    {

        public enum Icon
        {
            plus,
            minus,
            help,
            warning,
            error,
            android,
            ios,
            mac,
            windows,
        }

        public enum HeaderSize
        {
            h1,
            h2,
            h3,
        }

        public enum ButtonSize
        {
            small,
            medium,
            wide,
            big,
        }

        float contentWidth;
        float contentHeight;
        int contentPadding = 15;

        static GUIStyle areaStyle;
        static GUIStyle labelStyle;
        static GUIStyle inputBoxStyle;
        static GUIStyle dropdownMenuStyle;

        static Dictionary<string, bool> allBools = new Dictionary<string, bool>();
        static Dictionary<Icon, GUIContent> allIcons = new Dictionary<Icon, GUIContent>();
        static Dictionary<string, Vector2> allPoints = new Dictionary<string, Vector2>();
        static Dictionary<string, string> allTabStrs = new Dictionary<string, string>();
        static string[] allDictKeys = new string[] {};
        static string[] allDictVals = new string[] {};

        /// <summary>
        /// Call Prepare() before use the instance.
        /// The parameter contentWidth will be the standard full-width for all the gui contents.
        /// </summary>
        /// <param name="contentWidth"></param>
        /// <param name="contentHeight"></param>
        public HiveGUI(float contentWidth, float contentHeight = 25)
        {
            this.contentWidth = contentWidth;
            this.contentHeight = contentHeight;
        }

        /// <summary>
        /// Call on OnGUI before use any other methods.
        /// It initializes commonly used gui styles which are not allowed to be called from a ScriptableObject constructor or instance field initializer.
        /// </summary>
        public void Prepare()
        {
            if (HiveGUI.labelStyle != null) return;

            HiveGUI.areaStyle = new GUIStyle(GUIStyle.none);
            HiveGUI.areaStyle.margin.left = contentPadding;
            HiveGUI.areaStyle.margin.right = contentPadding;
            HiveGUI.labelStyle = new GUIStyle(GUI.skin.label); 
            HiveGUI.labelStyle.alignment = TextAnchor.MiddleLeft;
            HiveGUI.inputBoxStyle = new GUIStyle(GUI.skin.textField); 
            HiveGUI.inputBoxStyle.alignment = TextAnchor.MiddleLeft;
            HiveGUI.dropdownMenuStyle = new GUIStyle(GUI.skin.GetStyle("ExposablePopupMenu")); 
            HiveGUI.dropdownMenuStyle.margin.top = contentPadding / 2;
            HiveGUI.dropdownMenuStyle.padding.left += contentPadding / 2;

            foreach (Icon icon in Enum.GetValues(typeof(Icon))) {
                string iconName;
                switch (icon) {
                case Icon.plus:
                    iconName = "d_Toolbar Plus";
                break;
                case Icon.minus:
                    iconName = "d_Toolbar Minus";
                break;
                case Icon.help:
                    iconName = "d_Help";
                break;
                case Icon.warning:
                    iconName = "console.warnicon";
                break;
                case Icon.error:
                    iconName = "console.erroricon";
                break;
                case Icon.android:
                    iconName = "BuildSettings.Android On";
                break;
                case Icon.ios:
                    iconName = "BuildSettings.iPhone On";
                break;
                case Icon.mac:
                    iconName = "BuildSettings.Standalone On";
                break;
                case Icon.windows:
                    iconName = "BuildSettings.Metro On";
                break;
                default:
                    iconName = "";
                break;
                }
                GUIContent iconImage = EditorGUIUtility.IconContent(iconName);
                if (iconImage != null) {
                    HiveGUI.allIcons.Add(icon, iconImage);
                }
            }
        }

        GUILayoutOption[] commonSize(float widthRatio = 1)
        {
            if (widthRatio == 1) {
                return new GUILayoutOption[] { GUILayout.Height(contentHeight) };
            } else {
                return new GUILayoutOption[] { GUILayout.Width(contentWidth * widthRatio), GUILayout.Height(contentHeight) };
            }
        }

        public void Filler()
        {
            GUILayout.FlexibleSpace();
        }

        public void Space()
        {
            EditorGUILayout.Space(contentPadding);
        }

        public void Space(float customPadding)
        {
            EditorGUILayout.Space(customPadding);
        }

        public void Horizontal(Action innerGui)
        {
            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            innerGui();
            EditorGUILayout.EndHorizontal();
        }

        public void Vertical(Action innerGui)
        {
            EditorGUILayout.BeginVertical(HiveGUI.areaStyle);
            innerGui();
            EditorGUILayout.EndVertical();
        }

        /// <summary>
        /// ContainerView.
        /// </summary>
        /// <param name="innerGui"></param>
        public void Container(Action innerGui)
        {
            GUIStyle boxStyle = new GUIStyle(HiveGUI.areaStyle);
            boxStyle.fixedWidth = contentWidth - contentPadding * 2;
            boxStyle.margin = new RectOffset(0, 0, contentPadding / 2, contentPadding / 2);

            EditorGUILayout.BeginHorizontal();
            Space();
            EditorGUILayout.BeginVertical(boxStyle);
            innerGui();
            EditorGUILayout.EndVertical();
            Space();
            EditorGUILayout.EndHorizontal();
        }

        /// <summary>
        /// Boxed containerView.
        /// </summary>
        /// <param name="innerGui"></param>
        public void BoxArea(Action innerGui)
        {
            GUIStyle boxStyle = new GUIStyle(GUI.skin.box);
            boxStyle.margin = new RectOffset(contentPadding, contentPadding, contentPadding / 2, contentPadding / 2);
            boxStyle.padding = new RectOffset(contentPadding, contentPadding, contentPadding / 2, contentPadding / 2);

            EditorGUILayout.BeginVertical(boxStyle);
            innerGui();
            EditorGUILayout.EndVertical();
        }

        /// <summary>
        /// Boxed scrollView without bars.
        /// The parameter 'title' must be identical.
        /// </summary>
        /// <param name="height"></param>
        /// <param name="title"></param>
        /// <param name="innerGui"></param>
        public void BoxArea(int height, string title, Action innerGui)
        {
            GUIStyle noStyle = GUIStyle.none;
            GUIStyle boxStyle = new GUIStyle(GUI.skin.box);
            boxStyle.margin = new RectOffset(contentPadding, contentPadding, contentPadding / 2, contentPadding / 2);
            boxStyle.padding = new RectOffset(contentPadding, contentPadding, contentPadding / 2, contentPadding / 2);
            GUILayoutOption scrollHeight = GUILayout.Height(height);
            
            if (!HiveGUI.allPoints.Keys.Contains(title)) HiveGUI.allPoints[title] = Vector2.zero;
            Vector2 oldPoint = HiveGUI.allPoints[title];
            Vector2 newPoint = EditorGUILayout.BeginScrollView(oldPoint, false, false, noStyle, noStyle, boxStyle, scrollHeight);

            HiveGUI.allPoints[title] = newPoint;
            innerGui();
            EditorGUILayout.EndScrollView();
        }

        /// <summary>
        /// Value input field.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public void ValueInputField(string title, string guide, ref List<string> values)
        {
            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent titleLabel = new GUIContent(guidedTitle, guide);
            GUIContent addIcon = HiveGUI.allIcons[Icon.plus];
            GUIContent removeIcon = HiveGUI.allIcons[Icon.minus];
            GUIContent valueLabel = new GUIContent("  Value: ");

            HiveGUI.allDictVals = new string[] {};

            bool willAdd = false;
            bool willRemove = false;
            string inputValue = "";

            Horizontal(()=>{
                EditorGUILayout.LabelField(titleLabel, commonSize());
                Filler();
                willAdd = GUILayout.Button(addIcon, commonSize(0.1f));
            });

            if (values.Count == 0) {
                if (willAdd) {
                    values.Add("");
                }
                return;
            }

            HiveGUI.allDictVals = values.ToArray();

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                inputValue = HiveGUI.allDictVals[i];

                Horizontal(()=>{
                    willRemove = GUILayout.Button(removeIcon, commonSize(0.1f));
                    EditorGUILayout.BeginHorizontal();
                    EditorGUILayout.LabelField(valueLabel, HiveGUI.labelStyle, commonSize(0.25f));
                    inputValue = EditorGUILayout.DelayedTextField(inputValue, HiveGUI.inputBoxStyle, commonSize());
                    EditorGUILayout.EndHorizontal();
                });

                HiveGUI.allDictVals[i] = inputValue;
                if (willRemove) HiveGUI.allDictVals[i] = null;
            }

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                values[i] = HiveGUI.allDictVals[i];
            }
            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                if (HiveGUI.allDictVals[i] == null) {
                    values.RemoveAt(i);
                }
            }

            if (willAdd) {
                values.Add("");
            }
        }

        public void ValueInputField(string title, string guide, ref List<string> values, string url)
        {
            string guidedTitle = title;
            GUIContent titleLabel = new GUIContent(guidedTitle, guide);
            GUIContent addIcon = HiveGUI.allIcons[Icon.plus];
            GUIContent removeIcon = HiveGUI.allIcons[Icon.minus];
            GUIContent valueLabel = new GUIContent("  Value: ");

            HiveGUI.allDictVals = new string[] {};

            bool willAdd = false;
            bool willRemove = false;
            string inputValue = "";

            Horizontal(()=>{
                addLinkLabel(url, guide);
                EditorGUILayout.LabelField(titleLabel, commonSize());
                Filler();
                willAdd = GUILayout.Button(addIcon, commonSize(0.1f));
            });

            if (values.Count == 0) {
                if (willAdd) {
                    values.Add("");
                }
                return;
            }

            HiveGUI.allDictVals = values.ToArray();

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                inputValue = HiveGUI.allDictVals[i];

                Horizontal(()=>{
                    willRemove = GUILayout.Button(removeIcon, commonSize(0.1f));
                    EditorGUILayout.BeginHorizontal();
                    EditorGUILayout.LabelField(valueLabel, HiveGUI.labelStyle, commonSize(0.25f));
                    inputValue = EditorGUILayout.DelayedTextField(inputValue, HiveGUI.inputBoxStyle, commonSize());
                    EditorGUILayout.EndHorizontal();
                });

                HiveGUI.allDictVals[i] = inputValue;
                if (willRemove) HiveGUI.allDictVals[i] = null;
            }

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                values[i] = HiveGUI.allDictVals[i];
            }
            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                if (HiveGUI.allDictVals[i] == null) {
                    values.RemoveAt(i);
                }
            }

            if (willAdd) {
                values.Add("");
            }
        }

        /// <summary>
        /// Key-Value input field.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="keys"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public void KeyValueInputField(string title, string guide, ref List<string> keys, ref List<string> values)
        {
            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent titleLabel = new GUIContent(guidedTitle, guide);

            GUIContent addIcon = HiveGUI.allIcons[Icon.plus];
            GUIContent removeIcon = HiveGUI.allIcons[Icon.minus];
            GUIContent keyLabel = new GUIContent("  Name: ");
            GUIContent valueLabel = new GUIContent("  Value: ");

            HiveGUI.allDictKeys = new string[] {};
            HiveGUI.allDictVals = new string[] {};

            bool willAdd = false;
            bool willRemove = false;
            string inputKey = "";
            string inputValue = "";

            Horizontal(()=>{
                EditorGUILayout.LabelField(titleLabel, commonSize());
                willAdd = GUILayout.Button(addIcon, commonSize(0.1f));
            });

            if (values.Count == 0) {
                if (willAdd) {
                    keys.Add("");
                    values.Add("");
                }
                return;
            }

            HiveGUI.allDictKeys = keys.ToArray();
            HiveGUI.allDictVals = values.ToArray();

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                inputKey = HiveGUI.allDictKeys[i];
                inputValue = HiveGUI.allDictVals[i];

                Horizontal(()=>{
                    willRemove = GUILayout.Button(removeIcon, commonSize(0.1f));
                    EditorGUILayout.BeginHorizontal();
                    EditorGUILayout.LabelField(keyLabel, HiveGUI.labelStyle, commonSize(0.1f));
                    inputKey = EditorGUILayout.DelayedTextField(inputKey, HiveGUI.inputBoxStyle, commonSize());
                    EditorGUILayout.LabelField(valueLabel, HiveGUI.labelStyle, commonSize(0.1f));
                    inputValue = EditorGUILayout.DelayedTextField(inputValue, HiveGUI.inputBoxStyle, commonSize());
                    EditorGUILayout.EndHorizontal();
                });

                HiveGUI.allDictKeys[i] = inputKey;
                HiveGUI.allDictVals[i] = inputValue;
                if (willRemove) HiveGUI.allDictVals[i] = null;
            }

            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                keys[i] = HiveGUI.allDictKeys[i];
                values[i] = HiveGUI.allDictVals[i];
            }
            for (int i = 0; i < HiveGUI.allDictVals.Length; i++) {
                if (HiveGUI.allDictVals[i] == null) {
                    keys.RemoveAt(i);
                    values.RemoveAt(i);
                }
            }

            if (willAdd) {
                keys.Add("");
                values.Add("");
            }
        }

        /// <summary>
        /// Enum dropdown menu.
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="label"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public void DropdownMenu<E>(string title, string guide, ref E value) where E: System.Enum
        {
            E newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);
            
            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = (E)EditorGUILayout.EnumPopup(value, HiveGUI.dropdownMenuStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void DropdownMenu<E>(string title, string guide, ref E value, string url) where E: System.Enum
        {
            E newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);
            
            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            addLinkLabel(url, guide);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = (E)EditorGUILayout.EnumPopup(value, HiveGUI.dropdownMenuStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        /// <summary>
        /// Int slider.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        /// <param name="minValue"></param>
        /// <param name="maxValue"></param>
        public void Slider(string title, string guide, ref int value, int minValue, int maxValue)
        {
            int newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            GUIStyle sliderStyle = new GUIStyle(GUI.skin.horizontalSlider);
            sliderStyle.margin.top = (int)(contentPadding / 2);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = (int)GUILayout.HorizontalSlider(value, minValue, maxValue, sliderStyle, GUI.skin.horizontalSliderThumb, commonSize(0.45f));
            newValue = EditorGUILayout.IntField(newValue, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void Slider(string title, string guide, ref int value, int minValue, int maxValue, string url)
        {
            int newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);

            GUIStyle sliderStyle = new GUIStyle(GUI.skin.horizontalSlider);
            sliderStyle.margin.top = (int)(contentPadding / 2);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            addLinkLabel(url, guide);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = (int)GUILayout.HorizontalSlider(value, minValue, maxValue, sliderStyle, GUI.skin.horizontalSliderThumb, commonSize(0.45f));
            newValue = EditorGUILayout.IntField(newValue, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }


        /// <summary>
        /// Float slider.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        /// <param name="minValue"></param>
        /// <param name="maxValue"></param>
        public void Slider(string title, string guide, ref float value, float minValue, float maxValue)
        {
            float newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            GUIStyle sliderStyle = new GUIStyle(GUI.skin.horizontalSlider);
            sliderStyle.margin.top = (int)(contentPadding / 2);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = GUILayout.HorizontalSlider(value, minValue, maxValue, sliderStyle, GUI.skin.horizontalSliderThumb, commonSize(0.45f));
            newValue = EditorGUILayout.FloatField(newValue, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        /// <summary>
        /// Solid line separator. Insert 'position.width' inside OnGUI().
        /// </summary>
        /// <param name="positionWidth"></param>
        public void Separator(float customWidth = 0)
        {
            Rect lastRect = GUILayoutUtility.GetLastRect();
            float width = customWidth == 0 ? lastRect.width : customWidth;
            
            Rect lineRect = new Rect(lastRect.x, lastRect.y + lastRect.height, width, 1);
            if (lineRect.x < contentPadding) lineRect.x += (float)contentPadding;
            lineRect.y += (float)contentPadding / 2;
            lineRect.xMax = width + contentPadding;

            EditorGUILayout.BeginVertical(HiveGUI.areaStyle, GUILayout.Height(contentHeight));
            EditorGUILayout.Space();
            EditorGUI.DrawRect(lineRect, Color.grey);
            EditorGUILayout.Space();
            EditorGUILayout.EndVertical();
        }

        /// <summary>
        /// Basic toggle item which has a checkbox on the leftside and a label on the rightside.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void Toggle(string title, string guide, ref bool value)
        {
            bool newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            GUIStyle labelStyle = new GUIStyle(HiveGUI.labelStyle);
            labelStyle.padding.left = contentPadding / 2;
            labelStyle.margin.right = contentPadding;

            newValue = EditorGUILayout.ToggleLeft(label, value, labelStyle);

            value = newValue;
        }

        /// <summary>
        /// Wide toggle field which has wider label located at first.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void ToggleWide(string title, string guide, ref bool value)
        {
            bool newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.Toggle(value, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void ToggleWide(string title, string guide, ref bool value, string url)
        {
            bool newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            addLinkLabel(url, guide);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.Toggle(value, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        /// <summary>
        /// Simple Label.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="isCentered"></param>
        public void Label(string text, bool isCentered = false)
        {
            GUIStyle style = new GUIStyle(HiveGUI.labelStyle);
            style.fontSize = 12;
            if (isCentered) style.alignment = TextAnchor.MiddleCenter;

            GUILayout.Label(text, style);
        }
        
        /// <summary>
        /// Tinted Label.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="color"></param>
        /// <param name="isCentered"></param>
        public void TintedLabel(string text, Color color, bool isCentered = false)
        {
            GUIStyleState state = new GUIStyleState { textColor = color };
            GUIStyle style = new GUIStyle(HiveGUI.labelStyle) {
                fontSize = 12,
                normal = state,
                focused = state,
                active = state,
                hover = state
            };
            if (isCentered) style.alignment = TextAnchor.MiddleCenter;

            GUILayout.Label(text, style);
        }

        /// <summary>
        /// Simple Header.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="size"></param>
        /// <param name="isCentered"></param>
        public void HeaderText(string title, HeaderSize size = HeaderSize.h3, bool isCentered = false)
        {
            GUIStyle style = new GUIStyle(HiveGUI.labelStyle);
            style.fontStyle = FontStyle.Bold;
            style.margin.left = 0;
            style.margin.right = 0;
            if (isCentered) style.alignment = TextAnchor.MiddleCenter;
            if (size == HeaderSize.h1) style.fontSize = 30;
            if (size == HeaderSize.h2) style.fontSize = 20;
            if (size == HeaderSize.h3) style.fontSize = 15;

            EditorGUILayout.Space();
            EditorGUILayout.BeginVertical(commonSize());
            GUILayout.Label(title, style);
            EditorGUILayout.EndVertical();
        }

        /// <summary>
        /// Simple Header.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="size"></param>
        /// <param name="isCentered"></param>
        public void IconHeaderText(string title, Icon icon, HeaderSize size = HeaderSize.h3)
        {
            GUIContent guiIcon = allIcons[icon];
            GUIStyle iconStyle = new GUIStyle(HiveGUI.labelStyle);
            GUIStyle textStyle = new GUIStyle(HiveGUI.labelStyle);
            textStyle.fontStyle = FontStyle.Bold;
            textStyle.alignment = TextAnchor.MiddleLeft;
            if (size == HeaderSize.h1) textStyle.fontSize = 30; iconStyle.fixedHeight = contentHeight * 2; iconStyle.fixedWidth = contentHeight * 2;
            if (size == HeaderSize.h2) textStyle.fontSize = 20; iconStyle.fixedHeight = contentHeight * 4/3; iconStyle.fixedWidth = contentHeight * 4/3;
            if (size == HeaderSize.h3) textStyle.fontSize = 15; iconStyle.fixedHeight = contentHeight; iconStyle.fixedWidth = contentHeight;

            EditorGUILayout.Space();
            EditorGUILayout.BeginHorizontal(commonSize());
            GUILayout.Label(guiIcon, iconStyle);
            GUILayout.Label(title, textStyle);
            EditorGUILayout.EndHorizontal();
        }
        
        /// <summary>
        /// Selectable textfield.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void SelectableText(string title, string guide, string value)
        {
            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.25f));
            EditorGUILayout.SelectableLabel(value, HiveGUI.labelStyle, commonSize());
            EditorGUILayout.EndHorizontal();
        }

        /// <summary>
        /// Editable textfield
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void EditableText(string title, string guide, ref string value)
        {
            string newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.25f));
            newValue = EditorGUILayout.DelayedTextField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void EditableText(string title, string guide, ref string value, string url)
        {
            string newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            addLinkLabel(url, guide);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.25f));
            newValue = EditorGUILayout.DelayedTextField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        private void addLinkLabel(string url, string guide)
        {
            GUIContent label = new GUIContent("?", guide);
            GUIStyle style = getQuestionBtnStyle();
            Rect labelRect = GUILayoutUtility.GetRect(label, style, commonSize(0.025f));
            EditorGUI.LabelField(labelRect, label, style);
            
            if(url != "") {
                if (Event.current.type == EventType.MouseDown && labelRect.Contains(Event.current.mousePosition)) {
                    Application.OpenURL(url);
                    GUI.FocusControl(null);
                    Event.current.Use();
                }
            }
        }

        private GUIStyle getQuestionBtnStyle()
        {
            GUIStyle btnStyle = EditorStyles.miniButton;
            btnStyle.alignment = TextAnchor.MiddleCenter;
            btnStyle.normal.textColor = Color.gray;
            return btnStyle;
        }

        /// <summary>
        /// Vertical editable textfield
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void EditableTextVertical(string title, string guide, ref string value)
        {
            string newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            float height = contentHeight + contentPadding;
            EditorGUILayout.BeginVertical(HiveGUI.areaStyle, GUILayout.Height(height));
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, GUILayout.Height(height / 2));
            newValue = EditorGUILayout.DelayedTextField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndVertical();

            value = newValue;
        }

        /// <summary>
        /// Editable textfield for long type values.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void EditableNumber(string title, string guide, ref long value)
        {
            long newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.LongField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void EditableNumber(string title, string guide, ref long value, string url)
        {
            long newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            addLinkLabel(url, guide);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.LongField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        /// <summary>
        /// Editable textfield for float type values.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="value"></param>
        public void EditableNumber(string title, string guide, ref float value)
        {
            float newValue;

            string guidedTitle = title;
            if (guide != "") guidedTitle += " [?]";
            GUIContent label = new GUIContent(guidedTitle, guide);

            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);
            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.FloatField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        public void EditableNumber(string title, string guide, ref float value, string url)
        {
            float newValue;

            string guidedTitle = title;
            GUIContent label = new GUIContent(guidedTitle, guide);
            addLinkLabel(url, guide);
            EditorGUILayout.BeginHorizontal(HiveGUI.areaStyle);

            EditorGUILayout.LabelField(label, HiveGUI.labelStyle, commonSize(0.4f));
            newValue = EditorGUILayout.FloatField(value, HiveGUI.inputBoxStyle, commonSize());
            EditorGUILayout.EndHorizontal();

            value = newValue;
        }

        /// <summary>
        /// Toggled foldout area. The 'title' should be an identical value.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="guide"></param>
        /// <param name="innerGui"></param>
        public void Foldout(string title, string guide, Action innerGui)
        {
            bool isShown;
            GUIContent label;

            if (!HiveGUI.allBools.Keys.Contains(title)) HiveGUI.allBools[title] = false;
            isShown = HiveGUI.allBools[title];

            if (isShown && guide != "") {
                label = new GUIContent(guide);
            } else {
                label = new GUIContent(title);
            }

            EditorWindow currentWindow = EditorWindow.focusedWindow ?? EditorWindow.mouseOverWindow;

            Vertical(()=>{
                isShown = EditorGUILayout.BeginFoldoutHeaderGroup(isShown, label, null);
                if (currentWindow != null) currentWindow.Repaint();

                if (EditorGUILayout.BeginFadeGroup(isShown ? 1.0f : 0.0f)) {
                    BoxArea(()=>{
                        innerGui();
                    });
                }
                EditorGUILayout.EndFadeGroup();
                EditorGUILayout.EndFoldoutHeaderGroup();
            });

            HiveGUI.allBools[title] = isShown;
        }

        /// <summary>
        /// Default button with designated size.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="size"></param>
        /// <param name="onClicked"></param>
        public void Button(string title, ButtonSize size, Action onClicked)
        {
            Button(title, size, 0, onClicked);
        }
        
        /// <summary>
        /// Default buttons with designated size.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="size"></param>
        /// <param name="ratio"></param>
        /// <param name="onClickedActions"></param>
        public void Button(string title, ButtonSize size, float ratio, Action onClicked)
        {
            Color defaultButtonColor = Color.clear;
            TintedButton(title, defaultButtonColor, size, ratio, onClicked);
        }

        /// <summary>
        /// Colored button with designated size.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="color"></param>
        /// <param name="size"></param>
        /// <param name="onClicked"></param>
        public void TintedButton(string title, Color color, ButtonSize size, Action onClicked)
        {
            TintedButton(title, color, size, 0, onClicked);
        }

        /// <summary>
        /// Colored button with designated size.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="hexColorCode"></param>
        /// <param name="size"></param>
        /// <param name="onClicked"></param>
        public void TintedButton(string title, string hexColorCode, ButtonSize size, Action onClicked)
        {
            Color color = hexToLinearColor(hexColorCode);
            TintedButton(title, color, size, 0, onClicked);
        }

        /// <summary>
        /// Colored buttons with designated size.
        /// </summary>
        /// <param name="titles"></param>
        /// <param name="color"></param>
        /// <param name="size"></param>
        /// <param name="ratio"></param>
        /// <param name="onClickedActions"></param>
        public void TintedButton(string title, Color color, ButtonSize size, float ratio, Action onClicked)
        {
            Color newColor;
            bool isClicked = false;
            int vertPadding, horiPadding, vertMargin;

            GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
            buttonStyle.normal.background = null;
            buttonStyle.normal.scaledBackgrounds = null;
            buttonStyle.border = new RectOffset(1, 1, 1, 1);

            if (color == Color.clear) {
                newColor = color;
            } else {
                Color themeColor = EditorGUIUtility.isProSkin
                    ? new Color32(56, 56, 56, 255)
                    : new Color32(194, 194, 194, 255);
                newColor = new Color(color.r / themeColor.r, 
                                     color.g / themeColor.g, 
                                     color.b / themeColor.b, 
                                     1.0f);
            }

            switch (size) {
                case ButtonSize.small:
                    vertPadding = contentPadding / 3; vertMargin = vertPadding;
                    horiPadding = contentPadding / 2;
                break;
                case ButtonSize.medium:
                    vertPadding = contentPadding / 2; vertMargin = vertPadding;
                    horiPadding = contentPadding;
                break;
                case ButtonSize.wide:
                    vertPadding = contentPadding / 3; vertMargin = vertPadding / 2;
                    horiPadding = contentPadding * 2;
                break;
                case ButtonSize.big:
                    vertPadding = contentPadding;     vertMargin = vertPadding / 2;
                    horiPadding = contentPadding * 2;
                break;
                default:
                    vertPadding = contentPadding;     vertMargin = vertPadding / 2;
                    horiPadding = contentPadding;
                break;
            }

            buttonStyle.fixedHeight = contentHeight + vertPadding * 2;
            if (size != ButtonSize.wide && ratio != 0) {
                buttonStyle.fixedWidth = contentWidth * ratio;
            }
            buttonStyle.padding = new RectOffset(horiPadding, horiPadding, vertPadding, vertPadding);
            buttonStyle.margin = new RectOffset(0, 0, vertMargin, vertMargin);

            Color oldColor = GUI.backgroundColor;
            if (newColor != Color.clear) GUI.backgroundColor = newColor;

            if (size == ButtonSize.wide) {
                Vertical(()=>{
                    isClicked = GUILayout.Button(title, buttonStyle);
                });
            } else {
                isClicked = GUILayout.Button(title, buttonStyle);
            }
            if (isClicked) onClicked();

            GUI.backgroundColor = oldColor;
        }

        /// <summary>
        /// Colored button with designated size.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="hexColorCode"></param>
        /// <param name="size"></param>
        /// <param name="ratio"></param>
        /// <param name="onClicked"></param>
        public void TintedButton(string title, string hexColorCode, ButtonSize size, float ratio, Action onClicked)
        {
            Color color = hexToLinearColor(hexColorCode);
            TintedButton(title, color, size, ratio, onClicked);
        }

        /// <summary>
        /// Rounded TabView. Background color is grey and the text color is black.
        /// The parameter 'tabStr' must be identical. 
        /// The length of tabStrs and onClickedActions must be equal each other.
        /// </summary>
        /// <param name="height"></param>
        /// <param name="title"></param>
        /// <param name="tabStrs"></param>
        /// <param name="onClickedActions"></param>
        public void TabView(int height, string title, string[] tabStrs, params Action[] onClickedActions)
        {
            GUILayoutOption contentBoxHeight = GUILayout.Height(height);
            int tabHeight = 25;
            int tabIndent = 3;
            int contentBoxWidth = (int)(contentWidth - contentPadding * 2);
            int tabWidth = (int)(contentBoxWidth / tabStrs.Length - tabIndent);

            Texture2D contentBoxTexture = new Texture2D(contentBoxWidth, height);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < contentBoxWidth; x++) {
                    if (x == 0 || x == contentBoxWidth - 1 || y == 0) {
                        contentBoxTexture.SetPixel(x, y, Color.black);
                    } else {
                        contentBoxTexture.SetPixel(x, y, Color.grey);
                    }
                }
            }
            contentBoxTexture.Apply();

            GUIStyle noStyle = GUIStyle.none;
            GUIStyle contentBoxStyle = new GUIStyle(GUIStyle.none);
            contentBoxStyle.normal.background = contentBoxTexture;
            contentBoxStyle.border = new RectOffset(1, 1, 0, 1);
            contentBoxStyle.margin = new RectOffset(contentPadding, contentPadding, 0, contentPadding / 2);
            contentBoxStyle.padding = new RectOffset(contentPadding, contentPadding, contentPadding / 2, contentPadding / 2);

            GUIStyle tabBoxStyle = new GUIStyle(GUIStyle.none);
            tabBoxStyle.fixedHeight = tabHeight;
            tabBoxStyle.margin.right = tabIndent;
            tabBoxStyle.alignment = TextAnchor.MiddleCenter;
            tabBoxStyle.normal.background = halfRoundedCornerTexture(tabWidth, tabHeight, Color.grey, false);
            GUIStyle selectedTabBoxStyle = new GUIStyle(GUIStyle.none);
            selectedTabBoxStyle.fixedHeight = tabHeight;
            selectedTabBoxStyle.margin.right = tabIndent;
            selectedTabBoxStyle.alignment = TextAnchor.MiddleCenter;
            selectedTabBoxStyle.normal.background = halfRoundedCornerTexture(tabWidth, tabHeight, Color.grey * 0.75f, true);
            GUIStyle lastTabBoxStyle = new GUIStyle(tabBoxStyle);
            lastTabBoxStyle.margin.right = 0;
            GUIStyle lastSelectedTabBoxStyle = new GUIStyle(selectedTabBoxStyle);
            lastSelectedTabBoxStyle.margin.right = 0;

            if (!HiveGUI.allTabStrs.Keys.Contains(title)) HiveGUI.allTabStrs[title] = tabStrs[0];
            
            string presentingTabName = HiveGUI.allTabStrs[title];
            int presentingTabIndex = Array.IndexOf(tabStrs, presentingTabName);

            Horizontal(()=>{
                foreach (string tabStr in tabStrs) {
                    int index = Array.IndexOf(tabStrs, tabStr);
                    bool isSelected = tabStr == presentingTabName;
                    if (isSelected) {
                        if (index < tabStrs.Length - 1) {
                            isSelected = GUILayout.Button(tabStr, tabBoxStyle);
                        } else {
                            isSelected = GUILayout.Button(tabStr, lastTabBoxStyle);
                        }
                    } else {
                        if (index < tabStrs.Length - 1) {
                            isSelected = GUILayout.Button(tabStr, selectedTabBoxStyle);
                        } else {
                            isSelected = GUILayout.Button(tabStr, lastSelectedTabBoxStyle);
                        }
                    }
                    if (isSelected) {
                        HiveGUI.allTabStrs[title] = tabStr;
                    }
                }
            });

            if (!HiveGUI.allPoints.Keys.Contains(title)) HiveGUI.allPoints[title] = Vector2.zero;
            Vector2 oldPoint = HiveGUI.allPoints[title];
            Vector2 newPoint = EditorGUILayout.BeginScrollView(oldPoint, false, false, noStyle, noStyle, contentBoxStyle, contentBoxHeight);

            HiveGUI.allPoints[title] = newPoint;
            Action innerGui = onClickedActions[presentingTabIndex];
            Color oldColor = GUI.contentColor;
            GUI.contentColor = Color.black;
            innerGui();
            GUI.contentColor = oldColor;
            EditorGUILayout.EndScrollView();
        }

        Texture2D halfRoundedCornerTexture(int width, int height, Color fillerColor, bool drawBottomBorder)
        {
            Texture2D texture = new Texture2D(width, height);

            Color borderColor = Color.black;
            Color backgroundColor = fillerColor;

            int radius = height / 3;
            int borderWidth = 1;

            // 배경색
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    texture.SetPixel(x, y, backgroundColor);
                }
            }

            // 상단 테두리
            for (int y = height - borderWidth; y < height; y++) {
                for (int x = radius; x < width - radius; x++) {
                    texture.SetPixel(x, y, borderColor);
                }
            }

            // 하단 테두리
            if (drawBottomBorder) {
                for (int y = 0; y < borderWidth; y++) {
                    for (int x = 0; x < width; x++) {
                        texture.SetPixel(x, y, borderColor);
                    }
                }
            }

            // 좌우 테두리
            for (int y = 0; y < height - radius; y++) {
                for (int x = 0; x < borderWidth; x++) {
                    texture.SetPixel(x, y, borderColor);
                }
                for (int x = width - borderWidth; x < width; x++) {
                    texture.SetPixel(x, y, borderColor);
                }
            }

            // 좌상단 우상단 둥근 모서리
            for (int y = 0; y < radius; y++) {
                for (int x = 0; x < radius; x++) {
                    int coordXL = x + 1; int coordXR = width - 1 - x;
                    int coordYL = y + 1; int coordYR = height - 1 - y;
                    float round = Mathf.Round(Mathf.Sqrt((x - radius) * (x - radius) + (y - radius) * (y - radius)));
                    if (round == radius) {
                        texture.SetPixel(coordXL, coordYR, borderColor);
                        texture.SetPixel(coordXR, coordYR, borderColor);
                    } else if (round > radius) {
                        texture.SetPixel(x, coordYR, Color.clear);
                        texture.SetPixel(coordXL, coordYR, Color.clear);
                        texture.SetPixel(coordXR, coordYR, Color.clear);
                    }
                }
            }

            texture.Apply();
            return texture;
        }

        Color hexToLinearColor(string hex)
        {
            if (hex.StartsWith("#")) hex = hex.Substring(1);
            byte r = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
            byte g = byte.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
            byte b = byte.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
            byte a = (hex.Length == 8) ? byte.Parse(hex.Substring(6, 2), System.Globalization.NumberStyles.HexNumber) : (byte)255;

            float SrgbToLinear(float s)
            {
                s /= 255f;
                return (s <= 0.04045f) ? s / 12.92f : Mathf.Pow((s + 0.055f) / 1.055f, 2.4f);
            }

            return new Color(
                SrgbToLinear(r),
                SrgbToLinear(g),
                SrgbToLinear(b),
                a / 255f
            );
        }

    }

}