using ExtensiblePortfolioSite.SDK.Git;
using ExtensiblePortfolioSite.SDK.Resources;

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json.Serialization;

namespace ExtensiblePortfolioSite.SDK.Plugins
{
    internal delegate void PluginUnloadDelegate(Plugin plugin);
    internal class Plugin : IPluginManagedLibrary
    {
        private class PluginLoadContext : AssemblyLoadContext
        {
            private readonly Plugin Plugin;

            public PluginLoadContext(Plugin Plugin) : base(true) => this.Plugin = Plugin;

            protected override IntPtr LoadUnmanagedDll(String unmanagedDllName)
            {
                IPluginUnmanagedLibrary? Lib = PluginManager.LoadUnmanagedLibrary(unmanagedDllName);
                if (Lib == null)
                    return IntPtr.Zero;

                Plugin.References.Add(Lib);
                return Lib.LibraryPtr;
            }

            protected override Assembly? Load(AssemblyName assemblyName)
            {
                IPluginManagedLibrary? Lib = PluginManager.LoadLibrary(assemblyName);
                if (Lib == null)
                    return PluginManager.GetFixedAssembly(assemblyName);

                Plugin.References.Add(Lib);
                return Lib.Assembly;
            }
        }

        public String Location { get; private set; }
        public Assembly Assembly { get; private set; }
        public EPSPluginAttribute Info { get; private set; }

        public event PluginUnloadDelegate? OnUnloading;

        public IList<IPluginLibrary> Dependents { get; } = new List<IPluginLibrary>();
        IReadOnlyList<IPluginLibrary> IPluginLibrary.References => References.AsReadOnly();
        public List<IPluginLibrary> References = new();

        public Boolean Initialized { get; private set; }

        private readonly PluginLoadContext LoadContext;
        internal Plugin(String Location)
        {
            this.Location = Location;
            this.LoadContext = new PluginLoadContext(this);
            this.Assembly = this.LoadContext.LoadFromAssemblyPath(this.Location);
            this.Info = this.Assembly.GetCustomAttribute<EPSPluginAttribute>() ?? throw new InvalidOperationException("Target Assembly is not a Plugin Assembly!");
        }

        private readonly List<JsonConverter> JsonConverters = new();
        private readonly Dictionary<Type, String[]> ResourceContainers = new();
        private readonly Dictionary<GitService, IGitProvider> GitProviders = new();
        public void Init()
        {
            if (Initialized)
                throw new InvalidOperationException();

            Initialized = true;
            foreach (Type t in this.Assembly.ExportedTypes)
                foreach (Attribute Attr in t.GetCustomAttributes())
                    if (Attr is EPSMarkerAttribute)
                    {
                        if (Attr is GitProviderAttribute GitProviderAttr)
                        {
                            // Load Git Provider
                            if (t.IsAssignableTo(typeof(IGitProvider)) && t.GetConstructor(Type.EmptyTypes) != null)
                                if (Activator.CreateInstance(t, true) is IGitProvider Provider)
                                {
                                    GitService Service = GitManager.GetService(GitProviderAttr.ServiceName);
                                    GitProviders.Add(Service, Provider);
                                    Service.I_AddProvider(this, Provider);
                                }
                                else
                                    PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Failed to Instantiate GitProvider", t, null);
                            else
                                PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked GitProvider does not have an empty Constructor or does not expose an IGitProvider interface", t, null);
                        }
                        else if (Attr is JsonConverterAttribute)
                        {
                            // Load Json Converter
                            if (t.IsAssignableTo(typeof(JsonConverter)) && t.GetConstructor(Type.EmptyTypes) != null)
                                if (Activator.CreateInstance(t, true) is JsonConverter Converter)
                                {
                                    JsonConverters.Add(Converter);
                                    Json.Register(Converter);
                                }
                                else
                                    PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Failed to Instantiate JsonConverter", t, null);
                            else
                                PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked JsonConverter does not have an empty Constructor or does not derive from JsonConverter", t, null);
                        }
                        else if (Attr is ResourceContainerAttribute ResourceContainerAttr)
                        {
                            // Load Resource Container
                            if (t.IsAssignableTo(typeof(IResource)) && t.GetConstructor(new Type[1] { typeof(Uri) }) != null)
                            {
                                try
                                {
                                    ResourceContainers.Add(t, ResourceContainerAttr.Schemes);
                                    ResourceManager.Register(t, ResourceContainerAttr.Schemes);
                                }
                                catch (Exception ex)
                                {
                                    PluginManager.LogMessage(PluginErrorSeverity.Fatal, this, "Failed to Create ResoruceContainer Factory", t, ex);
                                    Console.Error.WriteLine("Runtime Unstable, Exitting!");
                                    Environment.Exit(5);
                                }
                            }
                            else
                                PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked ResourceContainer does not have a valid Constructor or does not expose an IResource interface", t, null);
                        }
                    }
        }

        public void Disposing()
        {
            OnUnloading?.Invoke(this);

            foreach (KeyValuePair<GitService, IGitProvider> provider in GitProviders)
                provider.Key.I_RemoveProvider(this, provider.Value);
            this.GitProviders.Clear();

            foreach (KeyValuePair<Type, String[]> resourceContainer in ResourceContainers)
                ResourceManager.Unregister(resourceContainer.Key);
            this.ResourceContainers.Clear();

            foreach (JsonConverter Converter in JsonConverters)
                Json.Unregister(Converter);
            this.JsonConverters.Clear();

            this.Assembly = null!;
            this.LoadContext.Unload();
        }
    }
}
