#include <blitz/godot/NetworkInterface.h>

#include <blitz/protocol/PacketFactory.h>
#include <blitz/protocol/PacketSender.h>
#include <blitz/protocol/PacketSerializer.h>
#include <blitz/protocol/PacketVisitor.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/utility_functions.hpp>


namespace blitz {

#define RPC_CONFIG(functionName, rpc_mode, transfer_mode, call_local, channel)                                                        \
	{                                                                                                                                 \
		Dictionary config;                                                                                                            \
		config["rpc_mode"] = rpc_mode;                                                                                                \
		config["transfer_mode"] = transfer_mode;                                                                                      \
		config["call_local"] = call_local;                                                                                            \
		config["channel"] = channel;                                                                                                  \
		rpc_config(functionName, config);                                                                                             \
	}

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

using namespace godot;

void NetworkInterface::_bind_methods() {
	ClassDB::bind_method(D_METHOD("RecievePacketDataReliable", "a_PacketData"), &NetworkInterface::RecievePacketDataReliable);
	ClassDB::bind_method(D_METHOD("RecievePacketDataUnreliable", "a_PacketData"), &NetworkInterface::RecievePacketDataUnreliable);
	ClassDB::bind_method(
		D_METHOD("RecievePacketDataUnreliableOrdered", "a_PacketData"), &NetworkInterface::RecievePacketDataUnreliableOrdered);

	// server
	ADD_SIGNAL(MethodInfo("player_connected", PropertyInfo(Variant::INT, "peer_id")));
	ADD_SIGNAL(MethodInfo("player_disconnected", PropertyInfo(Variant::INT, "peer_id")));

	// client
	ADD_SIGNAL(MethodInfo("server_disconnected"));
	ADD_SIGNAL(MethodInfo("local_player_connected"));

	protocol::PacketFactory::Init();
}

NetworkInterface::NetworkInterface() {}

NetworkInterface::~NetworkInterface() {}

void NetworkInterface::_ready() {
	RPC_CONFIG("RecievePacketDataReliable", MultiplayerAPI::RPC_MODE_ANY_PEER, MultiplayerPeer::TRANSFER_MODE_RELIABLE, true, 0);
	RPC_CONFIG("RecievePacketDataUnreliable", MultiplayerAPI::RPC_MODE_ANY_PEER, MultiplayerPeer::TRANSFER_MODE_UNRELIABLE, true, 1);
	RPC_CONFIG("RecievePacketDataUnreliableOrdered", MultiplayerAPI::RPC_MODE_ANY_PEER,
		MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED, true, 2);

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

void NetworkInterface::BroadcastPacket(const protocol::Packet& a_Packet) {
	protocol::PacketBroadcaster packetBroadcaster(*this);
	packetBroadcaster.BroadcastPacket(a_Packet);
}

void NetworkInterface::SendPacket(PeerID a_Peer, const protocol::Packet& a_Packet) {
	protocol::PacketSender packetSender(a_Peer, *this);
	packetSender.SendPacket(a_Packet);
}

void NetworkInterface::RecievePacketDataReliable(godot::PackedByteArray a_PacketData) {
	auto packet = protocol::PacketSerializer::Deserialize(a_PacketData);
	if (packet) {
		packet->m_Sender = get_multiplayer()->get_remote_sender_id();
		Dispatch(*packet);
	}
}

void NetworkInterface::RecievePacketDataUnreliable(godot::PackedByteArray a_PacketData) {
	// we have to copy the function body in order to preserve the remote sender id
	auto packet = protocol::PacketSerializer::Deserialize(a_PacketData);
	if (packet) {
		packet->m_Sender = get_multiplayer()->get_remote_sender_id();
		Dispatch(*packet);
	}
}

void NetworkInterface::RecievePacketDataUnreliableOrdered(godot::PackedByteArray a_PacketData) {
	// we have to copy the function body in order to preserve the remote sender id
	auto packet = protocol::PacketSerializer::Deserialize(a_PacketData);
	if (packet) {
		packet->m_Sender = get_multiplayer()->get_remote_sender_id();
		Dispatch(*packet);
	}
}

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

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

Error NetworkInterface::CreateGame(uint16_t a_Port, bool a_Dedicated) {
	auto* peer = memnew(ENetMultiplayerPeer);
	Error error = peer->create_server(a_Port, 50, 3);
	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 NetworkInterface::OnPlayerConnected(PeerID a_PeerId) {
	emit_signal("player_connected", a_PeerId);
}

void NetworkInterface::OnPlayerDisconnected(PeerID a_PeerId) {
	emit_signal("player_disconnected", a_PeerId);
}

void NetworkInterface::OnConnectOk() {
	emit_signal("local_player_connected");
}

void NetworkInterface::OnConnectFail() {
	ShutdownNetwork();
	emit_signal("server_disconnected");
}

void NetworkInterface::OnServerDisconnected() {
	ShutdownNetwork();
	emit_signal("server_disconnected");
}

void NetworkInterface::ShutdownNetwork() {
	get_multiplayer()->set_multiplayer_peer(nullptr);
	if (auto* server = find_child("Server")) {
		server->queue_free();
	}
}

}  // namespace blitz