using System;
using System.Collections.Generic;

using UnityEditor;
using UnityEngine;

using PlasticGui;

namespace Unity.PlasticSCM.Editor.UI
{
    [InitializeOnLoad]
    internal abstract class PlasticDialog : EditorWindow, IPlasticDialogCloser
    {
        protected virtual Rect DefaultRect
        {
            get
            {
                int pixelWidth = Screen.currentResolution.width;
                float x = (pixelWidth - DEFAULT_WIDTH) / 2;
                return new Rect(x, 200, DEFAULT_WIDTH, DEFAULT_HEIGHT);
            }
        }

        protected virtual bool IsResizable { get; set; }

        internal void OkButtonAction()
        {
            CompleteModal(ResponseType.Ok);
        }

        internal void CancelButtonAction()
        {
            CompleteModal(ResponseType.Cancel);
        }

        internal void CloseButtonAction()
        {
            CompleteModal(ResponseType.None);
        }

        internal void ApplyButtonAction()
        {
            CompleteModal(ResponseType.Apply);
        }

        internal ResponseType RunModal(EditorWindow parentWindow)
        {
            InitializeVars(parentWindow);

            if (!IsResizable)
                MakeNonResizable();

            if (UI.RunModal.IsAvailable())
            {
                UI.RunModal.Dialog(this);
                return mAnswer;
            }

            EditorUtility.DisplayDialog(
                PlasticLocalization.GetString(PlasticLocalization.Name.PlasticSCM),
                PlasticLocalization.GetString(PlasticLocalization.Name.PluginModalInformation),
                PlasticLocalization.GetString(PlasticLocalization.Name.CloseButton));
            return ResponseType.None;
        }

        protected void OnGUI()
        {
            try
            {
                // If the Dialog has been saved into the Unity editor layout and persisted between restarts, the methods
                // to configure the dialogs will be skipped. Simple fix here is to close it when this state is detected.
                // Fixes a NPE loop when the state mentioned above is occuring.
                if (!mIsConfigured)
                {
                    mIsClosed = true;
                    Close();
                    return;
                }

                if (Event.current.type == EventType.Layout)
                {
                    EditorDispatcher.Update();
                }

                if (!mFocusedOnce)
                {
                    // Somehow the prevents the dialog from jumping when dragged
                    // NOTE(rafa): We cannot do every frame because the modal kidnaps focus for all processes (in mac at least)
                    Focus();
                    mFocusedOnce = true;
                }

                ProcessKeyActions();

                if (mIsClosed)
                    return;

                GUI.Box(new Rect(0, 0, position.width, position.height), GUIContent.none, EditorStyles.label);

                float margin = 25;
                float marginTop = 25;
                using (new EditorGUILayout.HorizontalScope(GUILayout.Height(position.height)))
                {
                    GUILayout.Space(margin);
                    using (new EditorGUILayout.VerticalScope(GUILayout.Height(position.height)))
                    {
                        GUILayout.Space(marginTop);
                        OnModalGUI();
                        GUILayout.Space(margin);
                    }
                    GUILayout.Space(margin);
                }

                var lastRect = GUILayoutUtility.GetLastRect();
                float desiredHeight = lastRect.yMax;
                Rect newPos = position;
                newPos.height = desiredHeight;
                if (position.height < desiredHeight)
                    position = newPos;

                if (Event.current.type == EventType.Repaint)
                {
                    if (mIsCompleted)
                    {
                        mIsClosed = true;
                        Close();
                    }
                }
            }
            finally
            {
                if (mIsClosed)
                    EditorGUIUtility.ExitGUI();
            }
        }

        void OnDestroy()
        {
            if (!mIsConfigured)
                return;

            SaveSettings();
            mParentWindow.Focus();
        }

        protected virtual void SaveSettings() { }
        protected abstract void OnModalGUI();
        protected abstract string GetTitle();

        protected void Paragraph(string text)
        {
            GUILayout.Label(text, UnityStyles.Paragraph);
            GUILayout.Space(DEFAULT_PARAGRAPH_SPACING);
        }

        protected void TextBlockWithEndLink(
            string url, string formattedExplanation,
            GUIStyle textblockStyle)
        {
            string explanation = string.Format(
                formattedExplanation, "");

            GUILayout.Label(explanation, textblockStyle);

            if (explanation == formattedExplanation)
                return;

            string coloredUrl = string.Format(
                "<color=\"{0}\">{1}</color>",
                UnityStyles.HexColors.LINK_COLOR,
                url);

            float linkWidth =
                textblockStyle.CalcSize(new GUIContent(url)).x;

            if (GUILayout.Button(coloredUrl, textblockStyle, GUILayout.Width(linkWidth)))
                Application.OpenURL(url);

            EditorGUIUtility.AddCursorRect(
                GUILayoutUtility.GetLastRect(), MouseCursor.Link);
        }

        protected static void Title(string text)
        {
            GUILayout.Label(text, UnityStyles.Dialog.Toggle);
        }

        protected static bool TitleToggle(string text, bool isOn)
        {
            return EditorGUILayout.ToggleLeft(text, isOn, UnityStyles.Dialog.Toggle);
        }

        protected static bool TitleToggle(string text, bool isOn, GUIStyle style)
        {
            return EditorGUILayout.ToggleLeft(text, isOn, style);
        }

        protected static string TextEntry(
            string label,
            string value,
            float width,
            float x)
        {
            return TextEntry(
                label,
                value,
                null,
                width,
                x);
        }

