using System.Collections;
using UnityEngine;
using System.Threading;
namespace UnityAsync
{
    [AddComponentMenu("")] // don't show in menu
	public partial class AsyncManager : MonoBehaviour
	{
		/// 
		/// The frame count in the currently active update loop.
		/// 
		public static int CurrentFrameCount { get; private set; }
		
		/// 
		/// The time in the currently active update loop.
		/// 
		public static float CurrentTime { get; private set; }
		
		/// 
		/// The unscaled time in the currently active update loop.
		/// 
		public static float CurrentUnscaledTime { get; private set; }
		
		/// 
		/// Unity's .
		/// 
		public static SynchronizationContext UnitySyncContext { get; private set; }
		
		/// 
		/// Background (thread pool) .
		/// 
		public static SynchronizationContext BackgroundSyncContext { get; private set; }
		
		/// 
		/// Returns true if called from Unity's .
		/// 
		public static bool InUnityContext => Thread.CurrentThread.ManagedThreadId == unityThreadId;
		public static AsyncManager Instance { get; private set; }
		static int unityThreadId, updateCount, lateCount, fixedCount;
		static ContinuationProcessorGroup updates, lateUpdates, fixedUpdates;
		[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
		static void Initialize()
		{
			unityThreadId = Thread.CurrentThread.ManagedThreadId;
			UnitySyncContext = SynchronizationContext.Current;
			BackgroundSyncContext = new SynchronizationContext(); // TODO: confirm this produces desired behaviour
			updates = new ContinuationProcessorGroup();
			lateUpdates = new ContinuationProcessorGroup();
			fixedUpdates = new ContinuationProcessorGroup();
			Instance = new GameObject("Async Manager").AddComponent();
			
			DontDestroyOnLoad(Instance);
		}
		/// 
		/// Initializes the manager explicitly. Typically used when running Unity Editor tests (NUnit unit tests).
		/// 
		public static void InitializeForEditorTests()
		{
			Initialize();
			
			// Do not run tasks in background when testing:
			BackgroundSyncContext = UnitySyncContext;
		}
		/// 
		/// Queues a continuation.
		/// Intended for internal use only - you shouldn't need to invoke this.
		/// 
		public static void AddContinuation(in T cont) where T : IAwaitInstructionAwaiter
		{
			switch(cont.Scheduler)
			{
				case FrameScheduler.Update:
					updates.Add(cont);
					break;
				case FrameScheduler.LateUpdate:
					lateUpdates.Add(cont);
					break;
				case FrameScheduler.FixedUpdate:
					fixedUpdates.Add(cont);
					break;
			}
		}
		/// 
		/// Start a coroutine from any context without requiring a MonoBehaviour.
		/// 
		public new static Coroutine StartCoroutine(IEnumerator coroutine) => ((MonoBehaviour)Instance).StartCoroutine(coroutine);
		void Update()
		{
			CurrentFrameCount = ++updateCount;
			
			if(CurrentFrameCount <= 1)
				return;
			
			CurrentTime = Time.time;
			CurrentUnscaledTime = Time.unscaledTime;
			
			updates.Process();
		}
		void LateUpdate()
		{
			CurrentFrameCount = ++lateCount;
			
			if(CurrentFrameCount <= 1)
				return;
			
			lateUpdates.Process();
		}
		void FixedUpdate()
		{
			CurrentFrameCount = ++fixedCount;
			
			if(CurrentFrameCount <= 1)
				return;
						
			fixedUpdates.Process();
		}
	}
}