using ExtensiblePortfolioSite.SDK.Plugins;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ExtensiblePortfolioSite.SDK.Resources
{
    /// <summary>
    /// Generic Resource Manager
    /// </summary>
    public static class ResourceManager
    {
        static ResourceManager()
        {
            Register(typeof(HTTPFile), typeof(HTTPFile).GetCustomAttribute<ResourceContainerAttribute>()!.Schemes);
            Register(typeof(LocalFile), typeof(LocalFile).GetCustomAttribute<ResourceContainerAttribute>()!.Schemes);
        }

        private delegate IResource ResourceContainerFactory(Uri URI);
        private class ResourceContainer
        {
            public ResourceContainer(ResourceContainerFactory Factory, Type ResourceContainerType)
            {
                this.Schemes = new();
                this.Factory = Factory;
                this.ResourceContainerType = ResourceContainerType;
            }

            public readonly List<String> Schemes;
            public readonly ResourceContainerFactory Factory;
            public readonly Type ResourceContainerType;
        }
        private static readonly SortedDictionary<String, List<ResourceContainer>> SchemeLookup = new();
        private static readonly Dictionary<Type, ResourceContainer> ResourceLookup = new();

        /// <summary>
        /// Resolves a URI to its Resource Handler
        /// </summary>
        /// <param name="URI">Resource URI</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static IResource ResolveResource(Uri URI)
        {
            List<ResourceContainer>? Containers;
            Boolean B;

            lock (SchemeLookup)
                B = SchemeLookup.TryGetValue(URI.Scheme, out Containers);

            if (B)
            {
                lock (Containers!)
                    foreach (ResourceContainer Container in Containers)
                        try { return Container.Factory(URI); } catch { }

                throw new Exception($"Failed to Build Resouce Container! for Scheme {URI.Scheme}");
            }

            throw new Exception($"Unknown Scheme! {URI.Scheme}");
        }

        internal static void Register(Type T, params String[] Scheme)
        {
            lock (ResourceLookup)
            {
                if (ResourceLookup.TryGetValue(T, out ResourceContainer? Container))
                    return;

                ConstructorInfo? Ctor = T.GetConstructor(new Type[1] { typeof(Uri) });
                if (Ctor != null && T.GetInterface(nameof(IResource)) != null)
                {
                    DynamicMethod Factory = new("ResourceManager_Factory_dynamic", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(IResource), new Type[1] { typeof(Uri) }, T, true);

                    ILGenerator IL = Factory.GetILGenerator(7);
                    IL.Emit(OpCodes.Ldarg_0);
                    IL.Emit(OpCodes.Newobj, Ctor);
                    IL.Emit(OpCodes.Ret);

                    Container = new(Factory.CreateDelegate<ResourceContainerFactory>(), T);

                    ResourceLookup.Add(T, Container);

                    lock (SchemeLookup)
                        foreach (String s in Scheme)
                        {
                            Container.Schemes.Add(s);

                            if (SchemeLookup.TryGetValue(s, out List<ResourceContainer>? Containers))
                                lock (Containers)
                                    Containers.Add(Container);
                            else
                                SchemeLookup.Add(s, new List<ResourceContainer>() { Container });
                        }
                }
                else
                    throw new ArgumentException("Provided Type does not have the required constructor/interface", nameof(T));
            }
        }
        internal static void Unregister(Type T)
        {
            lock (ResourceLookup)
            {
                if (ResourceLookup.TryGetValue(T, out ResourceContainer? Container))
                {
                    lock (SchemeLookup)
                        foreach (String s in Container.Schemes)
                            if (SchemeLookup.TryGetValue(s, out List<ResourceContainer>? Containers))
                                lock (Containers)
                                {
                                    Containers.Remove(Container);
                                    if (Containers.Count == 0)
                                        SchemeLookup.Remove(s);
                                }
                }
            }
        }
    }
}
