using System.Collections.Generic;
using Robust.Shared.Timing;

namespace Robust.Shared.CPUJob.JobQueues.Queues
{
    [Virtual]
    public class JobQueue
    {
        private readonly IStopwatch _stopwatch;

        public JobQueue(double maxTime) : this(new Stopwatch())
        {
            MaxTime = maxTime;
        }

        public JobQueue() : this(new Stopwatch()) {}

        public JobQueue(IStopwatch stopwatch)
        {
            _stopwatch = stopwatch;
        }

        /// <summary>
        /// How long the job's allowed to run for before suspending
        /// </summary>
        public virtual double MaxTime { get; } = 0.002;

        private readonly Queue<IJob> _pendingQueue = new();
        private readonly List<IJob> _waitingJobs = new();

        public void EnqueueJob(IJob job)
        {
            _pendingQueue.Enqueue(job);
        }

        public void Process()
        {
            // Move all finished waiting jobs back into the regular queue.
            foreach (var waitingJob in _waitingJobs)
            {
                if (waitingJob.Status != JobStatus.Waiting)
                {
                    _pendingQueue.Enqueue(waitingJob);
                }
            }

            _waitingJobs.RemoveAll(p => p.Status != JobStatus.Waiting);

            // At one point I tried making the pathfinding queue multi-threaded but ehhh didn't go great
            // Could probably try it again at some point
            // it just seemed slow af but I was probably doing something dumb with semaphores
            _stopwatch.Restart();

            // Although the jobs can stop themselves we might be able to squeeze more of them in the allotted time
            while (_stopwatch.Elapsed.TotalSeconds < MaxTime && _pendingQueue.TryDequeue(out var job))
            {
                // Deque and re-enqueue these to cycle them through to avoid starvation if we've got a lot of jobs.

                job.Run();

                switch (job.Status)
                {
                    case JobStatus.Finished:
                        continue;
                    case JobStatus.Waiting:
                        // If this job goes into waiting we have to move it into a separate list.
                        // Otherwise we'd just be spinning like mad here for external IO or such.
                        _waitingJobs.Add(job);
                        break;
                    default:
                        _pendingQueue.Enqueue(job);
                        break;
                }
            }
        }
    }
}
