package haxe;

#if (target.threaded && !cppia)
import sys.thread.Lock;
import sys.thread.Mutex;
import sys.thread.Thread;
#elseif sys
private class Lock {
	public function new() {}

	public inline function release() {}

	public inline function wait(?t:Float) {}
}

private class Mutex {
	public function new() {}

	public inline function acquire() {}

	public inline function release() {}
}

private class Thread {
	public static function create(f:Void->Void) {
		f();
	}
}
#end

/**
	If `haxe.MainLoop` is kept from DCE, then we will insert an `haxe.EntryPoint.run()` call just at then end of `main()`.
	This class can be redefined by custom frameworks so they can handle their own main loop logic.
**/
class EntryPoint {
	#if sys
		static var mutex = new Mutex();
		#if (target.threaded && !cppia)
			static var mainThread:Thread = Thread.current();
		#else
			static var sleepLock = new Lock();
		#end
	#end
	static var pending = new Array<Void->Void>();
	public static var threadCount(default, null):Int = 0;

	/**
		Wakeup a sleeping `run()`
	**/
	public static function wakeup() {
		#if (sys && !(target.threaded && !cppia))
		sleepLock.release();
		#end
	}

	public static function runInMainThread(f:Void->Void) {
		#if sys
			#if (target.threaded && !cppia)
				mainThread.events.run(f);
			#else
				mutex.acquire();
				pending.push(f);
				mutex.release();
				wakeup();
			#end
		#else
		pending.push(f);
		#end
	}

	public static function addThread(f:Void->Void) {
		#if sys
		mutex.acquire();
		threadCount++;
		mutex.release();
		#if (target.threaded && !cppia)
			mainThread.events.promise();
		#end
		Thread.create(function() {
			f();
			mutex.acquire();
			threadCount--;
			if (threadCount == 0)
				wakeup();
			mutex.release();
			#if (target.threaded && !cppia)
				mainThread.events.runPromised(() -> {});
			#end
		});
		#else
		threadCount++;
		pending.push(function() {
			f();
			threadCount--;
		});
		#end
	}

	static function processEvents():Float {
		#if (target.threaded && !cppia)
		return -1;
		#else
		// flush all pending calls
		while (true) {
			#if sys
			mutex.acquire();
			var f = pending.shift();
			mutex.release();
			#else
			var f = pending.shift();
			#end
			if (f == null)
				break;
			f();
		}
		var time = @:privateAccess MainLoop.tick();
		if (!MainLoop.hasEvents() && threadCount == 0)
			return -1;
		return time;
		#end
	}

	/**
		Start the main loop. Depending on the platform, this can return immediately or will only return when the application exits.
	**/
	@:keep public static function run() @:privateAccess {
		#if js
		var nextTick = processEvents();
		inline function setTimeoutNextTick() {
			if (nextTick >= 0) {
				(untyped setTimeout)(run, nextTick * 1000);
			}
		}
		#if nodejs
		setTimeoutNextTick();
		#else
		if(js.Lib.typeof(js.Browser.window) != 'undefined') {
			var window:Dynamic = js.Browser.window;
			var rqf:Dynamic = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
			if(rqf != null) {
				rqf(run);
			} else {
				setTimeoutNextTick();
			}
		} else {
			setTimeoutNextTick();
		}
		#end
		#elseif flash
		flash.Lib.current.stage.addEventListener(flash.events.Event.ENTER_FRAME, function(_) processEvents());
		#elseif (target.threaded && !cppia)
		//everything is delegated to sys.thread.EventLoop
		#elseif sys
		while (true) {
			var nextTick = processEvents();
			if (nextTick < 0)
				break;
			if (nextTick > 0)
				sleepLock.wait(nextTick); // wait until nextTick or wakeup() call
		}
		#else
		// no implementation available, let's exit immediately
		#end
	}
}
