extends Node

class_name OverworldClient

const Ow_pb = preload('ow_pb.gd')

var ws = WebSocketClient.new()

export(Dictionary) var session

var debug = false

###########################################################################
#  Constants
###########################################################################

# These are merely aliases for convenience. We should not leak Protobuf
# details to the consumer of the library

# via overworld_pb
enum gen_response_status {
	OK = Overworld_pb.gen_response.status.OK,
	ERROR = Overworld_pb.gen_response.status.ERROR,
}

# via ow_pb
enum gen_response_status {
	OK = Ow_pb.gen_response.status.OK,
	ERROR = Ow_pb.gen_response.status.ERROR,
}


###########################################################################
#  Signals
###########################################################################

signal connection(status)
signal session_id(id)
signal session_beacon(id)
signal session_pong(latency)
signal session_log(color,msg)
signal gen_response(msg,status)


###########################################################################
#  OpCodes
###########################################################################

func bytepack(opcode):
	var buf = StreamPeerBuffer.new()
	buf.big_endian=true
	buf.put_16(opcode)
	return buf.data_array

var OpCode = {
	VERSION = bytepack(16),
	SESSION_ID_REQ = bytepack(21),
	SESSION_ID = bytepack(22),
	SESSION_BEACON = bytepack(25),
	SESSION_PING = bytepack(32),
	SESSION_PONG = bytepack(33),
	SESSION_LOG = bytepack(80),
	ACCOUNT_NEW = bytepack(256),
	ACCOUNT_LOGIN = bytepack(272),
}


###########################################################################
#  Router 
###########################################################################

func route(packet):
	var opcode = packet.subarray(0,1)
	var hex_opcode = str(opcode.hex_encode())
	var payload = []
	if packet.size() > 2:
		payload = packet.subarray(2,-1)
	match opcode:
		OpCode.ACCOUNT_LOGIN:
			server_account_login(payload)
		OpCode.ACCOUNT_NEW:
			server_account_new(payload)
		OpCode.SESSION_LOG:
			server_session_log(payload)
		OpCode.SESSION_PONG:
			server_session_pong(payload)
		OpCode.SESSION_PING:
			server_session_ping(payload)
		OpCode.SESSION_BEACON:
			server_session_beacon(payload)
		OpCode.SESSION_ID:
			server_session_id(payload)
		OpCode.SESSION_ID_REQ:
			server_session_id_req(payload)
		OpCode.VERSION:
			server_version(payload)
		_:
			print("[WARNING] Unknown opcode from the server:" + hex_opcode)


###########################################################################
#  Payload unmarshalling (server packets)
###########################################################################

