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

namespace Godot
{
    /// <summary>
    /// GodotTaskScheduler contains a linked list of tasks to perform as a queue. Methods
    /// within the class are used to control the queue and perform the contained tasks.
    /// </summary>
    public sealed class GodotTaskScheduler : TaskScheduler, IDisposable
    {
        /// <summary>
        /// The current synchronization context.
        /// </summary>
        internal GodotSynchronizationContext Context { get; }

        /// <summary>
        /// The queue of tasks for the task scheduler.
        /// </summary>
        private readonly LinkedList<Task> _tasks = new LinkedList<Task>();

        /// <summary>
        /// Constructs a new GodotTaskScheduler instance.
        /// </summary>
        public GodotTaskScheduler()
        {
            Context = new GodotSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(Context);
        }

        protected sealed override void QueueTask(Task task)
        {
            lock (_tasks)
            {
                _tasks.AddLast(task);
            }
        }

        protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            if (SynchronizationContext.Current != Context)
                return false;

            if (taskWasPreviouslyQueued)
                TryDequeue(task);

            return TryExecuteTask(task);
        }

        protected sealed override bool TryDequeue(Task task)
        {
            lock (_tasks)
            {
                return _tasks.Remove(task);
            }
        }

        protected sealed override IEnumerable<Task> GetScheduledTasks()
        {
            lock (_tasks)
            {
                foreach (Task task in _tasks)
                    yield return task;
            }
        }

        /// <summary>
        /// Executes all queued tasks and pending tasks from the current context.
        /// </summary>
        public void Activate()
        {
            ExecuteQueuedTasks();
            Context.ExecutePendingContinuations();
        }

        /// <summary>
        /// Loops through and attempts to execute each task in _tasks.
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        private void ExecuteQueuedTasks()
        {
            while (true)
            {
                Task task;

                lock (_tasks)
                {
                    if (_tasks.Any())
                    {
                        task = _tasks.First.Value;
                        _tasks.RemoveFirst();
                    }
                    else
                    {
                        break;
                    }
                }

                if (task != null)
                {
                    if (!TryExecuteTask(task))
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
        }

        public void Dispose()
        {
            Context.Dispose();
        }
    }
}
