using System;
using System.Collections.Generic;
using UnityEngine;

namespace Aidlab
{
    internal class MainThreadWorker
    {
        private static readonly object ExecuteLock = new object();
        private static readonly Queue<Action> ExecuteOnMainThread = new Queue<Action>();
        // The last queued action until it starts; only it takes more items, so everything runs in arrival order.
        private static Action lastQueued;
        private const int MaxEventsPerFrame = 256;

        public static void Enqueue(Action action)
        {
            if (action == null)
                return;
            lock (ExecuteLock)
            {
                ExecuteOnMainThread.Enqueue(action);
                lastQueued = action;
            }
        }

        // Adds item to the batch of action while action is the last queued one and has not started.
        public static bool TryAddToLast<T>(Action action, List<T> batch, T item)
        {
            lock (ExecuteLock)
            {
                if (action == null || !ReferenceEquals(action, lastQueued))
                    return false;
                batch.Add(item);
                return true;
            }
        }

        public void Update()
        {
            for (int processed = 0; processed < MaxEventsPerFrame; processed++)
            {
                Action action;
                lock (ExecuteLock)
                {
                    if (ExecuteOnMainThread.Count == 0)
                        return;
                    action = ExecuteOnMainThread.Dequeue();
                    if (ReferenceEquals(action, lastQueued))
                        lastQueued = null;
                }

                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    Debug.LogException(ex);
                }
            }

            int left;
            lock (ExecuteLock)
                left = ExecuteOnMainThread.Count;
            if (left > 0)
                Debug.LogWarning($"MainThreadWorker: budget exceeded, leaving {left} events for next frame");
        }
    }
}
