/**
 * @file netc_server.c
 *
 * Server-side netcode
 */

#include "netcode.h"

#include "inv_gui.h"
#include "Level.h"
#include "Game.h"
#include "Log.h"
#include "World.h"

#include <SDL_assert.h>
#include <SDL_rwops.h>

static struct NetConnection conn;
static client_t clients[NET_MAX_CLIENTS];

static void SendPacketToClient(int id, int type, void *data, int size);

static int AddClient(Netaddr address);
static void RemoveClient(int id);
// Ensures that the given client is correctly loading the world. This process may take multiple ticks.
static void SetupSpawn(int client);

// Sends the entity list needed by the given client
static void SendEntities(int client);

extern const char *packetnames[14];

static void ProcessPacket(struct PacketHeader header, Netaddr addr, SDL_RWops *rw)
{
	int clientId = -1;
	//Msg_Info("Packet from addr %s", Net_AddrToString(addr));
	//Msg_Info("sv <- %d:%s, sz %d, tick %d, ack %d", header.seqnum, packetnames[header.type], header.size, header.tick, header.acknum);
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].address.addr == addr.addr && clients[i].address.port == addr.port)
		{
			clientId = i;
			break;
		}
	}
	// Packets from unknown addresses are acceptable, since they are likely clients wanting to join the game.

	switch (header.type)
	{
	case NET_PACKET_DISCONNECT:
		SDL_assert(clientId >= 0);
		RemoveClient(clientId);
		break;
	case NET_PACKET_C2S_LOGIN:
		AddClient(addr);
		break;
	case NET_PACKET_C2S_INPUTCMD:
	{
		struct InputCmd input;
		SDL_assert(clientId >= 0);
		SDL_assert(clients[clientId].state > NET_CLIENT_NONE);
		SDL_assert(header.size == sizeof(struct InputCmd));
		rw->read(rw, &input, sizeof(struct InputCmd), 1);
		World_Players[clientId + 1].input = input;
		clients[clientId].ack = header.acknum;
	}
	break;
	case NET_PACKET_C2S_BUTTONPRESS:
	{
		int button;
		SDL_assert(clientId >= 0);
		SDL_assert(clients[clientId].state > NET_CLIENT_NONE);
		SDL_assert(header.size == sizeof(int));
		button = SDL_ReadLE32(rw);
		World_PlayerButtonPress(clientId + 1, button);
	}
	break;
	case NET_PACKET_C2S_GUIINTERACT:
	{
		int interact;
		SDL_assert(clientId >= 0);
		SDL_assert(clients[clientId].state > NET_CLIENT_NONE);
		SDL_assert(header.size == sizeof(int));
		interact = SDL_ReadLE32(rw);
		player_t *player = &World_Players[clientId + 1];
		Inv_Interact(&player->inventory, player, interact);
	}
	break;
	default:
		Msg_Warning("Received invalid packet id %d", header.type);
		break;
	}
}

void Netcode_Sv_Init(void)
{
	if (!Net_OpenServer(&conn))
	{
		Msg_Error("Could not start server");
		return;
	}
}

void Netcode_Sv_Close(void)
{
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state != NET_CLIENT_NONE)
		{
			SendPacketToClient(i, NET_PACKET_DISCONNECT, NULL, 0);
		}
	}
	Net_Close(&conn);
}

void Netcode_Sv_ReadPackets(void)
{
	Netaddr fromAddr;
	void *data;
	int size;

	int now = Net_CurrentTimeMs();

	while (Net_ReadNextPacket(&conn, &fromAddr, &data, &size))
	{
		SDL_RWops *rw = SDL_RWFromConstMem(data, size);
		struct PacketHeader header;
		int clientnum = -1;

		rw->read(rw, &header, sizeof(struct PacketHeader), 1);

		int delay = now - header.timestamp;

		ProcessPacket(header, fromAddr, rw);

		rw->close(rw);
	}
}

void Netcode_Sv_Tick(void)
{
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		switch (clients[i].state)
		{
		case NET_CLIENT_CONNECTED:
			SetupSpawn(i);
			break;
		case NET_CLIENT_LOADED:
		{
			player_t *player = &World_Players[i + 1];
			struct PlayerUpdate update;
			update.health = (int16_t)player->entity->health;
			update.maxHealth = (int16_t)player->entity->maxHealth;
			update.mana = (int16_t)player->mana;
			update.maxMana = (int16_t)player->maxMana;
			update.str = (uint8_t)player->str;
			update.spd = (uint8_t)player->spd;
			update.def = (uint8_t)player->def;
			update.vit = (uint8_t)player->vit;
			update.itl = (uint8_t)player->itl;
			update.handSlot = (uint8_t)player->handSlot;
			update.itemCooldown = (int16_t)player->itemCooldown;
			update.statPoints = (uint16_t)player->statPoints;
			update.level = (uint16_t)player->level;
			update.xp = (uint16_t)player->xp;
			SendPacketToClient(i, NET_PACKET_S2C_PLAYERSTATE, &update, sizeof(struct PlayerUpdate));
			SendPacketToClient(i, NET_PACKET_S2C_INVENTORY, &player->inventory, sizeof(inventory_t));
			SendEntities(i);
		}
		break;
		default:
			break;
		}
	}
}

