using Microsoft.VisualBasic;
using System.Reflection;

namespace Pilz.Plugins.Advanced;

public class PluginFeatureController
{
    // D e l e g a t e s

    public delegate void PluginFeatureEventHandler(PluginFeatureController controller, PluginFeature feature);

    // S t a t i c   E v e n t s

    /// <summary>
    /// Fires when a new <see cref="PluginFeature"/> has been registred.
    /// </summary>
    public static event PluginFeatureEventHandler? OnPluginFeatureReistred;

    /// <summary>
    /// Fires when a <see cref="PluginFeature"/> has been unregistred.
    /// </summary>
    public static event PluginFeatureEventHandler? OnPluginFeatureUnregistred;

    // S t a t i c   M e m b e r s

    protected static readonly string nameGetFeatures = $"{nameof(IPluginFeaturesProvider.GetFeatures)}";
    protected static readonly string nameGetFeaturesExplicit = $"{typeof(IPluginFeaturesProvider).FullName}.{nameof(IPluginFeaturesProvider.GetFeatures)}";
    protected static readonly string nameInstance = $"get_{nameof(IPluginFeatureProvider.Instance)}";
    protected static readonly string nameInstnaceExplicit = $"{typeof(IPluginFeaturesProvider).FullName}.get_{nameof(IPluginFeatureProvider.Instance)}";

    /// <summary>
    /// The default public instance that can be used by plugins and the interface providing software.
    /// </summary>
    public static PluginFeatureController Instance { get; private set; } = new();

    // I n s t a n c e   M e m e b e r s

    private readonly HashSet<PluginFeature> features = [];

    /// <summary>
    /// A wrapper of all registred <see cref="PluginFeature"/> instances.
    /// </summary>
    public FeatureController Features { get; init; }

    /// <summary>
    /// A wrapper for all registred <see cref="PluginModule"/> instances.
    /// </summary>
    public ModuleController Modules { get; init; }

    /// <summary>
    /// A wrapper for all registred <see cref="PluginFunction"/> instances.
    /// </summary>
    public FunctionController Functions { get; init; }

    public PluginFeatureController()
    {
        Features = new(this);
        Functions = new(this);
        Modules = new(this);
    }

    /// <summary>
    /// Registers all features found in the currently executing Assembly via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
    /// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
    /// </summary>
    public void RegisterAllOwn(string? @namespace = null)
    {
        RegisterAll(Assembly.GetCallingAssembly());
    }

    /// <summary>
    /// Registers all features found in the given <see cref="Assembly[]"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
    /// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
    /// </summary>
    /// <param name="assemblies">The assemblies to query for types.</param>
    /// <param name="namespace">If not null, only types within the given namespace will be registered.</param>
    public void RegisterAll(Assembly[] assemblies, string? @namespace = null)
    {
        foreach (var assembly in assemblies)
            RegisterAll(assembly);
    }

    /// <summary>
    /// Registers all features found in the given <see cref="Assembly"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
    /// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
    /// </summary>
    /// <param name="assembly">The assembly to query for types.</param>
    /// <param name="namespace">If not null, only types within the given namespace will be registered.</param>
    public void RegisterAll(Assembly assembly, string? @namespace = null)
    {
        RegisterAll(assembly.GetTypes());
    }

    /// <summary>
    /// Registers all features found from the given <see cref="Type[]"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
    /// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
    /// </summary>
    /// <param name="types">The types to query for providers.</param>
    /// <param name="namespace">If not null, the types will only be processed if they're within the given namespace.</param>
    public void RegisterAll(Type[] types, string? @namespace = null)
    {
        foreach (var type in types)
            RegisterAll(type);
    }