        protected static string TextEntry(
            string label, string value, string controlName, float width, float x)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                EntryLabel(label);

                GUILayout.FlexibleSpace();

                var rt = GUILayoutUtility.GetRect(
                    new GUIContent(value), UnityStyles.Dialog.EntryLabel);
                rt.width = width;
                rt.x = x;

                if (!string.IsNullOrEmpty(controlName))
                    GUI.SetNextControlName(controlName);

                return GUI.TextField(rt, value);
            }
        }

        protected static string ComboBox(
            string label,
            string value,
            string controlName,
            List<string> dropDownOptions,
            GenericMenu.MenuFunction2 optionSelected,
            float width,
            float x)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                EntryLabel(label);

                GUILayout.FlexibleSpace();

                var rt = GUILayoutUtility.GetRect(
                    new GUIContent(value), UnityStyles.Dialog.EntryLabel);
                rt.width = width;
                rt.x = x;

                return DropDownTextField.DoDropDownTextField(
                    value,
                    label,
                    dropDownOptions,
                    optionSelected,
                    rt);
            }
        }

        protected static string PasswordEntry(
            string label, string value, float width, float x)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                EntryLabel(label);

                GUILayout.FlexibleSpace();

                var rt = GUILayoutUtility.GetRect(
                    new GUIContent(value), UnityStyles.Dialog.EntryLabel);
                rt.width = width;
                rt.x = x;

                return GUI.PasswordField(rt, value, '*');
            }
        }

        protected static bool ToggleEntry(
            string label, bool value, float width, float x)
        {
            var rt = GUILayoutUtility.GetRect(
                new GUIContent(label), UnityStyles.Dialog.EntryLabel);
            rt.width = width;
            rt.x = x;

            return GUI.Toggle(rt, value, label);
        }

        protected static bool NormalButton(string text)
        {
            return GUILayout.Button(
                text, UnityStyles.Dialog.NormalButton,
                GUILayout.MinWidth(80),
                GUILayout.Height(25));
        }

        void IPlasticDialogCloser.CloseDialog()
        {
            OkButtonAction();
        }

        void ProcessKeyActions()
        {
            Event e = Event.current;

            if (mEnterKeyAction != null &&
                Keyboard.IsReturnOrEnterKeyPressed(e))
            {
                mEnterKeyAction.Invoke();
                e.Use();
                return;
            }

            if (mEscapeKeyAction != null &&
                Keyboard.IsKeyPressed(e, KeyCode.Escape))
            {
                mEscapeKeyAction.Invoke();
                e.Use();
                return;
            }
        }

        protected static bool AcceptButton(string text)
        {
            GUI.color = new Color(0.098f, 0.502f, 0.965f, .8f);

            int textWidth = (int)((GUIStyle)UnityStyles.Dialog.AcceptButtonText)
                .CalcSize(new GUIContent(text)).x;

            bool pressed = GUILayout.Button(
                string.Empty, GetEditorSkin().button,
                GUILayout.MinWidth(Math.Max(80, textWidth + 10)),
                GUILayout.Height(25));

            GUI.color = Color.white;

            Rect buttonRect = GUILayoutUtility.GetLastRect();
            GUI.Label(buttonRect, text, UnityStyles.Dialog.AcceptButtonText);

            return pressed;
        }

        void CompleteModal(ResponseType answer)
        {
            mIsCompleted = true;
            mAnswer = answer;
        }

        void InitializeVars(EditorWindow parentWindow)
        {
            mIsConfigured = true;
            mIsCompleted = false;
            mIsClosed = false;
            mAnswer = ResponseType.Cancel;

            titleContent = new GUIContent(GetTitle());

            mFocusedOnce = false;

            position = DefaultRect;
            mParentWindow = parentWindow;
        }

        void MakeNonResizable()
        {
            maxSize = DefaultRect.size;
            minSize = maxSize;
        }

        static void EntryLabel(string labelText)
        {
            GUIContent labelContent = new GUIContent(labelText);
            GUIStyle labelStyle = UnityStyles.Dialog.EntryLabel;

            Rect rt = GUILayoutUtility.GetRect(labelContent, labelStyle);

            GUI.Label(rt, labelText, EditorStyles.label);
        }

        static GUISkin GetEditorSkin()
        {
            return EditorGUIUtility.isProSkin ?
                EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene) :
                EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector);
        }

        bool mIsConfigured;
        bool mIsCompleted;
        bool mIsClosed;
        ResponseType mAnswer;

        protected Action mEnterKeyAction = null;
        protected Action mEscapeKeyAction = null;

        bool mFocusedOnce;

        Dictionary<string, string[]> mWrappedTextLines =
            new Dictionary<string, string[]>();

        EditorWindow mParentWindow;

        protected const float DEFAULT_LINE_SPACING = -5;
        const float DEFAULT_WIDTH = 500;
        const float DEFAULT_HEIGHT = 180;
        const float DEFAULT_PARAGRAPH_SPACING = 10f;

        static class BuildLine
        {
            internal static string ForIndex(string text, int index)
            {
                if (index < 0 || index > text.Length)
                    return string.Empty;

                return text.Substring(index).Trim();
            }

            internal static string ForIndexAndLenght(string text, int index, int lenght)
            {
                if (index < 0 || index > text.Length)
                    return string.Empty;

                return text.Substring(index, lenght);
            }
        }
    }
}