void Netcode_Sv_ChangeLevel(int depth)
{
	uint8_t floor = depth;
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state == NET_CLIENT_NONE)
			continue;
		clients[i].state = NET_CLIENT_CONNECTED;
		clients[i].entityId = World_SpawnPlayer(i + 1, -1);
		SendPacketToClient(i, NET_PACKET_S2C_CHANGELEVEL, &floor, 1);
	}
}

void Netcode_Sv_PlaySound(int id)
{
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state == NET_CLIENT_LOADED)
		{
			SendPacketToClient(i, NET_PACKET_S2C_PLAYSOUND, &id, 4);
		}
	}
}

void Netcode_Sv_PlaySound3D(int soundId, int entityId, float x, float y, float dx, float dy)
{
	char buffer[20];
	((int *)buffer)[0] = soundId;
	((float *)buffer)[1] = x;
	((float *)buffer)[2] = y;
	((float *)buffer)[3] = dx;
	((float *)buffer)[4] = dy;
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state == NET_CLIENT_LOADED)
		{
			if (clients[i].entityId == entityId)
				SendPacketToClient(i, NET_PACKET_S2C_PLAYSOUND, &soundId, 4);
			else
				SendPacketToClient(i, NET_PACKET_S2C_PLAYSOUND3D, buffer, sizeof(buffer));
		}
	}
}

void Netcode_Sv_SetTile(int x, int y, char tile)
{
	char buffer[3];
	buffer[0] = (uint8_t)x;
	buffer[1] = (uint8_t)y;
	buffer[2] = (uint8_t)tile;
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state == NET_CLIENT_LOADED)
		{
			SendPacketToClient(i, NET_PACKET_S2C_SETTILE, buffer, 3);
		}
	}
}

#define MAX_PACKET_BUF 16384
#define MAX_PACKET_SIZE (MAX_PACKET_BUF - sizeof(struct PacketHeader))

static void SendPacketToClient(int id, int type, void *data, int size)
{
	char buffer[MAX_PACKET_BUF];
	client_t *client = &clients[id];

	if (client->state == NET_CLIENT_NONE)
		return;

	if (size >= MAX_PACKET_SIZE)
	{
		Msg_Error("cannot send a packet that big bucko");
		return;
	}

	struct PacketHeader *header = (struct PacketHeader *)buffer;
	header->type = type;
	header->size = size;
	header->tick = Game.levelTime;
	header->timestamp = Net_CurrentTimeMs();
	header->seqnum = client->seqnum++;
	header->acknum = 0;
	SDL_memcpy(buffer + sizeof(struct PacketHeader), data, size);
	Net_SendPacket(&conn, client->address, buffer, sizeof(struct PacketHeader) + size);
	//Msg_Info("sv -> %d:%s, sz %d, tick %d, ack %d", header->seqnum, packetnames[header->type], header->size, header->tick, header->acknum);
}

static int AddClient(Netaddr address)
{
	int id = -1;
	for (int i = 0; i < NET_MAX_CLIENTS; i++)
	{
		if (clients[i].state == NET_CLIENT_NONE)
		{
			id = i;
			break;
		}
	}

	if (id == -1)
		return id;

	Msg_Info("Server: Adding client #%d to the game", id);

	clients[id].state = NET_CLIENT_CONNECTED;
	clients[id].address = address;
	clients[id].seqnum = 0;
	clients[id].ack = 0;
	clients[id].entityId = World_SpawnPlayer(id + 1, -1);

	return id;
}

static void RemoveClient(int id)
{
	Msg_Info("Server: Removing client #%d from the game", id);
	if (clients[id].state == NET_CLIENT_LOADED)
	{
		World_RemoveObject(World_Players[id + 1].entity);
		Player_Reset(&World_Players[id + 1]);
	}
	clients[id].state = NET_CLIENT_NONE;
}

void SetupSpawn(int client)
{
	uint8_t floor = Game.depth;
	SendPacketToClient(client, NET_PACKET_S2C_CHANGELEVEL, &floor, 1);
	SendPacketToClient(client, NET_PACKET_S2C_TILEMAP, Level.tilemap, LEVEL_SIZE * LEVEL_SIZE);
	SendEntities(client);
	SendPacketToClient(client, NET_PACKET_S2C_SETPLAYER, &clients[client].entityId, sizeof(short));
	// TODO: Don't set this until the client has sent acknowledgement for the previous packets
	clients[client].state = NET_CLIENT_LOADED;
}

void SendEntities(int client)
{
	SendPacketToClient(client, NET_PACKET_S2C_ENTITIES, World_Sprites, sizeof(World_Sprites));
}
