extends Node

signal ms_updated(action, data)
signal left()

# Hosting info
const SERVER_PORT = 5513
const MAX_PLAYERS = 30
var port = SERVER_PORT

# Master server data
const MASTER_SERVER_ADDR = "fgms.zyg.ovh"
const MASTER_SERVER_UDP_PORT = 9434
const MS_GAME_CODE = "odyssey-0-a1"

# Master server entry
var ms_active = false
var ms_key = ""
var server_name = ""

export var player_name = ""

var player_info = {}

func _ready():
	player_name = "tider-" + str(randi() % 1000)

func bind_events():
	get_tree().connect("network_peer_connected", self, "_player_connected")
	get_tree().connect("network_peer_disconnected", self, "_player_disconnected")
	get_tree().connect("connected_to_server", self, "_connected_ok")
	get_tree().connect("connection_failed", self, "_connected_fail")
	get_tree().connect("server_disconnected", self, "_server_disconnected")

func punch_nat():
	var socketUDP = PacketPeerUDP.new()
	if socketUDP.listen(SERVER_PORT) != OK:
		push_error("error listening on port: " + str(SERVER_PORT))
	socketUDP.set_dest_address(MASTER_SERVER_ADDR, MASTER_SERVER_UDP_PORT)
	socketUDP.put_packet("hi server!".to_ascii())
	# Poll for answer from server, since godot doesn't have events for UDP packets (ノへ￣、)
	var failed = 0
	while failed < 5:
		yield(get_tree().create_timer(.3), "timeout")
		if socketUDP.get_available_packet_count() < 1:
			failed += 1
			print("no reply (attempt #", failed, ")")
			continue
		port = socketUDP.get_var(false)
		print("Received ", port)
		break
	socketUDP.close()

func discover_upnp():
	var upnp = UPNP.new()
	upnp.discover(2000, 2, "InternetGatewayDevice")
	return upnp.add_port_mapping(SERVER_PORT)

func host():
	print("Running UPNP magicks")
	if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS:
		print("UPNP mapping added")
	else:
		push_warning("UPNP magicks fail, punching NAT in the face")
		yield(punch_nat(), "completed")
	
	bind_events()
	var peer = NetworkedMultiplayerENet.new()
	var server_res = peer.create_server(port, MAX_PLAYERS)
	if server_res != OK:
		match server_res:
			ERR_CANT_CREATE:
				push_error("Can't create server")
			ERR_ALREADY_IN_USE:
				push_error("Already in use")
		return
	get_tree().network_peer = peer
	print("Hosting")
	server_name = player_name + "'s server"
	player_info[1] = { name=player_name }

	get_tree().change_scene("res://Scenes/Game.tscn")
	
	# Add to master server
	create_ms_entry()

func join(addr: String):
	bind_events()
	var peer = NetworkedMultiplayerENet.new()
	peer.create_client(addr, SERVER_PORT)
	get_tree().network_peer = peer
	print("Connecting to ", addr)

func leave():
	var peer = get_tree().network_peer
	if get_tree().is_network_server():
		# Tell MS we're leaving
		if ms_active:
			_ms_request("remove", { "key": ms_key })
			yield(self, "ms_updated")
	elif peer != null:
		#TODO Send leave message
		pass
	get_tree().network_peer = null
	emit_signal("left")
func _player_connected(id):
	rpc_id(id, "register_player", player_name)
	if get_tree().is_network_server():
		pass
		#rpc_id(id, "spawn_players", mapLoader.map.all_players())

func _player_disconnected(id):
	print(player_info[id].name, " (", id, ") disconnected")
	player_info.erase(id)

func _connected_ok():
	print("Connected to server")

func _server_disconnected():
	print("Disconnected from server")

func _connected_fail():
	push_warning("Connection failed")

remote func register_player(username: String):
	var id = get_tree().get_rpc_sender_id()
	player_info[id] = { name=username }
	print(player_info[id].name, " (", id, ") connected")

remote func spawn_players(players):
	for player_id in players:
		pass
		#mapLoader.map.spawn_player(player_id)
	# Spawn myself as well
	#mapLoader.map.rpc("spawn_player", get_tree().network_peer.get_unique_id())

func _ms_request(endpoint: String, data):
	var http_request = HTTPRequest.new()
	add_child(http_request)
	http_request.connect("request_completed", self, "_ms_response", [endpoint])
	print_debug("Telling ms to " + endpoint)
	var error = http_request.request(
		"https://" + MASTER_SERVER_ADDR + "/" + endpoint,
		["Content-Type: application/json"],
		true, HTTPClient.METHOD_POST, JSON.print(data))
	if error != OK:
		push_error("An error occurred in the HTTP request.")

func get_game_data():
	return {
		"name": server_name,
		"port": port,
		"max_players": MAX_PLAYERS,
		"players": player_info.size()
	}

func create_ms_entry():
	_ms_request("new", { 
		"game_id": MS_GAME_CODE,
		"data": get_game_data()
	})
	update_ms_entry()

func update_ms_entry():
	if ms_active:
		_ms_request("update", { 
			"key": ms_key,
			"data": get_game_data()
		})
		yield(get_tree().create_timer(20), "timeout")

func _ms_response(_result: int, response_code: int, _headers: PoolStringArray, body: PoolByteArray, action: String):
	print_debug("MS said " + str(response_code))
	if response_code > 299:
		push_error("ms action \"" + action + "\" returned error: " + str(response_code) + " - " + body.get_string_from_utf8())
		return
	var json = JSON.parse(body.get_string_from_utf8())
	match action:
		"new":
			if json.result.ok:
				ms_active = true
				ms_key = json.result.key
	emit_signal("ms_updated", action, json.result)

func _notification(what):
	# Are we quittin son?
	if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
		leave()
		yield(self, "left")