func unpack_gen_response(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var msg = obj.get_msg()
			var status = obj.get_status()
			var dict = {'status': status, 'msg': msg, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var msg = object.get_msg()
		var status = object.get_status()
		var dict = {'status': status, 'msg': msg, }
		return dict
func unpack_account_new(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var password = obj.get_password()
			var email = obj.get_email()
			var dict = {'email': email, 'password': password, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var password = object.get_password()
		var email = object.get_email()
		var dict = {'email': email, 'password': password, }
		return dict
func unpack_account_login(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var password = obj.get_password()
			var email = obj.get_email()
			var dict = {'email': email, 'password': password, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var password = object.get_password()
		var email = object.get_email()
		var dict = {'email': email, 'password': password, }
		return dict
func unpack_session_log(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var color = obj.get_color()
			var msg = obj.get_msg()
			var dict = {'msg': msg, 'color': color, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var color = object.get_color()
		var msg = object.get_msg()
		var dict = {'msg': msg, 'color': color, }
		return dict
func unpack_session_beacon(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var id = obj.get_id()
			var dict = {'id': id, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var id = object.get_id()
		var dict = {'id': id, }
		return dict
func unpack_session_ping(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var id = obj.get_id()
			var dict = {'id': id, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var id = object.get_id()
		var dict = {'id': id, }
		return dict
func unpack_session_pong(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var latency = obj.get_latency()
			var dict = {'latency': latency, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var latency = object.get_latency()
		var dict = {'latency': latency, }
		return dict
func unpack_session_id(object):
	if typeof(object) == TYPE_ARRAY and object != []:
		var array = []
		for obj in object:
			var id = obj.get_id()
			var dict = {'id': id, }
			array.append(dict)
		return array
	elif typeof(object) == TYPE_ARRAY and object == []:
		return []
	else:
		var id = object.get_id()
		var dict = {'id': id, }
		return dict


func server_account_login(packet):
	if debug:
		print('[DEBUG] Processing a account_login packet')
	var m = Ow_pb.gen_response.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new account_login packet')
		return
	var d = unpack_gen_response(m)
	emit_signal('gen_response',d['msg'],d['status'])


func server_account_new(packet):
	if debug:
		print('[DEBUG] Processing a account_new packet')
	var m = Ow_pb.gen_response.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new account_new packet')
		return
	var d = unpack_gen_response(m)
	emit_signal('gen_response',d['msg'],d['status'])


func server_session_log(packet):
	if debug:
		print('[DEBUG] Processing a session_log packet')
	var m = Ow_pb.session_log.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new session_log packet')
		return
	var d = unpack_session_log(m)
	emit_signal('session_log',d['color'],d['msg'])


func server_session_pong(packet):
	if debug:
		print('[DEBUG] Processing a session_pong packet')
	var m = Ow_pb.session_pong.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new session_pong packet')
		return
	var d = unpack_session_pong(m)
	emit_signal('session_pong',d['latency'])


func server_session_ping(_packet):
	print('[WARN] Received a session_ping packet')
	return

func server_session_beacon(packet):
	if debug:
		print('[DEBUG] Processing a session_beacon packet')
	var m = Ow_pb.session_beacon.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new session_beacon packet')
		return
	var d = unpack_session_beacon(m)
	emit_signal('session_beacon',d['id'])


func server_session_id(packet):
	if debug:
		print('[DEBUG] Processing a session_id packet')
	var m = Ow_pb.session_id.new()
	var result_code = m.from_bytes(packet)
	if result_code != Ow_pb.PB_ERR.NO_ERRORS:
		print('[CRITICAL] Error decoding new session_id packet')
		return
	var d = unpack_session_id(m)
	emit_signal('session_id',d['id'])


func server_session_id_req(_packet):
	print('[WARN] Received a session_id_req packet')
	return

func server_version(_packet):
	print('[WARN] Received a version packet')
	return


###########################################################################
#  Payload marshalling (client packets)
###########################################################################

func pack_session_id(obj, ref):
	ref.set_id(obj.id)

func pack_session_pong(obj, ref):
	ref.set_latency(obj.latency)

func pack_session_ping(obj, ref):
	ref.set_id(obj.id)

func pack_session_beacon(obj, ref):
	ref.set_id(obj.id)

func pack_session_log(obj, ref):
	ref.set_color(obj.color)
	ref.set_msg(obj.msg)

func pack_account_login(obj, ref):
	ref.set_password(obj.password)
	ref.set_email(obj.email)

func pack_account_new(obj, ref):
	ref.set_password(obj.password)
	ref.set_email(obj.email)

func pack_gen_response(obj, ref):
	ref.set_msg(obj.msg)
	ref.set_status(obj.status)



func version():
	send_message([], OpCode.VERSION)
	if debug:
		print('[INFO] Sent a version packet')


func session_id_req():
	send_message([], OpCode.SESSION_ID_REQ)
	if debug:
		print('[INFO] Sent a session_id_req packet')


func session_ping(id: int):
	var m = Ow_pb.session_ping.new()
	m.set_id(id)
	var payload = m.to_bytes()
	send_message(payload, OpCode.SESSION_PING)
	if debug:
		print('[INFO] Sent a session_ping packet')


func account_new(email: String, password: String):
	var m = Ow_pb.account_new.new()
	m.set_password(password)
	m.set_email(email)
	var payload = m.to_bytes()
	send_message(payload, OpCode.ACCOUNT_NEW)
	if debug:
		print('[INFO] Sent a account_new packet')


func account_login(email: String, password: String):
	var m = Ow_pb.account_login.new()
	m.set_password(password)
	m.set_email(email)
	var payload = m.to_bytes()
	send_message(payload, OpCode.ACCOUNT_LOGIN)
	if debug:
		print('[INFO] Sent a account_login packet')



############################################################################
# Various other utility and initialization functions
############################################################################

func send_message(payload, opcode):
	# Create a new packet starting with opcode, append the message if it exists,
	# then send it across the websocket.
	var packet = opcode
	if payload.empty() == true:
		# Message is empty, just send the bare opcode
		ws.get_peer(1).put_packet(packet)
	else:
		packet.append_array(payload)
		ws.get_peer(1).put_packet(packet)

############################################################################
#  Signal Handlers
############################################################################

func _ready():
	ws.connect("data_received", self, "_on_data")

# Take the host and whether or not its a TLS conncetion
func ws_connect(url: String, tls: bool):
	ws.set_verify_ssl_enabled(tls)
	print("[INFO] Attempting to connect to ", url)
	ws.connect_to_url(url+"/ws")

func ws_disconnect():
	ws.disconnect_from_host(1000,"Goodbye")

func _process(_delta):
	# Poll the socket for new data
	if ws.get_connection_status() == ws.CONNECTION_CONNECTING || ws.get_connection_status() == ws.CONNECTION_CONNECTED:
		ws.poll()

func _on_data():
	var packet = ws.get_peer(1).get_packet()
	route(packet)

func _connection_established(_protocol):
	print("[INFO] Connection established")
	emit_signal("connection", "established")

func _connection_closed(msg):
	print("[INFO] Connection closed: ", msg)
	emit_signal("connection", "closed")

func _connection_error():
	print("[INFO] Connection error")
	emit_signal("connection", "error")
	print(ws.get_peer(1))
