class_name Game
extends Node2D

signal game_paused
signal game_resumed
signal turn_started
signal round_started
signal shuffle_sync_recieved

enum PlayerType {HUMAN, BOT}

const WORKER_DECK_SAVE_PATH = "user://worker_deck.json"
const CLIENT_DECK_SAVE_PATH = "user://client_deck.json"

@export var worker_deck: Deck
@export var client_deck: Deck
@export var worker_discard: Deck
@export var client_discard: Deck
@export var seat1: Node2D
@export var seat2: Node2D
@export var seat3: Node2D
@export var seat4: Node2D

var networked_controllers: Array[HumanController] = []
var current_player := 0
var players: Array[PlayerController] = []
var player_boards: Array[PlayerBoard] = []
var readied_players: Array[int] = []
var finished_players: Array[int] = []
var round_number := -1
var turn_number := 0

var _worker_scene = preload("res://Scenes/worker_card.tscn")
var _client_scene = preload("res://Scenes/client_card.tscn")
var _player_scene = preload("res://Scenes/player_board.tscn")
var _human_scene = preload("res://Scenes/human_player.tscn")
var _bot_scene = preload("res://Scenes/bot_player.tscn")


func _ready() -> void:
	_load_workers()
	_load_clients()


func _load_workers():
	if !FileAccess.file_exists(WORKER_DECK_SAVE_PATH):
		return
	var save_game = FileAccess.open(WORKER_DECK_SAVE_PATH, FileAccess.READ)
	var card_dict = JSON.parse_string(save_game.get_line())
	for key in card_dict:
		var value = card_dict[key]
		var card_instance = _worker_scene.instantiate()
		#JSON only returns floats so we have to get ints out of the dict
		var bonuses = []
		for x in value.slice(1, value.size()):
			bonuses.append(int(x))
		card_instance.setup(key, int(value[0]), bonuses)
		add_child(card_instance)
		worker_deck.place(card_instance)
	worker_deck.shuffle()


func _load_clients():
	if !FileAccess.file_exists(CLIENT_DECK_SAVE_PATH):
		return
	var save_game = FileAccess.open(CLIENT_DECK_SAVE_PATH, FileAccess.READ)
	var card_dict = JSON.parse_string(save_game.get_line())
	for key in card_dict:
		var value = card_dict[key]
		var card_instance = _client_scene.instantiate()
		#JSON only returns floats so we have to get ints out of the dict
		var bool_array = []
		var int_array = []
		for x in value.slice(1, 5):
			bool_array.append(bool(x))
		for x in value.slice(5, value.size()):
			int_array.append(int(x))
		add_child(card_instance)
		card_instance.setup(key, int(value[0]), bool_array, int_array)
		client_deck.place(card_instance)
	client_deck.shuffle()


@rpc("reliable") #Called by the network lobby code
func add_player(id: int, username: String, type: PlayerType) -> void:
	if players.size() > 4:
		return
	if type == null:
		type = PlayerType.HUMAN
	var board = _player_scene.instantiate()
	match players.size():
		0:
			board.global_position = seat1.position
		1:
			board.global_position = seat2.position
		2:
			board.global_position = seat3.position
		3:
			board.global_position = seat4.position
	$Players.add_child(board)
	var controller
	match type:
		PlayerType.HUMAN:
			controller = _human_scene.instantiate()
			networked_controllers.append(controller)
			controller.ready_button_pressed.connect(ready_player)
			controller.round_finished.connect(finish_player)
			controller.chat_message_submitted.connect(message)
			controller.workers_discarded.connect(discard_workers)
			controller.workers_kept.connect(board.add_to_roster)
			controller.hire_pressed.connect(hire)
			var player_info = {}
			player_info["id"] = id
			player_info["username"] = username
			controller.player_info = player_info
			board.player_info = player_info
		PlayerType.BOT:
			controller = _bot_scene.instantiate()
	controller.name = str(id)
	controller.set_multiplayer_authority(id)
	board.add_child(controller)
	controller.client_position = board.position
	controller.board = board
	board.slots[0].clicked.connect(controller.select_workspace)
	board.slots[1].clicked.connect(controller.select_workspace)
	board.slots[2].clicked.connect(controller.select_workspace)
	board.slots[3].clicked.connect(controller.select_workspace)
	board.poor_deck.clicked.connect(controller.on_poor_discard_deck_clicked)
	board.clients_discarded.connect(discard_clients)
	board.name = "board " + str(player_boards.size())
	player_boards.append(board)
	players.append(controller)
	for player in networked_controllers:
		player.rpc("update_ready_label", readied_players.size(), players.size())


