﻿using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;

namespace Enklu.Embedded.Editor
{
    /// <summary>
    /// Utility for ensuring the Enklu Embedded packages are installed in a Unity project.
    /// </summary>
    [InitializeOnLoad]
    static class EmbeddedInstaller
    {
        private static ListRequest _listRequest;
        private static AddRequest _addRequest;

        private const string ENKLU_REGISTRY = "http://nexus.enklu.com/repository/upm/";
        private const string TITLE = "Hold on...";

        static EmbeddedInstaller()
        {
            // Only run installer on launch.
            if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode ||
                UnityEditor.EditorApplication.isPlaying ||
                UnityEditor.EditorApplication.isPaused)
            {
                return;
            }

            InstallEnkluEmbedded();
        }

        /// <summary>
        /// Install Enklu Embedded, if not already installed.
        /// </summary>
        [MenuItem("Enklu/Install Enklu Embedded...")]
        static void InstallEnkluEmbedded()
        {
            EditorUtility.DisplayProgressBar(TITLE, "Retrieving current package dependencies.", 0.1f);
            _listRequest = Client.List(true);
            UnityEditor.EditorApplication.update += ListProgress;
        }

        /// <summary>
        /// Tracks Progress of fetching the list of packages and then attempts to install Enklu Embedded.
        /// </summary>
        static void ListProgress()
        {
            if (_listRequest.IsCompleted)
            {
                UnityEditor.EditorApplication.update -= ListProgress;
                if (_listRequest.Status >= StatusCode.Failure)
                {
                    Debug.LogWarning("Unable to fetch installed packages");
                    EditorUtility.ClearProgressBar();
                    return;
                }

                var packages = _listRequest.Result.ToList();
                var embedded = packages.Find(p => p.name == "com.enklu.embedded");
                if (embedded != null)
                {
                    Debug.Log("Enklu Embedded is already installed.");
                    EditorUtility.ClearProgressBar();
                    return;
                }

                // load manifest json if it exists
                var packagesDir = Path.Combine(Application.dataPath, "../Packages");
                var manifestPath = Path.Combine(packagesDir, "manifest.json");

                if (!Directory.Exists(packagesDir))
                {
                    EditorUtility.DisplayProgressBar(TITLE, "Creating Packages directory for project.", 0.2f);
                    Directory.CreateDirectory(packagesDir);
                }

                var json = "{}";
                if (File.Exists(manifestPath))
                {
                    EditorUtility.DisplayProgressBar(TITLE, "Loading package manifest", 0.3f);
                    json = File.ReadAllText(manifestPath);
                }
                else
                {
                    EditorUtility.DisplayProgressBar(TITLE, "Creating package manifest", 0.3f);
                }

                var manifest = JSON.Parse(json).AsObject;
                JSONArray registries;
                if (manifest.HasKey("scopedRegistries"))
                {
                    EditorUtility.DisplayProgressBar(TITLE, "Found Enklu package registry", 0.4f);
                    registries = manifest["scopedRegistries"].AsArray;
                }
                else
                {
                    registries = new JSONArray();
                }

                JSONObject enkluRegistry = null;
                for (var i = 0; i < registries.Count; i++)
                {
                    var registry = registries[i].AsObject;
                    if (registry.HasKey("url") && registry["url"] == ENKLU_REGISTRY)
                    {
                        enkluRegistry = registry;
                        break;
                    }
                }

                if (enkluRegistry == null)
                {
                    EditorUtility.DisplayProgressBar(TITLE, "Creating scoped registry for Enklu packages.", 0.4f);

                    enkluRegistry = new JSONObject();
                    enkluRegistry.Add("name", new JSONString("Enklu"));
                    enkluRegistry.Add("url", new JSONString(ENKLU_REGISTRY));

                    var scopes = new JSONArray();
                    scopes.Add(new JSONString("com.enklu"));
                    enkluRegistry.Add("scopes", scopes);

                    registries.Add(enkluRegistry);
                    manifest["scopedRegistries"] = registries;
                    var jsonOut = manifest.ToString(2);

                    try
                    {
                        EditorUtility.DisplayProgressBar(TITLE, "Saving package manifest.", 0.5f);
                        File.WriteAllText(manifestPath, jsonOut);
                        AssetDatabase.Refresh();
                    }
                    catch (Exception exception)
                    {
                        Debug.LogErrorFormat("Unable to save package manifest: {0}", exception);
                        EditorUtility.ClearProgressBar();
                        return;
                    }
                }

                // add enklu embedded package
                EditorUtility.DisplayProgressBar(TITLE, "Please wait. Adding Enklu Embedded package as dependency...", 0.7f);
                _addRequest = Client.Add("com.enklu.embedded");
                UnityEditor.EditorApplication.update += AddProgress;
            }
        }

        /// <summary>
        /// Tracks progress while add the Enklu Embedded package.
        /// </summary>
        static void AddProgress()
        {
            if (_addRequest.IsCompleted)
            {
                UnityEditor.EditorApplication.update -= AddProgress;
                if (_addRequest.Status >= StatusCode.Failure)
                {
                    Debug.LogError("Unable to add Enklu Embedded packages. Please open the Package Manger to finish installation.");
                }
                EditorUtility.ClearProgressBar();
            }
        }
    }
}
