using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Pilz.Plugins.Advanced
{
    public sealed class PluginFeatureController
    {
        public delegate void PluginFeatureEventHandler(PluginFeatureController controller, PluginFeature feature);

        /// <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;

        /// <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();

        private readonly List<PluginFeature> features = new();

        /// <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);
        }

        public void Register(PluginFeature module)
        {
            if (!features.Contains(module))
            {
                features.Add(module);
                OnPluginFeatureReistred?.Invoke(this, module);
            }
        }

        public void Unregister(PluginFeature module)
        {
            features.Remove(module);
            OnPluginFeatureUnregistred?.Invoke(this, module);
        }

        public class FeatureController
        {
            protected readonly PluginFeatureController controller;

            public FeatureController(PluginFeatureController controller)
            {
                this.controller = controller;
            }

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

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

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

        public class FeatureController<T> : FeatureController where T : PluginFeature
        {
            public FeatureController(PluginFeatureController controller) : base(controller)
            {
            }

            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 GetAll().FirstOrDefault(n => n.Type == moduleType);
            }
        }

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

        public class FunctionController : FeatureController<PluginFunction>
        {
            public FunctionController(PluginFeatureController controller) : base(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));
            }
        }
    }
}
