/**
 * @file netc_client.c
 *
 * Network game implementation for the client. This runs for both the host and clients connecting to a remote server.
 */

#include "netcode.h"

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

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

static struct NetConnection conn;
static int seqnum, acknum;

static void SendPacketToServer(int type, void *data, int size);

static int ProcessPacket(struct PacketHeader header, SDL_RWops *rw)
{
	//Msg_Info("cl <- %d:%s, sz %d, tick %d, ack %d", header.seqnum, packetnames[header.type], header.size, header.tick, header.acknum);

	switch (header.type)
	{
	case NET_PACKET_DISCONNECT:
		Game_QuitToTitle();
		return 1;
	case NET_PACKET_S2C_CHANGELEVEL:
	{
		int depth = SDL_ReadU8(rw);
		World_OnLevelChange(depth);
		LvlGen_SetLevelProperties(depth);
		Game.depth = depth;
		return 1;
	}
	case NET_PACKET_S2C_TILEMAP:
		SDL_assert(header.size == LEVEL_SIZE * LEVEL_SIZE);
		rw->read(rw, Level.tilemap, 1, LEVEL_SIZE * LEVEL_SIZE);
		return 1;
	case NET_PACKET_S2C_SETTILE:
	{
		SDL_assert(header.size == 3);
		uint8_t x = SDL_ReadU8(rw);
		uint8_t y = SDL_ReadU8(rw);
		char tile = SDL_ReadU8(rw);
		SDL_assert(x < LEVEL_SIZE && y < LEVEL_SIZE);
		Level.tilemap[x + y * LEVEL_SIZE] = tile;
		Lvl_ComputeLightmap();
		return 1;
	}
	case NET_PACKET_S2C_ENTITIES:
		SDL_assert(header.size == sizeof(World_Sprites));
		rw->read(rw, World_Sprites, sizeof(World_Sprites), 1);
		return 1;
	case NET_PACKET_S2C_PLAYSOUND:
	{
		int soundId;
		SDL_assert(header.size == 4);
		soundId = SDL_ReadLE32(rw);
		Audio_PlaySound(soundId);
	}
	return 1;
	case NET_PACKET_S2C_PLAYSOUND3D:
	{
		int soundId;
		float x, y, dx, dy;
		SDL_assert(header.size == 20);
		soundId = SDL_ReadLE32(rw);
		rw->read(rw, &x, sizeof(float), 1);
		rw->read(rw, &y, sizeof(float), 1);
		rw->read(rw, &dx, sizeof(float), 1);
		rw->read(rw, &dy, sizeof(float), 1);
		Audio_PlaySound3D(soundId, x, y, dx, dy);
	}
	return 1;
	case NET_PACKET_S2C_SETPLAYER:
	{
		short entityId;
		SDL_assert(header.size == sizeof(short));
		rw->read(rw, &entityId, sizeof(short), 1);
		SDL_assert(entityId > 0 && entityId < WORLD_MAX_ENTITIES);
		World_SpawnPlayer(0, entityId);
	}
	return 1;
	case NET_PACKET_S2C_PLAYERSTATE:
	{
		struct PlayerUpdate update;
		player_t *player = &World_Players[0];
		SDL_assert(header.size == sizeof(struct PlayerUpdate));
		rw->read(rw, &update, sizeof(struct PlayerUpdate), 1);
		player->entity->health = update.health;
		player->entity->maxHealth = update.maxHealth;
		player->mana = update.mana;
		player->maxMana = update.maxMana;
		player->str = update.str;
		player->spd = update.spd;
		player->def = update.def;
		player->vit = update.vit;
		player->itl = update.itl;
		player->handSlot = update.handSlot;
		player->itemCooldown = update.itemCooldown;
		player->statPoints = update.statPoints;
		player->level = update.level;
		player->xp = update.xp;
	}
	return 1;
	case NET_PACKET_S2C_INVENTORY:
		SDL_assert(header.size == sizeof(inventory_t));
		rw->read(rw, &World_Players[0].inventory, sizeof(inventory_t), 1);
		return 1;
	default:
		Msg_Warning("Received invalid packet id %d", header.type);
		return 0;
	}
}

void Netcode_Cl_Init(void)
{
	if (!Net_ConnectClient(&conn, Game.serverAddr))
	{
		Msg_Error("Could not connect to server at '%s'", Game.serverAddr);
		return;
	}
	seqnum = 0;
	acknum = 0;
	SendPacketToServer(NET_PACKET_C2S_LOGIN, NULL, 0);
}

void Netcode_Cl_Close(void)
{
	SendPacketToServer(NET_PACKET_DISCONNECT, NULL, 0);
	Net_Close(&conn);
}

void Netcode_Cl_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;

		// Client should only recieve packets from the server
		if (fromAddr.addr != conn.serverAddr.addr || fromAddr.port != conn.serverAddr.port)
		{
			Msg_Warning("Filtered packet from stranger %s", Net_AddrToString(fromAddr));
			continue;
		}

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

		int delay = now - header.timestamp;

		if (ProcessPacket(header, rw))
			acknum = header.seqnum;

		rw->close(rw);
	}
}

void Netcode_Cl_Tick(void)
{
	if (World_Players[0].entity)
		SendPacketToServer(NET_PACKET_C2S_INPUTCMD, &Game.input.command, sizeof(struct InputCmd));
}

void Netcode_Cl_SendButton(int button)
{
	SendPacketToServer(NET_PACKET_C2S_BUTTONPRESS, &button, sizeof(int));
}

void Netcode_Cl_GuiInteract(int interact)
{
	SendPacketToServer(NET_PACKET_C2S_GUIINTERACT, &interact, sizeof(int));
}

#define MAX_BUFFER 1024
#define MAX_PACKET (MAX_BUFFER - sizeof(struct PacketHeader))

static void SendPacketToServer(int type, void *data, int size)
{
	char buffer[MAX_BUFFER];
	struct PacketHeader *header = (struct PacketHeader *)buffer;

	SDL_assert(size < MAX_PACKET);
	header->type = type;
	header->size = size;
	header->tick = Game.levelTime;
	header->timestamp = Net_CurrentTimeMs();
	header->seqnum = seqnum++;
	header->acknum = acknum;

	SDL_memcpy(buffer + sizeof(struct PacketHeader), data, size);
	//Msg_Info("cl -> %d:%s, sz %d, tick %d, ack %d", header->seqnum, packetnames[header->type], header->size, header->tick, header->acknum);
	Net_SendPacket(&conn, conn.serverAddr, buffer, size + sizeof(struct PacketHeader));
}
