class_name MultiplayerClass
extends Node

const PORT: int = 4516

var peer: ENetMultiplayerPeer = null
var players: Dictionary = {}
var fuseboxes: int = 0

var playerlist_label: Label = null

var entered_username: String = ""

## Host Specific

# Hosts server
func host_server() -> void:
	peer = ENetMultiplayerPeer.new()
	if peer.create_server(PORT) != OK:
		peer = null
		return
	multiplayer.multiplayer_peer = peer
	
	if multiplayer.peer_connected.connect(_on_player_joined) != OK:
		print("Failed to connect signal")
	if multiplayer.peer_disconnected.connect(_on_player_left) != OK:
		print("Failed to connect signal")
	
	if get_tree().change_scene_to_file("res://ui/lobby.tscn") != OK:
		print("Failed to change to scene.")
	
	_on_player_joined(multiplayer.get_unique_id())
	player_username.rpc_id(1, Options.username)

# Runs when player joins
func _on_player_joined(id: int) -> void:
	players[id] = {
		"id": id,
		"username": "Waiting...",
		"loaded": false,
		"hunter": false,
		"captured": false,
		"escaped": false,
	}
	
	send_playerlist()

# Runs when player leaves
func _on_player_left(id: int) -> void:
	if !players.erase(id):
		print("Player was never registered!")
	send_playerlist()

# Receives the players username
@rpc("any_peer", "call_local", "reliable")
func player_username(username: String) -> void:
	players[multiplayer.get_remote_sender_id()].username = username
	send_playerlist()

# Prepares the label text for the playerlist on the join/host screen
func send_playerlist() -> void:
	var playerlist: String = ""
	
	for id: int in players:
		playerlist += players[id].username
		playerlist += "\n"
	
	update_playerlist.rpc(playerlist)

@rpc("any_peer", "call_local", "reliable")
func request_playerlist() -> void:
	if multiplayer.is_server():
		send_playerlist()

# Switches the scene, and waits until everyone is fully loaded.
@rpc("authority", "call_local", "reliable")
func switch_scene(path: String) -> void:
	playerlist_label = null
	if multiplayer.is_server():
		_scene_loaded.rpc_id(1)
		while true:
			var loaded: bool = true
			for id: int in players:
				if players[id].loaded == false:
					loaded = false
					break
			if loaded:
				break
			await get_tree().create_timer(0.1).timeout
		if get_tree().change_scene_to_file(path) != OK:
			print("Failed to change to scene.")
	else:
		if get_tree().change_scene_to_file(path) != OK:
			print("Failed to change to scene.")
		_scene_loaded.rpc_id(1)

# Callback to switch_scene from the remote peer
@rpc("any_peer", "call_local", "reliable")
func _scene_loaded() -> void:
	if multiplayer.is_server():
		players[multiplayer.get_remote_sender_id()].loaded = true

func _process(_delta: float) -> void:
	if multiplayer.multiplayer_peer and multiplayer.is_server() and !players.is_empty():
		var lost: bool = true
		for id: int in players:
			if !(players[id].captured or players[id].escaped) and !players[id].hunter:
				lost = false
		if lost:
			terminate_multiplayer.rpc()

## Client Specific

# Joins a server
func join_server(username: String = Options.username, ip_address: String = Options.ip_address) -> void:
	if !username:
		username = Options.username
	if !ip_address:
		ip_address = Options.ip_address
	
	entered_username = username
	
	peer = ENetMultiplayerPeer.new()
	if peer.create_client(ip_address, PORT) != OK:
		terminate_multiplayer()
		return
	multiplayer.multiplayer_peer = peer
	
	if multiplayer.connected_to_server.connect(_on_server_joined) != OK:
		print("Failed to connect signal")
	if multiplayer.connection_failed.connect(_on_server_join_failed) != OK:
		print("Failed to connect signal")

# After successful join
func _on_server_joined() -> void:
	if get_tree().change_scene_to_file("res://ui/lobby.tscn") != OK:
		print("Failed to change to scene.")
	player_username.rpc_id(1, entered_username)

# After unsuccessful join
func _on_server_join_failed() -> void:
	terminate_multiplayer()

## Shared

# Terminates all Multiplayer functionality.
@rpc("authority", "call_local", "reliable")
func terminate_multiplayer() -> void:
	multiplayer.multiplayer_peer = null
	players = {}
	fuseboxes = 0
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
	if get_tree().change_scene_to_file("res://ui/main_menu.tscn") != OK:
		print("Failed to change to scene.")

# Updates the player list
@rpc("authority", "call_local", "reliable")
func update_playerlist(list: String) -> void:
	if playerlist_label != null:
		playerlist_label.text = list

# Adds Fusebox
@rpc("authority", "call_local", "reliable")
func add_fusebox() -> void:
	fuseboxes += 1

# Removes Fusebox
@rpc("authority", "call_local", "reliable")
func remove_fusebox() -> void:
	fuseboxes -= 1
