// Animancer // Copyright 2020 Kybernetik //

#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.

#if UNITY_EDITOR

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace Animancer.Editor
{
    /// <summary>[Editor-Only] A welcome screen for Animancer.</summary>
    /// <remarks>Automatic selection is handled by <c>ShowReadMeOnStartup</c>.</remarks>
    //[CreateAssetMenu(menuName = Strings.MenuItemPrefix + "Read Me", order = Strings.AssetMenuOrder + 4)]
    public sealed class ReadMe : ScriptableObject
    {
        /************************************************************************************************************************/

        /// <summary>
        /// The release ID of this Animancer version.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        ///   <item>[1] = v1.0: 2018-05-02.</item>
        ///   <item>[2] = v1.1: 2018-05-29.</item>
        ///   <item>[3] = v1.2: 2018-08-14.</item>
        ///   <item>[4] = v1.3: 2018-09-12.</item>
        ///   <item>[5] = v2.0: 2018-10-08.</item>
        ///   <item>[6] = v3.0: 2019-05-27.</item>
        ///   <item>[7] = v3.1: 2019-08-12.</item>
        ///   <item>[8] = v4.0: 2020-01-28.</item>
        /// </list>
        /// </remarks>
        private const int ReleaseNumber = 8;

        /// <summary>The display name of this Animancer version.</summary>
        private const string VersionName = "v4.0";

        /// <summary>The end of the URL for the change log of this Animancer version.</summary>
        private const string ChangeLogSuffix = "v4-0";

        /// <summary>The key used to save the release number.</summary>
        private const string ReleaseNumberPrefKey = "Animancer.ReleaseNumber";

        /************************************************************************************************************************/

        [SerializeField] private Texture2D _Icon;
        [SerializeField] private DefaultAsset _ExamplesFolder;
        [SerializeField] private bool _DontShowOnStartup;

        /************************************************************************************************************************/

        /// <summary>If true, <c>ShowReadMeOnStartup</c> will not automatically select this asset.</summary>
        public bool DontShowOnStartup
        {
            get
            {
                return _DontShowOnStartup && HasCorrectName;
            }
        }

        /************************************************************************************************************************/

        /// <summary>
        /// The <see cref="ReadMe"/> file name ends with the <see cref="VersionName"/> to detect if the user imported
        /// this version without deleting a previous version.
        /// <para></para>
        /// When Unity's package importer sees an existing file with the same GUID as one in the package, it will
        /// overwrite that file but not move or rename it if the name has changed. So it will leave the file there with
        /// the old version name.
        /// </summary>
        private bool HasCorrectName
        {
            get
            {
                return name.EndsWith(VersionName);
            }
        }

        /************************************************************************************************************************/

        [CustomEditor(typeof(ReadMe))]
        private sealed class Editor : UnityEditor.Editor
        {
            /************************************************************************************************************************/

            /// <summary>The <see cref="Editor.target"/> cast to <see cref="ReadMe"/>.</summary>
            public ReadMe Target { get; private set; }

            /// <summary>The <see cref="ReleaseNumber"/> from when "Don't show this again" was last ticked.</summary>
            [NonSerialized]
            private int _PreviousVersion;

            /// <summary>The file path of the Examples folder.</summary>
            [NonSerialized]
            private string _ExamplesDirectory;

            /// <summary>The details of all example scenes.</summary>
            [NonSerialized]
            private List<ExampleGroup> _Examples;

            /************************************************************************************************************************/

            private void OnEnable()
            {
                Target = (ReadMe)target;

                _PreviousVersion = PlayerPrefs.GetInt(ReleaseNumberPrefKey, -1);
                if (_PreviousVersion < 0)
                    _PreviousVersion = EditorPrefs.GetInt(ReleaseNumberPrefKey, -1);// Animancer v2.0 used EditorPrefs.

                _Examples = ExampleGroup.Gather(Target._ExamplesFolder, out _ExamplesDirectory);
            }

            /************************************************************************************************************************/

            protected override void OnHeaderGUI()
            {
                GUILayout.BeginHorizontal("In BigTitle");
                {
                    const string Title = "Animancer Pro\n" + VersionName;
                    var title = AnimancerGUI.TempContent(Title, null, false);
                    var iconWidth = GUIStyles.Title.CalcHeight(title, EditorGUIUtility.currentViewWidth);
                    GUILayout.Label(Target._Icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth));
                    GUILayout.Label(title, GUIStyles.Title);
                }
                GUILayout.EndHorizontal();

            }

            /************************************************************************************************************************/

            public override void OnInspectorGUI()
            {
                DoWarnings();

                DoShowOnStartup();

                DoSpace();

                GUILayout.BeginVertical(GUI.skin.box);

                DoHeadingLink("Documentation", null, Strings.DocumentationURL);

                DoSpace();

                DoHeadingLink("Change Log", null, Strings.DocsURLs.ChangeLogPrefix + ChangeLogSuffix);

                GUILayout.EndVertical();

                DoSpace();

                GUILayout.BeginVertical(GUI.skin.box);

                DoHeadingLink("Examples", null, Strings.DocumentationURL + "/docs/examples");
                if (Target._ExamplesFolder != null)
                {
                    EditorGUILayout.ObjectField(_ExamplesDirectory, Target._ExamplesFolder, typeof(SceneAsset), false);

                    ExampleGroup.DoExampleGUI(_Examples);
                }

                GUILayout.EndVertical();

                DoSpace();

                GUILayout.BeginVertical(GUI.skin.box);

                DoHeadingLink("Forum",
                    "for general discussion, feedback, and news",
                    "https://forum.unity.com/threads/566452");

                DoSpace();

                DoHeadingLink("Issues",
                    "for questions, suggestions, and bug reports",
                    "https://github.com/KybernetikGames/animancer/issues");

                DoSpace();

                DoHeadingLink("Email",
                    "for anything private",
                    "mailto:" + Strings.DeveloperEmail + "?subject=Animancer", Strings.DeveloperEmail);

                GUILayout.EndVertical();

                DoSpace();

                DoShowOnStartup();
            }

            /************************************************************************************************************************/

            private void DoShowOnStartup()
            {
                EditorGUI.BeginChangeCheck();
                Target._DontShowOnStartup = GUILayout.Toggle(Target._DontShowOnStartup, "Don't show this Read Me on startup");
                if (EditorGUI.EndChangeCheck())
                {
                    EditorUtility.SetDirty(Target);
                    if (Target._DontShowOnStartup)
                        PlayerPrefs.SetInt(ReleaseNumberPrefKey, ReleaseNumber);
                }
            }

            /************************************************************************************************************************/

            private void DoSpace()
            {
                GUILayout.Space(AnimancerGUI.LineHeight * 0.2f);
            }

            /************************************************************************************************************************/

            private void DoWarnings()
            {
                MessageType messageType;

                if (!Target.HasCorrectName)
                {
                    messageType = MessageType.Error;
                }
                else if (_PreviousVersion >= 0 && _PreviousVersion < ReleaseNumber)
                {
                    messageType = MessageType.Warning;
                }
                else return;

                // Upgraded from any older version.

                DoSpace();

                var directory = AssetDatabase.GetAssetPath(Target);
                directory = Path.GetDirectoryName(directory);

                string versionWarning;
                if (messageType == MessageType.Error)
                {
                    versionWarning = "You must fully delete any old version of Animancer before importing a new version." +
                        "\n\nClick here to delete '" + directory + "' then you will need to import Animancer again.";
                }
                else
                {
                    versionWarning = "You must fully delete any old version of Animancer before importing a new version." +
                        "\n\nYou can ignore this message if you have already done so." +
                        " Otherwise click here to delete '" + directory + "' then you will need to import Animancer again.";
                }

                EditorGUILayout.HelpBox(versionWarning, messageType);
                CheckDeleteAnimancer(directory);

                // Upgraded from before v2.0.
                if (_PreviousVersion < 4)
                {
                    DoSpace();

                    EditorGUILayout.HelpBox("It seems you have just upgraded from an earlier version of Animancer" +
                        " (before v2.0) so you will need to restart Unity before you can use it.",
                        MessageType.Warning);
                }

                DoSpace();
            }

            /************************************************************************************************************************/

            /// <summary>
            /// Asks if the user wants to delete the root Animancer folder and does so if they confirm.
            /// </summary>
            private void CheckDeleteAnimancer(string directory)
            {
                if (!AnimancerGUI.TryUseClickEventInLastRect())
                    return;

                if (!AssetDatabase.IsValidFolder(directory))
                {
                    Debug.Log(directory + " doesn't exist." +
                        " You must have moved Animancer somewhere else so you will need to delete it manually.");
                    return;
                }

                if (!EditorUtility.DisplayDialog("Delete Animancer?",
                    "Would you like to delete " + directory + "?" +
                    "\n\nYou will then need to reimport Animancer manually.",
                    "Delete", "Cancel"))
                    return;

                AssetDatabase.DeleteAsset(directory);
            }

            /************************************************************************************************************************/

            private bool DoHeadingLink(string heading, string description, string url, string displayURL = null)
            {
                if (DoLinkLabel(heading, description))
                    Application.OpenURL(url);

                bool clicked;

                if (displayURL == null)
                    displayURL = url;

                var content = AnimancerGUI.TempContent(displayURL,
                    "Click to copy this link to the clipboard", false);

                var area = AnimancerGUI.LayoutSingleLineRect();
                if (GUI.Button(area, content, GUIStyles.URL))
                {
                    GUIUtility.systemCopyBuffer = displayURL;
                    Debug.Log("Copied '" + displayURL + "' to the clipboard.");
                    clicked = true;
                }
                else clicked = false;

                EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Text);

                return clicked;
            }

            /************************************************************************************************************************/

            private bool DoLinkLabel(string label, string description)
            {
                var headingStyle = GUIStyles.Heading;
                var content = AnimancerGUI.TempContent(label, null, false);
                var size = headingStyle.CalcSize(content);
                var labelArea = GUILayoutUtility.GetRect(0, size.y);
                var buttonArea = AnimancerGUI.StealFromLeft(ref labelArea, size.x);

                if (description != null)
                    GUI.Label(labelArea, description, GUIStyles.Description);

                Handles.BeginGUI();
                Handles.color = headingStyle.normal.textColor;
                Handles.DrawLine(new Vector3(buttonArea.xMin, buttonArea.yMax), new Vector3(buttonArea.xMax, buttonArea.yMax));
                Handles.color = Color.white;
                Handles.EndGUI();

                EditorGUIUtility.AddCursorRect(buttonArea, MouseCursor.Link);

                return GUI.Button(buttonArea, content, headingStyle);
            }

            /************************************************************************************************************************/

            private sealed class ExampleGroup
            {
                /************************************************************************************************************************/

                /// <summary>The name of this group.</summary>
                public readonly string Name;

                /// <summary>The scenes in the <see cref="Name"/>.</summary>
                public readonly List<SceneAsset> Scenes = new List<SceneAsset>();

                /// <summary>The folder paths of each of the <see cref="Scenes"/>.</summary>
                public readonly List<string> Directories = new List<string>();

                /// <summary>Indicates whether this group should show its contents in the GUI.</summary>
                private bool _IsExpanded;

                /************************************************************************************************************************/

                public static List<ExampleGroup> Gather(DefaultAsset rootDirectoryAsset, out string examplesDirectory)
                {
                    if (rootDirectoryAsset == null)
                    {
                        examplesDirectory = null;
                        return null;
                    }

                    examplesDirectory = AssetDatabase.GetAssetPath(rootDirectoryAsset);
                    if (string.IsNullOrEmpty(examplesDirectory))
                        return null;

                    var directories = Directory.GetDirectories(examplesDirectory);
                    var examples = new List<ExampleGroup>();

                    List<SceneAsset> scenes = null;

                    for (int i = 0; i < directories.Length; i++)
                    {
                        var directory = directories[i];
                        var files = Directory.GetFiles(directory, "*.unity", SearchOption.AllDirectories);

                        for (int j = 0; j < files.Length; j++)
                        {
                            var scene = AssetDatabase.LoadAssetAtPath<SceneAsset>(files[j]);
                            if (scene != null)
                            {
                                if (scenes == null)
                                    scenes = new List<SceneAsset>();

                                scenes.Add(scene);
                            }
                        }

                        if (scenes != null)
                        {
                            examples.Add(new ExampleGroup(examplesDirectory, directory, scenes));
                            scenes = null;
                        }
                    }

                    examplesDirectory = Path.GetDirectoryName(examplesDirectory);

                    return examples;
                }

                /************************************************************************************************************************/

                public ExampleGroup(string rootDirectory, string directory, List<SceneAsset> scenes)
                {
                    var start = rootDirectory.Length + 1;
                    Name = directory.Substring(start, directory.Length - start);
                    Scenes = scenes;

                    start = directory.Length + 1;

                    for (int i = 0; i < scenes.Count; i++)
                    {
                        directory = AssetDatabase.GetAssetPath(scenes[i]);

                        directory = directory.Substring(start, directory.Length - start);
                        directory = Path.GetDirectoryName(directory);
                        Directories.Add(directory);
                    }
                }

                /************************************************************************************************************************/

                public static void DoExampleGUI(List<ExampleGroup> examples)
                {
                    if (examples == null)
                        return;

                    for (int i = 0; i < examples.Count; i++)
                        examples[i].DoExampleGUI();
                }

                public void DoExampleGUI()
                {
                    EditorGUI.indentLevel++;
                    _IsExpanded = EditorGUILayout.Foldout(_IsExpanded, AnimancerGUI.TempContent(Name, null, false), true);
                    if (_IsExpanded)
                    {
                        for (int i = 0; i < Scenes.Count; i++)
                        {
                            EditorGUI.indentLevel++;
                            EditorGUILayout.ObjectField(Directories[i], Scenes[i], typeof(SceneAsset), false);
                            EditorGUI.indentLevel--;
                        }
                    }
                    EditorGUI.indentLevel--;
                }

                /************************************************************************************************************************/
            }

            /************************************************************************************************************************/

            private static class GUIStyles
            {
                /************************************************************************************************************************/

                public static readonly GUIStyle Title = new GUIStyle(GUI.skin.label)
                {
                    fontSize = 26,
                };

                public static readonly GUIStyle Heading = new GUIStyle(GUI.skin.label)
                {
                    fontSize = 18,
                    stretchWidth = false,
                };

                public static readonly GUIStyle Description = new GUIStyle(GUI.skin.label)
                {
                    alignment = TextAnchor.LowerLeft,
                };

                public static readonly GUIStyle URL = new GUIStyle(GUI.skin.label);

                /************************************************************************************************************************/

                static GUIStyles()
                {
                    Heading.normal.textColor = Heading.hover.textColor = new Color32(0x00, 0x78, 0xDA, 0xFF);

                    URL.fontSize = Mathf.CeilToInt(URL.fontSize * 0.8f);
                    URL.normal.textColor = Color.Lerp(URL.normal.textColor, Color.grey, 0.75f);
                }

                /************************************************************************************************************************/
            }

            /************************************************************************************************************************/
        }

        /************************************************************************************************************************/
    }
}

#endif