// Copyright © 2017-2018 Atomic Software, LLC. All Rights Reserved.
// See LICENSE.md for full license information.

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

namespace Atom.Core.Threading
{
    public class ThreadPool
    {
        public static readonly ThreadPool Instance = new ThreadPool();

        private readonly int MaxThreadCount = Environment.ProcessorCount + 2;
        private readonly Collections.List<Task> Workers = new Collections.List<Task>();
        private readonly Queue<Action> WorkItems = new Queue<Action>();
        private readonly ManualResetEvent WorkAvailable = new ManualResetEvent(false);
        private SpinLock SpinLock = new SpinLock();
        private int ActiveThreadCount;

        public void QueueWorkItem(Action workItem)
        {
            var LockTaken = false;

            try
            {
                SpinLock.Enter(ref LockTaken);

                PooledDelegateHelper.AddReference(workItem);
                WorkItems.Enqueue(workItem);

                if (ActiveThreadCount + 1 >= Workers.Count && Workers.Count < MaxThreadCount)
                {
                    Workers.Add(Task.Factory.StartNew(ProcessWorkItems, Workers.Count, TaskCreationOptions.LongRunning));
                }

                WorkAvailable.Set();
            }
            finally 
            {
                if (LockTaken)
                {
                    SpinLock.Exit(true);
                }
            }
        }

        private void ProcessWorkItems(object state)
        {
            while (true)
            {
                Action WorkItem = null;
                var LockTaken = false;

                try
                {
                    SpinLock.Enter(ref LockTaken);

                    if (WorkItems.Count > 0)
                    {
                        try
                        {
                            WorkItem = WorkItems.Dequeue();

                            if (WorkItems.Count == 0)
                            {
                                WorkAvailable.Reset();
                            }
                        }
                        catch
                        {
                            // ignored
                        }
                    }
                }
                finally 
                {
                    if (LockTaken)
                    {
                        SpinLock.Exit(true);
                    }
                }

                if (WorkItem != null)
                {
                    try
                    {
                        Interlocked.Increment(ref ActiveThreadCount);
                        WorkItem.Invoke();
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                    finally
                    {
                        PooledDelegateHelper.Release(WorkItem);
                        Interlocked.Decrement(ref ActiveThreadCount);
                    }
                }

                WorkAvailable.WaitOne();
            }
        }
    }
}
