#include "client/game/ClientGame.h"
#include "td/protocol/PacketDispatcher.h"
#include "client/Client.h"

#include "td/protocol/packets/ConnectionInfoPacket.h"
#include "td/protocol/packets/PlayerJoinPacket.h"
#include "td/protocol/packets/PlayerListPacket.h"
#include "td/protocol/packets/PlayerLeavePacket.h"
#include "td/protocol/packets/UpdatePlayerTeamPacket.h"
#include "td/protocol/packets/UpdateLobbyTimePacket.h"
#include "td/protocol/packets/UpdateGameStatePacket.h"
#include "td/protocol/packets/UpdateMoneyPacket.h"
#include "td/protocol/packets/UpdateExpPacket.h"
#include "td/protocol/packets/DisconnectPacket.h"
#include "td/protocol/packets/WorldDataPacket.h"

namespace td {
namespace client {

ClientGame::ClientGame(Client* client) : protocol::PacketHandler(client->GetConnexion().GetDispatcher()),
game::Game(&m_WorldClient), m_Client(client), m_Renderer(client->GetRenderer()), m_WorldClient(this),
m_WorldRenderer(&m_WorldClient, this) {
	GetDispatcher()->RegisterHandler(protocol::PacketType::ConnectionInfo, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerJoin, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerList, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerLeave, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdatePlayerTeam, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateLobbyTime, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateGameState, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateMoney, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateEXP, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::Disconnect, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::WorldData, this);
}

ClientGame::~ClientGame() {
	GetDispatcher()->UnregisterHandler(this);
}

void ClientGame::Tick(std::uint64_t delta) {
	game::Game::Tick(delta);
	m_WorldRenderer.Update();
	if (m_GameState == game::GameState::Lobby && m_LobbyTime > 0) {
		m_LobbyTime -= delta;
	}
}

void ClientGame::HandlePacket(const protocol::PlayerJoinPacket* packet) {
	game::Player player(packet->GetPlayerID());
	player.SetName(packet->GetPlayerName());

	m_Players.insert({ player.GetID(), player });
}

void ClientGame::HandlePacket(const protocol::PlayerLeavePacket* packet) {
	game::Player* player = &m_Players[packet->GetPlayerID()];
	if (player->GetTeamColor() != game::TeamColor::None) {
		m_Teams[static_cast<std::size_t>(player->GetTeamColor())].RemovePlayer(player);
	}
	m_Players.erase(player->GetID());
}

void ClientGame::HandlePacket(const protocol::PlayerListPacket* packet) {
	for (auto pair : packet->GetPlayers()) {
		std::uint8_t playerID = pair.first;
		protocol::PlayerInfo playerInfo = pair.second;
		game::Player player(playerID);
		player.SetName(playerInfo.name);
		player.SetTeamColor(playerInfo.team);
		m_Players.insert({ playerID, player });
		if (player.GetTeamColor() != game::TeamColor::None) {
			m_Teams[static_cast<std::size_t>(player.GetTeamColor())].AddPlayer(&m_Players[playerID]);
		}
	}
	m_Player = &m_Players[m_ConnexionID];
}

void ClientGame::HandlePacket(const protocol::UpdatePlayerTeamPacket* packet) {
	game::Player* player = &m_Players[packet->GetPlayerID()];

	SAFE_CHECK(player);

	if (player->GetTeamColor() == game::TeamColor::None) { //join a team
		GetTeam(packet->GetSelectedTeam()).AddPlayer(player);
	} else if (packet->GetSelectedTeam() == game::TeamColor::None) { // leave a team
		GetTeam(player->GetTeamColor()).RemovePlayer(player);
	} else { // change team
		GetTeam(player->GetTeamColor()).RemovePlayer(player);
		GetTeam(packet->GetSelectedTeam()).AddPlayer(player);
	}
	player->SetTeamColor(packet->GetSelectedTeam());
}

void ClientGame::HandlePacket(const protocol::UpdateGameStatePacket* packet) {
	SetGameState(packet->GetGameState());
}

void ClientGame::HandlePacket(const protocol::ConnexionInfoPacket* packet) {
	m_ConnexionID = packet->GetConnectionID();
}

void ClientGame::HandlePacket(const protocol::UpdateLobbyTimePacket* packet) {
	m_LobbyTime = packet->GetRemainingTime();
}

void ClientGame::HandlePacket(const protocol::UpdateMoneyPacket* packet) {
	SAFE_CHECK(m_Player);

	m_Player->SetGold(packet->GetGold());
}

void ClientGame::HandlePacket(const protocol::UpdateExpPacket* packet) {
	SAFE_CHECK(m_Player);

	m_Player->SetExp(packet->GetExp());
}

void ClientGame::HandlePacket(const protocol::DisconnectPacket* packet) {
	m_GameState = game::GameState::Disconnected;
	m_Renderer->SetBackgroundColor({ 0, 0, 0 });
}

void ClientGame::HandlePacket(const protocol::WorldDataPacket* packet) {
	m_WorldRenderer.LoadModels();
	// set cam pos to player spawn
	const game::Spawn& spawn = m_World->GetTeam(m_Player->GetTeamColor()).GetSpawn();
	m_WorldRenderer.ResetCam();
	m_WorldRenderer.SetCamPos(spawn.GetCenterX(), spawn.GetCenterY());
}

void ClientGame::RenderWorld() {
	if (m_GameState == game::GameState::Game || m_GameState == game::GameState::EndGame) {
		m_WorldRenderer.Render();
	}
}

} // namespace client
} // namespace td
