# It connects to all signals of given emitter and collects received signals and arguments
# The collected signals are cleand finally when the emitter is freed.
class_name GdUnitSignalCollector
extends RefCounted

const NO_ARG :Variant = GdUnitConstants.NO_ARG
const SIGNAL_BLACK_LIST = []#["tree_exiting", "tree_exited", "child_exiting_tree"]

# {
#	emitter<Object> : {
#		signal_name<String> : [signal_args<Array>],
#		...
#	}
# }
var _collected_signals :Dictionary = {}


func clear() -> void:
	for emitter in _collected_signals.keys():
		if is_instance_valid(emitter):
			unregister_emitter(emitter)


# connect to all possible signals defined by the emitter
# prepares the signal collection to store received signals and arguments
func register_emitter(emitter :Object):
	if is_instance_valid(emitter):
		# check emitter is already registerd
		if _collected_signals.has(emitter):
			return
		_collected_signals[emitter] = Dictionary()
		# connect to 'tree_exiting' of the emitter to finally release all acquired resources/connections.
		if emitter is Node and !emitter.tree_exiting.is_connected(unregister_emitter):
			emitter.tree_exiting.connect(unregister_emitter.bind(emitter))
		# connect to all signals of the emitter we want to collect
		for signal_def in emitter.get_signal_list():
			var signal_name = signal_def["name"]
			# set inital collected to empty
			if not is_signal_collecting(emitter, signal_name):
				_collected_signals[emitter][signal_name] = Array()
			if SIGNAL_BLACK_LIST.find(signal_name) != -1:
				continue
			if !emitter.is_connected(signal_name, _on_signal_emmited):
				var err := emitter.connect(signal_name, _on_signal_emmited.bind(emitter, signal_name))
				if err != OK:
					push_error("Can't connect to signal %s on %s. Error: %s" % [signal_name, emitter, error_string(err)])


# unregister all acquired resources/connections, otherwise it ends up in orphans
# is called when the emitter is removed from the parent
func unregister_emitter(emitter :Object):
	if is_instance_valid(emitter):
		for signal_def in emitter.get_signal_list():
			var signal_name = signal_def["name"]
			if emitter.is_connected(signal_name, _on_signal_emmited):
				emitter.disconnect(signal_name, _on_signal_emmited.bind(emitter, signal_name))
		_collected_signals.erase(emitter)


# receives the signal from the emitter with all emitted signal arguments and additional the emitter and signal_name as last two arguements
func _on_signal_emmited( arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG, arg10=NO_ARG, arg11=NO_ARG):
	var signal_args = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11], NO_ARG)
	# extract the emitter and signal_name from the last two arguments (see line 61 where is added)
	var signal_name :String = signal_args.pop_back()
	var emitter :Object = signal_args.pop_back()
	#prints("_on_signal_emmited:", emitter, signal_name, signal_args)
	if is_signal_collecting(emitter, signal_name):
		_collected_signals[emitter][signal_name].append(signal_args)


func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array):
	#_debug_signal_list("before claer");
	if _collected_signals.has(emitter):
		var signals_by_emitter = _collected_signals[emitter]
		if signals_by_emitter.has(signal_name):
			_collected_signals[emitter][signal_name].erase(signal_args)
	#_debug_signal_list("after claer");


func is_signal_collecting(emitter :Object, signal_name :String) -> bool:
	return _collected_signals.has(emitter) and _collected_signals[emitter].has(signal_name)


func match(emitter :Object, signal_name :String, args :Array) -> bool:
	#prints("match", signal_name,  _collected_signals[emitter][signal_name]);
	if _collected_signals.is_empty() or not _collected_signals.has(emitter):
		return false
	for received_args in _collected_signals[emitter][signal_name]:
		#prints("testing", signal_name, received_args, "vs", args)
		if GdObjects.equals(received_args, args):
			return true
	return false


func _debug_signal_list(message :String):
	prints("-----", message, "-------")
	prints("senders {")
	for emitter in _collected_signals:
		prints("\t", emitter)
		for signal_name in _collected_signals[emitter]:
			var args = _collected_signals[emitter][signal_name]
			prints("\t\t", signal_name, args)
	prints("}")
