extends Node
## This class implements a handshake protocol over UDP for multiple classes.

class HandshakeStatus:
	var did_read: bool = false
	var did_write: bool = false
	var did_handshake: bool = false
	
	func _to_string():
		return "$" + \
			("r" if did_read else "-") + \
			("w" if did_write else "-") + \
			("x" if did_handshake else "-")
	
	static func from_string(str: String) -> HandshakeStatus:
		var result = HandshakeStatus.new()
		result.did_read = str.contains("r")
		result.did_write = str.contains("w")
		result.did_handshake = str.contains("x")
		return result

## Conduct handshake over a [PacketPeer] instance.
func over_packet_peer(peer: PacketPeer, timeout: float = 8.0, frequency: float = 0.1) -> Error:
	var result = ERR_TIMEOUT
	var status = HandshakeStatus.new()
	status.did_write = true

	while timeout >= 0:
		# Process incoming packets
		while peer.get_available_packet_count() > 0:
			var packet = peer.get_packet()
			var incoming_status = HandshakeStatus.from_string(packet.get_string_from_ascii())
			
			# We did get a packet, so that means we read them
			status.did_read = true
			
			# They already read us, so that's a handshake
			if incoming_status.did_read:
				status.did_handshake = true
			
			# Both peers ack'd the handshake, we've succeeded
			if incoming_status.did_handshake and status.did_handshake:
				result = OK
				timeout = 0 # Break outer loop
		
		# Send our state
		peer.put_packet(status.to_string().to_ascii_buffer())
		
		await get_tree().create_timer(frequency).timeout
		timeout -= frequency
	
	# If we've read them and we know we've sent data to them successfully,
	# we're *probably* good to connect, even if the handshake did not actually
	# go through.
	# Depending on the context, the calling code may decide to connect anyway,
	# based on this return value. 
	if status.did_read and status.did_write and not status.did_handshake:
		result = ERR_BUSY
	
	return result

## Conduct handshake over an [ENetConnection].
##
## If the connection is closed during the handshake, errors will be printed
## until the end of the timeout. To avoid errors, use [method over_enet_peer]
## when possible.
##
## [i]Note[/i] that this is not a full-fledged handshake, since we can't receive
## data over the connection. Instead, we just pretend that the handshake is 
## successful on our end and blast that status for a given time.
func over_enet(connection: ENetConnection, address: String, port: int, timeout: float = 8.0, frequency: float = 0.1) -> Error:
	var result = OK
	var status = HandshakeStatus.new()

	# Pretend this is a perfectly healthy handshake, since we can't receive data here
	status.did_write = true
	status.did_read = true
	status.did_handshake = true
	
	while timeout >= 0:
		# Send our state
		connection.socket_send(address, port, status.to_string().to_ascii_buffer())
		
		await get_tree().create_timer(frequency).timeout
		timeout -= frequency
	
	return result

## Conduct handshake over an [ENetMultiplayerPeer].
##
## Compared to [method over_enet], using an [ENetMultiplayerPeer] allows
## checking for closed connections. If the peer is closed during the handshake,
## this method will gracefully fail.
##
## [i]Note[/i] that this is not a full-fledged handshake, since we can't receive
## data over the connection. Instead, we just pretend that the handshake is 
## successful on our end and blast that status for a given time.
func over_enet_peer(peer: ENetMultiplayerPeer, address: String, port: int, timeout: float = 8.0, frequency: float = 0.1) -> Error:
	var result = OK
	var status = HandshakeStatus.new()

	# Pretend this is a perfectly healthy handshake, since we can't receive data here
	status.did_write = true
	status.did_read = true
	status.did_handshake = true
	
	while timeout >= 0:
		if peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED:
			return ERR_CONNECTION_ERROR
			
		# Send our state
		peer.host.socket_send(address, port, status.to_string().to_ascii_buffer())
		
		await get_tree().create_timer(frequency).timeout
		timeout -= frequency
	
	return result
