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

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides a task scheduler that ensures a maximum concurrency level while
    /// running on top of the ThreadPool.
    /// </summary>
    internal class LimitedConcurrencyTaskScheduler : TaskScheduler
    {
        // Indicates whether the current thread is processing work items.
        [ThreadStatic]
        private static bool _currentThreadIsProcessingItems;

        // The list of tasks to be executed
        private readonly LinkedList<Task> _tasks = new();

        // The maximum concurrency level allowed by this scheduler.
        private int _maximumConcurrencyLevel;

        // Indicates whether the scheduler is currently processing work items.
        private int _delegatesQueuedOrRunning;

        /// <summary>
        /// Creates a new instance with the specified degree of parallelism.
        /// </summary>
        /// <param name="maximumConcurrencyLevel">The maximum degree of parallelism</param>
        public LimitedConcurrencyTaskScheduler(int maximumConcurrencyLevel)
        {
            if (maximumConcurrencyLevel < 1)
                throw new ArgumentOutOfRangeException(nameof(maximumConcurrencyLevel));
            _maximumConcurrencyLevel = maximumConcurrencyLevel;
        }

        /// <summary>
        /// Gets the maximum concurrency level supported by this scheduler.
        /// </summary>
        public override int MaximumConcurrencyLevel => _maximumConcurrencyLevel;

        /// <summary>
        /// Sets the maximum concurrency level for this scheduler.
        /// </summary>
        /// <param name="value">The new maximum concurrency level</param>
        public void SetMaximumConcurrencyLevel(int value)
        {
            if (value < 1)
                throw new ArgumentOutOfRangeException(nameof(value));
            _maximumConcurrencyLevel = value;
        }

        /// <summary>
        /// Queues a task to the scheduler.
        /// </summary>
        protected sealed override void QueueTask(Task task)
        {
            // Add the task to the list of tasks to be processed.
            lock (_tasks)
            {
                _tasks.AddLast(task);
            }

            // If there aren't enough delegates currently queued or running to process
            // tasks, schedule another.
            if (Interlocked.Increment(ref _delegatesQueuedOrRunning) <= _maximumConcurrencyLevel)
            {
                ThreadPool.QueueUserWorkItem(ProcessQueuedTasks);
            }
            else
            {
                Interlocked.Decrement(ref _delegatesQueuedOrRunning);
            }
        }

        /// <summary>
        /// Attempts to execute the specified task on the current thread.
        /// </summary>
        protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            // If this thread isn't already processing a task, we don't support inlining
            if (!_currentThreadIsProcessingItems)
                return false;

            // If the task was previously queued, remove it from the queue
            if (taskWasPreviouslyQueued)
            {
                // Try to remove the task from the queue.
                lock (_tasks)
                {
                    if (_tasks.Contains(task))
                    {
                        _tasks.Remove(task);
                    }
                    else
                    {
                        // The task isn't in the queue anymore, so it was probably already executed
                        return false;
                    }
                }
            }

            // Try to execute the task.
            bool result = TryExecuteTask(task);
            return result;
        }

        /// <summary>
        /// Attempts to remove a previously scheduled task from the scheduler.
        /// </summary>
        protected sealed override bool TryDequeue(Task task)
        {
            lock (_tasks)
            {
                return _tasks.Remove(task);
            }
        }

        /// <summary>
        /// Gets an enumerable of the tasks currently scheduled on this scheduler.
        /// </summary>
        protected sealed override IEnumerable<Task> GetScheduledTasks()
        {
            lock (_tasks)
            {
                return new List<Task>(_tasks);
            }
        }

        /// <summary>
        /// Processes tasks in the queue.
        /// </summary>
        private void ProcessQueuedTasks(object? state)
        {
            // This thread is now processing work items.
            _currentThreadIsProcessingItems = true;
            try
            {
                // Process all available items in the queue.
                while (true)
                {
                    Task? task = null;
                    lock (_tasks)
                    {
                        // When there are no more items to be processed,
                        // note that we're done processing, and get out.
                        if (_tasks.Count == 0)
                        {
                            Interlocked.Decrement(ref _delegatesQueuedOrRunning);
                            break;
                        }

                        // Get the next item from the queue
                        task = _tasks.First!.Value;
                        _tasks.RemoveFirst();
                    }

                    // Execute the task we pulled out of the queue
                    TryExecuteTask(task);
                }
            }
            finally
            {
                // We're done processing items on the current thread
                _currentThreadIsProcessingItems = false;
            }
        }
    }
}