class ThreadManager extends Object:
	
	var _thread_registry_lock : ReadWriteLock
	var _thread_registry_dict : Dictionary
	var _thread_registry : Tuple
	
	var _mutex : RLock = RLock.new()
	var _cond : ConditionalVariable = ConditionalVariable.new()
	var _cleanup_thread : Thread = Thread.new()
	var _shutdown : bool = false
	var _finished_threads : Array[StartableBase]
	
	var THREAD_REGISTRY : Tuple:
		get: return self._thread_registry
	
	func _init():
		self._thread_registry_lock = ReadWriteLock.new()
		self._thread_registry_dict = {}
		self._thread_registry = Tuple.new([
			self._thread_registry_lock,
			self._thread_registry_dict
		])
		
		self._cleanup_thread.start(self._cleanup)
	
	func stop_and_join():
		self._mutex.lock()
		self._shutdown = true
		self._mutex.unlock()
		
		self._cond.notify_all()
		self._cleanup_thread.wait_to_finish()
		
		if not self._finished_threads.is_empty():
			assert(false and "should not happen!".length())
	
	## This method adds the thread object to the cleanup thread
	## allowing it to be joined and disposed properly.
	## 
	## This is mainly done due to Godot's [method Thread.wait_to_finish] shinanigans.
	func finish(thread : StartableBase):
		var __ = LockGuard.new(self._mutex)
		self._finished_threads.push_back(thread)
		self._cond.notify_all()
	
	func _cleanup():
		while true:
			var thread : StartableBase = null
			if true:
				var __ = LockGuard.new(self._mutex)
				self._cond.wait_pred(self._mutex, func(): 
					return self._shutdown or !self._finished_threads.is_empty())
				
				if (self._shutdown):
					break
				
				thread = self._finished_threads.back()
				self._finished_threads.pop_back()
			
			thread.wait_to_finish()


## Links the [Thread] instance to a [Callable] before starting it.
class StartableThread extends StartableBase:
	var _thread : Thread
	var _target : Callable
	var _priority : int
	var _started : bool
	var _joined : AtomicFlag
	
	var thread : Thread:
		get: return self._thread
	
	func _init(target : Callable, priority = Thread.PRIORITY_NORMAL):
		self._thread = Thread.new()
		self._target = target
		self._priority = priority
		self._started = false
		self._joined = AtomicFlag.new()
	
	func _register_thread():
		var id = OS.get_thread_caller_id()
		var l : ReadWriteLock = GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(0)
		l.w_lock()
		GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(1)[id] = self._thread
		l.w_unlock()
	
	func _deregister_thread():
		var id = OS.get_thread_caller_id()
		var l : ReadWriteLock = GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(0)
		l.w_lock()
		GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(1).erase(id)
		l.w_unlock()
	
	func start():
		if self._started:
			GDRx.raise_message("Thread already started!")
			return
		
		var run = func():
			Thread.set_thread_safety_checks_enabled(false)
			self._register_thread()
			self._target.call()
			self._deregister_thread()
		
		self._started = true
		self._thread.start(run, self._priority)
	
	func wait_to_finish():
		if self._is_caller():
			GDRx.THREAD_MANAGER.finish(self)
			return
		if self._joined.test_and_set():
			return
		self._thread.wait_to_finish()
		self._thread = null
		self._target = GDRx.basic.noop
	
	func _is_caller() -> bool:
		return str(OS.get_thread_caller_id()) == self._thread.get_id()

func default_thread_factory(target : Callable) -> StartableThread:
	return StartableThread.new(target)

## Dummy class to represent the main [Thread] instance
class MainThreadDummy_ extends Thread:
	@warning_ignore("native_method_override")
	func start(_callable : Callable, _priority : Priority = PRIORITY_NORMAL) -> Error:
		GDRx.raise_message("Do not launch the Main Thread Dummy!")
		return FAILED
	@warning_ignore("native_method_override")
	func wait_to_finish() -> Variant:
		GDRx.raise_message("Do not join the Main Thread Dummy!")
		return null
	func _to_string():
		return "MAIN_THREAD::" + str(GDRx.MAIN_THREAD_ID)
	@warning_ignore("native_method_override")
	func get_id() -> String:
		return str(GDRx.MAIN_THREAD_ID)
	@warning_ignore("native_method_override")
	func is_started() -> bool:
		return true
	@warning_ignore("native_method_override")
	func is_alive() -> bool:
		return true

## A naive Atomic Flag
class AtomicFlag:
	var _flag : bool
	var _mutex : Mutex
	
	func _init():
		self._flag = false
		self._mutex = Mutex.new()
	
	func test_and_set() -> bool:
		var res : bool
		self._mutex.lock()
		res = self._flag
		self._flag = true
		self._mutex.unlock()
		return res
	
	func clear():
		self._mutex.lock()
		self._flag = false
		self._mutex.unlock()
