extends Node

class AwaitLogger:
	var _time_waited = 0.0
	var logger = GutUtils.get_logger()
	var waiting_on = "nothing"
	var logged_initial_message = false
	var wait_log_delay := 1.0
	var disabled = false

	func waited(x):
		_time_waited += x
		if(!logged_initial_message and _time_waited >= wait_log_delay):
			log_it()
			logged_initial_message = true


	func reset():
		_time_waited = 0.0
		logged_initial_message = false


	func log_it():
		if(!disabled):
			var msg = str("--- Awaiting ", waiting_on, " ---")
			logger.wait_msg(msg)




signal timeout
signal wait_started

var await_logger = AwaitLogger.new()
var _wait_time := 0.0
var _wait_process_frames := 0
var _wait_physics_frames := 0
var _signal_to_wait_on = null

var _predicate_method = null
var _waiting_for_predicate_to_be = null

var _predicate_time_between := 0.0
var _predicate_time_between_elpased := 0.0

var _elapsed_time := 0.0
var _elapsed_frames := 0

var _did_last_wait_timeout = false
var did_last_wait_timeout = false :
	get: return _did_last_wait_timeout
	set(val): push_error("Cannot set did_last_wait_timeout")



func _ready() -> void:
	get_tree().process_frame.connect(_on_tree_process_frame)
	get_tree().physics_frame.connect(_on_tree_physics_frame)


func _on_tree_process_frame():
	# Count frames here instead of in _process so that tree order never
	# makes a difference and the count/signaling happens outside of
	# _process being called.
	if(_wait_process_frames > 0):
		_elapsed_frames += 1
		if(_elapsed_frames > _wait_process_frames):
			_end_wait()


func _on_tree_physics_frame():
	# Count frames here instead of in _physics_process so that tree order never
	# makes a difference and the count/signaling happens outside of
	# _physics_process being called.
	if(_wait_physics_frames != 0):
		_elapsed_frames += 1
		if(_elapsed_frames > _wait_physics_frames):
			_end_wait()


func _physics_process(delta):
	if(is_waiting()):
		await_logger.waited(delta)

	if(_wait_time != 0.0):
		_elapsed_time += delta
		if(_elapsed_time >= _wait_time):
			_end_wait()

	if(_predicate_method != null):
		_predicate_time_between_elpased += delta
		if(_predicate_time_between_elpased >= _predicate_time_between):
			_predicate_time_between_elpased = 0.0
			var result = _predicate_method.call()
			if(_waiting_for_predicate_to_be == false):
				if(typeof(result) != TYPE_BOOL or result != true):
					_end_wait()
			else:
				if(typeof(result) == TYPE_BOOL and result == _waiting_for_predicate_to_be):
					_end_wait()


func _end_wait():
	await_logger.reset()
	# Check for time before checking for frames so that the extra frames added
	# when waiting on a signal do not cause a false negative for timing out.
	if(_wait_time > 0):
		_did_last_wait_timeout = _elapsed_time >= _wait_time
	elif(_wait_physics_frames > 0):
		_did_last_wait_timeout = _elapsed_frames >= _wait_physics_frames
	elif(_wait_process_frames > 0):
		_did_last_wait_timeout = _elapsed_frames >= _wait_process_frames

	if(_signal_to_wait_on != null and \
	   is_instance_valid(_signal_to_wait_on.get_object()) and \
	   _signal_to_wait_on.is_connected(_signal_callback)):
		_signal_to_wait_on.disconnect(_signal_callback)

	_wait_process_frames = 0
	_wait_time = 0.0
	_wait_physics_frames = 0
	_signal_to_wait_on = null
	_predicate_method = null
	_elapsed_time = 0.0
	_elapsed_frames = 0
	timeout.emit()


const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'
func _signal_callback(
		_arg1=ARG_NOT_SET, _arg2=ARG_NOT_SET, _arg3=ARG_NOT_SET,
		_arg4=ARG_NOT_SET, _arg5=ARG_NOT_SET, _arg6=ARG_NOT_SET,
		_arg7=ARG_NOT_SET, _arg8=ARG_NOT_SET, _arg9=ARG_NOT_SET):

	_signal_to_wait_on.disconnect(_signal_callback)
	# DO NOT _end_wait here.  For other parts of the test to get the signal that
	# was waited on, we have to wait for another frames.  For example, the
	# signal_watcher doesn't get the signal in time if we don't do this.
	_wait_process_frames = 1


func wait_seconds(x, msg=''):
	await_logger.waiting_on = str(x, " seconds ", msg)
	_did_last_wait_timeout = false
	_wait_time = x
	wait_started.emit()


func wait_process_frames(x, msg=''):
	await_logger.waiting_on = str(x, " idle frames ", msg)
	_did_last_wait_timeout = false
	_wait_process_frames = x
	wait_started.emit()


func wait_physics_frames(x, msg=''):
	await_logger.waiting_on = str(x, " physics frames ", msg)
	_did_last_wait_timeout = false
	_wait_physics_frames = x
	wait_started.emit()


func wait_for_signal(the_signal : Signal, max_time, msg=''):
	await_logger.waiting_on = str("signal ", the_signal.get_name(), " or ", max_time, "s ", msg)
	_did_last_wait_timeout = false
	the_signal.connect(_signal_callback)
	_signal_to_wait_on = the_signal
	_wait_time = max_time
	wait_started.emit()


func wait_until(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):
	await_logger.waiting_on = str("callable to return TRUE or ", max_time, "s.  ", msg)
	_predicate_time_between = time_between_calls
	_predicate_method = predicate_function
	_wait_time = max_time

	_waiting_for_predicate_to_be = true
	_predicate_time_between_elpased = 0.0
	_did_last_wait_timeout = false

	wait_started.emit()


func wait_while(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):
	await_logger.waiting_on = str("callable to return FALSE or ", max_time, "s.  ", msg)
	_predicate_time_between = time_between_calls
	_predicate_method = predicate_function
	_wait_time = max_time

	_waiting_for_predicate_to_be = false
	_predicate_time_between_elpased = 0.0
	_did_last_wait_timeout = false

	wait_started.emit()


func is_waiting():
	return _wait_time != 0.0 || \
		_wait_physics_frames != 0 || \
		_wait_process_frames != 0
