extends Node

const DEFAULT_PORT: int = 7777
const MAX_PLAYERS: int = 4

signal peer_connected(id: int)
signal peer_disconnected(id: int)
signal connected_to_server()
signal connection_failed()
signal server_disconnected()
signal world_ready()
signal peer_world_loaded(peer_id: int)

var player_names: Dictionary = {}
var local_name: String = "Player"
var _expected_peers: Array = []
var _ready_peers: Array = []

func _ready() -> void:
  multiplayer.peer_connected.connect(_on_peer_connected)
  multiplayer.peer_disconnected.connect(_on_peer_disconnected)
  multiplayer.connected_to_server.connect(_on_connected_to_server)
  multiplayer.connection_failed.connect(_on_connection_failed)
  multiplayer.server_disconnected.connect(_on_server_disconnected)

func host(port: int = DEFAULT_PORT, max_clients: int = MAX_PLAYERS - 1) -> bool:
  var peer := ENetMultiplayerPeer.new()
  var err := peer.create_server(port, max_clients)
  if err != OK:
    push_error("Net: failed to host on port %d (err=%d)" % [port, err])
    return false
  multiplayer.multiplayer_peer = peer
  player_names[1] = local_name
  return true

func join(address: String, port: int = DEFAULT_PORT) -> bool:
  var peer := ENetMultiplayerPeer.new()
  var err := peer.create_client(address, port)
  if err != OK:
    push_error("Net: failed to join %s:%d (err=%d)" % [address, port, err])
    return false
  multiplayer.multiplayer_peer = peer
  return true

func host_singleplayer() -> bool:
  if multiplayer.multiplayer_peer != null:
    multiplayer.multiplayer_peer.close()
  var peer := OfflineMultiplayerPeer.new()
  multiplayer.multiplayer_peer = peer
  player_names.clear()
  player_names[1] = local_name
  return true

func disconnect_net() -> void:
  if multiplayer.multiplayer_peer != null:
    multiplayer.multiplayer_peer.close()
    multiplayer.multiplayer_peer = null
  player_names.clear()

func is_host() -> bool:
  return multiplayer.multiplayer_peer == null or multiplayer.is_server()

func is_authority_for(node: Node) -> bool:
  return node.is_multiplayer_authority()

func local_id() -> int:
  if multiplayer.multiplayer_peer == null:
    return 1
  return multiplayer.get_unique_id()

func _on_peer_connected(id: int) -> void:
  peer_connected.emit(id)
  if is_host():
    _register_name.rpc_id(id, 1, local_name)

func _on_peer_disconnected(id: int) -> void:
  player_names.erase(id)
  peer_disconnected.emit(id)

func _on_connected_to_server() -> void:
  player_names[1] = "Host"
  player_names[multiplayer.get_unique_id()] = local_name
  _register_name.rpc(multiplayer.get_unique_id(), local_name)
  connected_to_server.emit()

func _on_connection_failed() -> void:
  multiplayer.multiplayer_peer = null
  connection_failed.emit()

func _on_server_disconnected() -> void:
  multiplayer.multiplayer_peer = null
  player_names.clear()
  server_disconnected.emit()

@rpc("any_peer", "reliable", "call_local")
func _register_name(peer_id: int, name: String) -> void:
  player_names[peer_id] = name

func mark_world_loaded() -> void:
  if multiplayer.multiplayer_peer == null:
    _expected_peers = [1]
    _ready_peers = [1]
    world_ready.emit()
    return
  if is_host():
    _expected_peers = [1]
    for p in multiplayer.get_peers():
      _expected_peers.append(p)
    _ready_peers = [1]
    if _all_ready():
      _broadcast_ready.rpc()
  else:
    _client_world_loaded.rpc_id(1, multiplayer.get_unique_id())

@rpc("any_peer", "reliable")
func _client_world_loaded(peer_id: int) -> void:
  if not is_host():
    return
  if not peer_id in _expected_peers:
    _expected_peers.append(peer_id)
  if not peer_id in _ready_peers:
    _ready_peers.append(peer_id)
  peer_world_loaded.emit(peer_id)
  if _all_ready():
    _broadcast_ready.rpc()

@rpc("authority", "reliable")
func _load_scene_for_peer(path: String) -> void:
  GameState.change_scene(path)

func tell_peer_to_load_scene(peer_id: int, path: String) -> void:
  if not is_host():
    return
  _load_scene_for_peer.rpc_id(peer_id, path)

@rpc("authority", "reliable", "call_local")
func _broadcast_ready() -> void:
  world_ready.emit()

func _all_ready() -> bool:
  for p in _expected_peers:
    if not p in _ready_peers:
      return false
  return true

func reset_world_ready() -> void:
  _expected_peers.clear()
  _ready_peers.clear()
