#include "client/game/ClientGame.h"

#include "blitz/misc/Log.h"
#include "blitz/misc/Random.h"
#include "blitz/protocol/PacketDispatcher.h"
#include "blitz/protocol/packets/PlayerJoinPacket.h"
#include "blitz/protocol/packets/PlayerLeavePacket.h"
#include "blitz/protocol/packets/PlayerListPacket.h"
#include "blitz/protocol/packets/PlayerPositionAndRotationPacket.h"
#include "blitz/protocol/packets/PlayerShootPacket.h"
#include "blitz/protocol/packets/PlayerStatsPacket.h"
#include "blitz/protocol/packets/ServerConfigPacket.h"
#include "blitz/protocol/packets/UpdateGameStatePacket.h"
#include "blitz/protocol/packets/UpdateHealthPacket.h"
#include "client/Client.h"

namespace blitz {
namespace client {

ClientGame::ClientGame(Client* client, protocol::PacketDispatcher* dispatcher) :
	protocol::PacketHandler(dispatcher), m_Client(client) {
	RegisterHandlers();
}

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

void ClientGame::AddPlayer(game::PlayerID player, const std::string& name) {
	Game::AddPlayer(player, name);
	m_LeaderBoard.AddPlayer(GetPlayerById(player));
}
void ClientGame::RemovePlayer(game::PlayerID player) {
	Game::RemovePlayer(player);
	m_LeaderBoard.RemovePlayer(player);
}

void ClientGame::RegisterHandlers() {
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerJoin, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerLeave, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerList, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerPositionAndRotation, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerShoot, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerStats, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateGameState, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateHealth, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::ServerConfig, this);
}

void ClientGame::HandlePacket(const protocol::PlayerJoinPacket* packet) {
	AddPlayer(packet->GetPlayerID(), packet->GetPlayerName());

	// Initialize camera
	if (packet->GetPlayerID() == m_Client->GetPlayerID()) {
		m_Client->NotifyListeners(&game::ClientListener::OnSpectatorChange, packet->GetPlayerID());
		m_Client->NotifyListeners(&game::ClientListener::OnGameJoin);
	}
}

void ClientGame::HandlePacket(const protocol::PlayerLeavePacket* packet) {
	RemovePlayer(packet->GetPlayerID());
}

void ClientGame::HandlePacket(const protocol::PlayerListPacket* packet) {
	for (const auto& [playerID, playerInfo] : packet->GetPlayers()) {
		AddPlayer(playerID, playerInfo.name);
	}
}

void ClientGame::HandlePacket(const protocol::PlayerStatsPacket* packet) {
	game::Player* player = m_Client->GetGame()->GetPlayerById(packet->GetPlayerID());
	if (!player)
		return;
	player->GetStats() = packet->GetPlayerStats();

	m_LeaderBoard.Update();
}

void ClientGame::HandlePacket(const protocol::UpdateHealthPacket* packet) {
	game::Player* player = m_Client->GetGame()->GetPlayerById(m_Client->GetPlayerID());
	player->SetHP(packet->GetNewHealth());

	// we are dead
	if (player->GetHP() <= 0.0f) {
		player->SetPosition({utils::GetRandomReal(-10.0f, 10.0f), 0, utils::GetRandomReal(-10.0f, 10.0f)});
	}
}

void ClientGame::HandlePacket(const protocol::UpdateGameStatePacket* packet) {
	m_GameState = packet->GetGameState();
	m_GameTimer.SetInterval(packet->GetTimeRemaining());
	m_GameTimer.Reset();
}

void ClientGame::HandlePacket(const protocol::ServerConfigPacket* packet) {
	m_Config = packet->GetGameConfig();
	m_Client->NotifyListeners(&game::ClientListener::OnGameConfigUpdate);
}

void ClientGame::HandlePacket(const protocol::PlayerShootPacket* packet) {
	m_Client->NotifyListeners(
		&game::ClientListener::OnPlayerShoot, packet->GetPlayer(), packet->GetPosition(), packet->GetYaw(), packet->GetPitch());
}

void ClientGame::HandlePacket(const protocol::PlayerPositionAndRotationPacket* packet) {
	if (packet->GetPlayer() == m_Client->GetPlayerID())
		return;

	game::Player* player = GetPlayerById(packet->GetPlayer());

	if (!player)
		return;

	player->SetVelocity(packet->GetPosition() - player->GetPosition());
	player->SetYaw(packet->GetYaw());
	player->SetPitch(packet->GetPitch());
}

void ClientGame::Tick(std::uint64_t delta) {
	for (auto& [playerId, player] : GetPlayers()) {
		player.SetPosition(player.GetPosition() + player.GetVelocity() * (static_cast<float>(delta) / 100.0f));
	}
	if (m_GameTimer.GetInterval() > 0) {
		if (m_GameTimer.Update(delta)) {
			m_GameTimer.SetInterval(0);	 // disables the timer so it does not loop
		}
	}
}

}  // namespace client
}  // namespace blitz
