using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AIRuntime.Runtime.Capabilities
{
    /// <summary>
    /// Executes capability middleware in order.
    ///
    /// Similar in design to the ASP.NET Core middleware pipeline.
    /// </summary>
    public sealed class CapabilityPipeline
    {
        private readonly List<ICapabilityMiddleware> _middleware =
            new List<ICapabilityMiddleware>();

        /// <summary>
        /// Adds middleware to the execution pipeline.
        /// Middleware executes in the order it is added.
        /// </summary>
        public CapabilityPipeline Use(
            ICapabilityMiddleware middleware)
        {
            if (middleware == null)
                throw new ArgumentNullException(nameof(middleware));

            _middleware.Add(middleware);
            return this;
        }

        /// <summary>
        /// Executes the pipeline.
        /// </summary>
        /// <param name="context">
        /// Capability execution context.
        /// </param>
        /// <param name="terminal">
        /// Final delegate that actually executes the capability.
        /// </param>
        public Task ExecuteAsync(
            CapabilityContext context,
            CapabilityDelegate terminal)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if (terminal == null)
                throw new ArgumentNullException(nameof(terminal));

            CapabilityDelegate pipeline = terminal;

            //
            // Build the pipeline backwards.
            //
            // Last middleware wraps the terminal delegate,
            // previous middleware wraps that,
            // until the first middleware becomes the entry point.
            //
            for (int i = _middleware.Count - 1; i >= 0; i--)
            {
                ICapabilityMiddleware current = _middleware[i];
                CapabilityDelegate next = pipeline;

                pipeline = async ctx =>
                {
                    await current.InvokeAsync(ctx, next);
                };
            }

            return pipeline(context);
        }
    }
}