#include "Lobby.h"

#include <godot_cpp/classes/e_net_multiplayer_peer.hpp>
#include <godot_cpp/classes/multiplayer_api.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

static const char ServerScenePath[] = "res://Scenes/Network/server.tscn";

namespace blitz {

void Lobby::_bind_methods() {
	godot::ClassDB::bind_method(godot::D_METHOD("create_game", "port", "dedicated"), &Lobby::CreateGame);
	godot::ClassDB::bind_method(godot::D_METHOD("join_game", "address", "port"), &Lobby::JoinGame);
	ADD_SIGNAL(MethodInfo("player_connected", PropertyInfo(Variant::INT, "peer_id")));
	ADD_SIGNAL(MethodInfo("player_disconnected", PropertyInfo(Variant::INT, "peer_id")));
	ADD_SIGNAL(MethodInfo("server_disconnected"));
	ADD_SIGNAL(MethodInfo("local_player_connected"));
}

Lobby::Lobby() {}

Lobby::~Lobby() {}

void Lobby::_ready() {
	get_multiplayer()->connect("peer_connected", callable_mp(this, &Lobby::OnPlayerConnected));
	get_multiplayer()->connect("peer_disconnected", callable_mp(this, &Lobby::OnPlayerDisconnected));
	get_multiplayer()->connect("connected_to_server", callable_mp(this, &Lobby::OnConnectOk));
	get_multiplayer()->connect("connection_failed", callable_mp(this, &Lobby::OnConnectFail));
	get_multiplayer()->connect("server_disconnected", callable_mp(this, &Lobby::OnServerDisconnected));
}

Error Lobby::JoinGame(const String& a_Address, uint16_t a_Port) {
	auto* peer = memnew(ENetMultiplayerPeer);
	Error error = peer->create_client(a_Address, a_Port);
	if (error)
		return error;

	get_multiplayer()->set_multiplayer_peer(peer);
	return Error::OK;
}

Error Lobby::CreateGame(uint16_t a_Port, bool a_Dedicated) {
	auto* peer = memnew(ENetMultiplayerPeer);
	Error error = peer->create_server(a_Port);
	if (error)
		return error;

	get_multiplayer()->set_multiplayer_peer(peer);

	Ref<PackedScene> serverScene = ResourceLoader::get_singleton()->load(ServerScenePath);
	add_child(serverScene->instantiate());

	if (!a_Dedicated) {
		emit_signal("local_player_connected");
		emit_signal("player_connected", get_multiplayer()->get_unique_id());
	}

	return Error::OK;
}

void Lobby::Shutdown() {
	get_multiplayer()->set_multiplayer_peer(nullptr);
	if (auto* server = find_child("Server")) {
		remove_child(server);
	}
}

void Lobby::OnPlayerConnected(PeerID a_PeerId) {
	if (get_multiplayer()->is_server()) {
		emit_signal("player_connected", a_PeerId);
	}
}

void Lobby::OnPlayerDisconnected(PeerID a_PeerId) {
	if (get_multiplayer()->is_server()) {
		emit_signal("player_disconnected", a_PeerId);
	}
}

void Lobby::OnConnectOk() {
	int32_t peerId = get_multiplayer()->get_unique_id();
	emit_signal("local_player_connected");
}

void Lobby::OnConnectFail() {
	Shutdown();
}

void Lobby::OnServerDisconnected() {
	Shutdown();
	emit_signal("server_disconnected");
}

}  // namespace blitz