@tool
class_name GdUnitTcpServer
extends Node

signal client_connected(client_id: int)
signal client_disconnected(client_id: int)
@warning_ignore("unused_signal")
signal rpc_data(rpc_data: RPC)

var _server: TCPServer
var _server_name: String

class TcpConnection extends GdUnitTcpNode:
	var _id: int
	var _stream: StreamPeerTCP


	func _init(tcp_server: TCPServer) -> void:
		_stream = tcp_server.take_connection()
		#_stream.set_big_endian(true)
		_id = _stream.get_instance_id()
		rpc_send(_stream, RPCClientConnect.new().with_id(_id))


	func _ready() -> void:
		server().client_connected.emit(_id)


	func close() -> void:
		if _stream != null and _stream.get_status() == StreamPeerTCP.STATUS_CONNECTED:
			_stream.disconnect_from_host()
			queue_free()


	func id() -> int:
		return _id


	func server() -> GdUnitTcpServer:
		return get_parent()


	func _process(_delta: float) -> void:
		if _stream == null or _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED:
			return
		receive_packages(_stream, func(rpc_data: RPC) -> void:
			server().rpc_data.emit(rpc_data)
			# is client disconnecting we close the server after a timeout of 1 second
			if rpc_data is RPCClientDisconnect:
				close()
		)


	func console(_value: Variant) -> void:
		#print_debug("TCP Server:		", value)
		pass


func _init(server_name := "GdUnit4 TCP Server") -> void:
	_server_name = server_name


func _ready() -> void:
	_server = TCPServer.new()
	client_connected.connect(_on_client_connected)
	client_disconnected.connect(_on_client_disconnected)


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		stop()


func start(server_port := GdUnitServerConstants.GD_TEST_SERVER_PORT) -> GdUnitResult:
	var err := OK
	for retry in GdUnitServerConstants.DEFAULT_SERVER_START_RETRY_TIMES:
		err = _server.listen(server_port, "127.0.0.1")
		if err != OK:
			prints("GdUnit4: Can't establish server checked port: %d, Error: %s" % [server_port, error_string(err)])
			server_port += 1
			prints("GdUnit4: Retry (%d) ..." % retry)
		else:
			break
	if err != OK:
		if err == ERR_ALREADY_IN_USE:
			return GdUnitResult.error("GdUnit4: Can't establish server, the server is already in use. Error: %s, " % error_string(err))
		return GdUnitResult.error("GdUnit4: Can't establish server. Error: %s." % error_string(err))
	console("Successfully started checked port: %d" % server_port)
	return GdUnitResult.success(server_port)


func stop() -> void:
	if _server:
		_server.stop()
	for connection in get_children():
		if connection is TcpConnection:
			@warning_ignore("unsafe_method_access")
			connection.close()
			remove_child(connection)
	_server = null


func disconnect_client(client_id: int) -> void:
	client_disconnected.emit(client_id)


func _process(_delta: float) -> void:
	if _server != null and not _server.is_listening():
		return
	# check if connection is ready to be used
	if _server != null and _server.is_connection_available():
		add_child(TcpConnection.new(_server))


func _on_client_connected(client_id: int) -> void:
	console("Client connected %d" % client_id)


func _on_client_disconnected(client_id: int) -> void:
	for connection in get_children():
		@warning_ignore("unsafe_method_access")
		if connection is TcpConnection and connection.id() == client_id:
			@warning_ignore("unsafe_method_access")
			connection.close()
			remove_child(connection)


func console(value: Variant) -> void:
	print(_server_name, ":	", value)