@rpc("call_local", "any_peer", "reliable") #called from message() by player signals
func relay_chat_message(msg):
	for player in networked_controllers:
		player.add_chat_line(msg)


func message(msg):
	rpc("relay_chat_message", msg)


func finish_player(id):
	if not finished_players.has(id):
		finished_players.append(id)


func are_all_finished() -> bool:
	return finished_players.size() == players.size()


func ready_player(id):
	if not readied_players.has(id):
		readied_players.append(id)
	for player in networked_controllers:
		player.rpc("update_ready_label", readied_players.size(), players.size())
	if readied_players.size() == players.size() and is_multiplayer_authority():
		if round_number < 0:
			rpc("start_game")
		else:
			rpc("start_round")


@rpc("call_local", "reliable")
func start_game():
	#Only the host should shuffle the decks
	readied_players = []
	finished_players = []
	round_number += 1
	if is_multiplayer_authority():
		randomize()
		shuffle_and_sync_workers()
		shuffle_and_sync_clients()
		for player in players:
			rpc("draft_workers", player.player_info["username"], 4, 2)


func shuffle_and_sync_workers():
	if not is_multiplayer_authority():
		await shuffle_sync_recieved
		return
	for x in worker_discard.cards.size():
		worker_deck.append(worker_discard.draw_card())
	worker_deck.shuffle()
	var deck_order = []
	for card in worker_deck.cards:
		deck_order.append(card.get_path())
	rpc("send_worker_order", deck_order)


func shuffle_and_sync_clients():
	if not is_multiplayer_authority():
		await shuffle_sync_recieved
		return
	for x in client_discard.cards.size():
		client_deck.append(client_discard.draw_card())
	client_deck.shuffle()
	var deck_order = []
	for card in client_deck.cards:
		deck_order.append(card.get_path())
	rpc("send_client_order", deck_order)


@rpc("call_local", "reliable")
func start_round():
	readied_players = []
	finished_players = []
	round_number += 1
	turn_number = 0
	if is_multiplayer_authority():
		for player in networked_controllers:
			player.rpc("update_ready_label", readied_players.size(), players.size())
		for player in players:
			rpc("draft_clients", player.player_info["username"])
		rpc("start_turn")


@rpc("call_local", "reliable")
func start_turn():
	turn_number += 1
	for x in players.size():
		for other_player in players:
			other_player.spectate_player(players[x].get_path())
		players[x].start_turn()
		await players[x].turn_finished
	if is_multiplayer_authority() and not are_all_finished():
		rpc("start_turn")


@rpc("reliable")
func send_worker_order(node_paths):
	worker_deck.order(node_paths)
	shuffle_sync_recieved.emit()


@rpc("reliable")
func send_client_order(node_paths):
	client_deck.order(node_paths)
	shuffle_sync_recieved.emit()


func hire(player):
	if is_multiplayer_authority():
		rpc("draft_workers", player, 4, 1)


@rpc("call_local", "reliable")
func draft_workers(player, draw_amount, pick_amount):
	var cards = []
	for x in draw_amount:
		cards.append(worker_deck.draw_card())
	for x in players:
		if x.player_info["username"] == player:
			x.draft(cards, pick_amount)


@rpc("call_local", "reliable")
func discard_workers(node_paths):
	for path in node_paths:
		var card = get_node(path)
		worker_discard.place(card)


@rpc("call_local", "reliable")
func draft_clients(player):
	var controller: PlayerController = null
	for player_controller in players:
		if player_controller.player_info["username"] == player:
			controller = player_controller
	var cards_to_draw = 4
	if controller.reputation_points >= 20:
		cards_to_draw += 4
	if controller.reputation_points >= 40:
		cards_to_draw += 4
	for x in cards_to_draw - controller.board.shift_deck.cards.size():
		if client_deck.cards.size() == 0:
			shuffle_and_sync_clients()
		controller.board.shift_deck.place(client_deck.draw_card())


@rpc("call_local", "reliable")
func discard_clients(node_paths):
	for path in node_paths:
		var card = get_node(path)
		client_discard.place(card)