    /// <summary>
    /// Registers all features found from the given <see cref="Type"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
    /// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
    /// </summary>
    /// <param name="type">The type to query for providers.</param>
    /// <param name="namespace">If not null, the type will only be processed if it's within the given namespace.</param>
    public void RegisterAll(Type type, string? @namespace = null)
    {
        if (@namespace != null && type.Namespace != null && type.Namespace != @namespace && !type.Namespace.StartsWith(@namespace + "."))
            return;

        if (type.IsAssignableTo(typeof(IPluginFeaturesProvider)))
        {
            var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            var method = methods.FirstOrDefault(n => n.Name == nameGetFeaturesExplicit || n.Name == nameGetFeatures);

            if (method != null && method.Invoke(null, null) is PluginFeature[] features)
            {
                foreach (var feature in features)
                    Register(feature);
            }
        }
        else if (type.IsAssignableTo(typeof(IPluginFeatureProvider)))
        {
            var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            var method = methods.FirstOrDefault(n => n.Name == nameInstnaceExplicit || n.Name == nameInstance);

            if (method != null && method.Invoke(null, null) is PluginFeature feature)
                Register(feature);
        }
        else if (type.IsAssignableTo(typeof(IPluginFeatureConstructor)))
        {
            if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, [typeof(PluginFeatureController)]) is ConstructorInfo ctor1)
            {
                if (ctor1.Invoke([this]) is PluginFeature feature)
                    Register(feature);
            }
            else if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, []) is ConstructorInfo ctor)
            {
                if (ctor.Invoke([]) is PluginFeature feature)
                    Register(feature);
            }
        }
    }

    /// <summary>
    /// Registers a feature via the given <see cref="IPluginFeatureProvider"/> or <see cref="IPluginFeatureProvider{T}"/>.
    /// </summary>
    public void Register<TProvider>() where TProvider : IPluginFeatureProvider
    {
        Register(TProvider.Instance);
    }

    /// <summary>
    /// Registers all features via the given <see cref="IPluginFeaturesProvider"/>.
    /// </summary>
    public void RegisterAll<TProvider>() where TProvider : IPluginFeaturesProvider
    {
        foreach (var feature in TProvider.GetFeatures())
            Register(feature);
    }

    /// <summary>
    /// Registers the given feature.
    /// </summary>
    /// <param name="module"></param>
    public void Register(PluginFeature module)
    {
        if (!features.Contains(module))
        {
            features.Add(module);
            OnPluginFeatureReistred?.Invoke(this, module);
        }
    }

    /// <summary>
    /// Unregisters the given feature.
    /// </summary>
    /// <param name="module"></param>
    public void Unregister(PluginFeature module)
    {
        features.Remove(module);
        OnPluginFeatureUnregistred?.Invoke(this, module);
    }

    public class FeatureController(PluginFeatureController controller)
    {
        protected readonly PluginFeatureController controller = controller;

        public virtual IEnumerable<PluginFeature> GetAll()
        {
            return controller.features.ToArray();
        }

        public virtual IEnumerable<PluginFeature> Get(string featureType)
        {
            return controller.features.Where(n => n.Type == featureType);
        }

        public virtual PluginFeature? GetFirst(string featureType)
        {
            return controller.features.FirstOrDefault(n => n.Type == featureType);
        }

        public virtual PluginFeature? GetByIdentifier(string fullIdentifier)
        {
            return controller.features.FirstOrDefault(n => n.FullIdentifier == fullIdentifier);
        }

        public virtual PluginFeature? GetByIdentifier(string featureType, string identifier)
        {
            return controller.features.FirstOrDefault(n => n.Type == featureType && n.Identifier == identifier);
        }
    }

    public class FeatureController<T>(PluginFeatureController controller) : FeatureController(controller) where T : PluginFeature
    {
        public override IEnumerable<T> GetAll()
        {
            return controller.features.OfType<T>();
        }

        public override IEnumerable<T> Get(string moduleType)
        {
            return GetAll().Where(n => n.Type == moduleType);
        }

        public override T? GetFirst(string moduleType)
        {
            return base.GetFirst(moduleType) as T;
        }

        public override T? GetByIdentifier(string fullIdentifier)
        {
            return base.GetByIdentifier(fullIdentifier) as T;
        }

        public override T? GetByIdentifier(string featureType, string identifier)
        {
            return base.GetByIdentifier(featureType, identifier) as T;
        }
    }

    public class ModuleController(PluginFeatureController controller) : FeatureController<PluginModuleBase>(controller)
    {
    }

    public class FunctionController(PluginFeatureController controller) : FeatureController<PluginFunction>(controller)
    {
        public void ExecuteAll(string functionType)
        {
            foreach (var function in Get(functionType))
                function.Execute();
        }

        public void ExecuteAll(string functionType, params object?[]? @params)
        {
            foreach (var function in Get(functionType))
                function.Execute(@params);
        }

        public void ExecuteAll(string functionType, PluginFunctionParameter @params)
        {
            foreach (var function in Get(functionType))
                function.Execute(@params);
        }

        public IEnumerable<object?> ExcuteAndGetResults(string functionType)
        {
            return Get(functionType).Select(n => n.Execute());
        }

        public IEnumerable<object?> ExcuteAndGetResults(string functionType, params object?[]? @params)
        {
            return Get(functionType).Select(n => n.Execute(@params));
        }

        public IEnumerable<object?> ExcuteAndGetResults(string functionType, PluginFunctionParameter @params)
        {
            return Get(functionType).Select(n => n.Execute(@params));
        }
    }
}
