#include "server/game/ServerGame.h"

#include "blitz/maths/Physics.h"
#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"
#include "blitz/misc/Random.h"
#include "blitz/protocol/packets/ChatPacket.h"
#include "blitz/protocol/packets/PlayerJoinPacket.h"
#include "blitz/protocol/packets/PlayerLeavePacket.h"
#include "blitz/protocol/packets/PlayerPositionAndRotationPacket.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 "server/Server.h"
#include <cmath>

namespace blitz {
namespace server {

ServerGame::ServerGame(Server* server) : m_Server(server), m_PositionTimer(SERVER_TPS) {
	CancelGame();
}

ServerGame::~ServerGame() {}

void ServerGame::StartGame() {
	SetGameState(game::gsPreparing, m_ServerDuration.m_PrepDuration);
	m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::AQUA) + "La partie commence dans 10s !");
}

void ServerGame::CancelGame() {
	SetGameState(game::gsWaiting, 0);
}

void ServerGame::Tick(std::uint64_t delta) {
	if (m_PositionTimer.Update(delta)) {
		SendPlayerPositions();
	}

	if (m_GameState != game::gsWaiting && m_GameTimer.Update(delta)) {
		switch (m_GameState) {
			case game::gsPreparing:
				SetGameState(game::gsGame, m_ServerDuration.m_GameDuration);
				break;

			case game::gsGame:
				SetGameState(game::gsEnd, m_ServerDuration.m_EndDuration);
				break;

			case game::gsEnd: {
				if (m_Players.size() > 1) {
					SetGameState(game::gsGame, m_ServerDuration.m_GameDuration);
				} else {
					SetGameState(game::gsWaiting, 0);
				}
				break;
			}

			default:
				break;
		}
	}
}

void ServerGame::SendPlayerPositions() {
	for (const auto& [playerID, player] : GetPlayers()) {
		protocol::PlayerPositionAndRotationPacket packet(player.GetPosition(), player.GetYaw(), player.GetPitch(), playerID);
		m_Server->BroadcastPacket(&packet);
	}
}

void ServerGame::ProcessShoot(game::PlayerID shooter, Vec3f position, float yaw, float pitch) {
	maths::Ray shootRay;
	shootRay.origin = position + Vec3f{0.0, 1.75, 0.0};
	shootRay.direction = {
		std::cos(yaw) * std::cos(pitch),
		std::sin(pitch),
		std::sin(yaw) * std::cos(pitch),
	};

	static const maths::AABB playerStaticAABB = {Vec3f{-0.5f, 0.0f, -0.5f}, Vec3f{0.5f, 1.8f, 0.5f}};

	bool shootLanded = false;

	game::Player* shooterPlayer = GetPlayerById(shooter);

	shooterPlayer->GetStats().m_ShootCount++;

	for (auto& [playerId, player] : GetPlayers()) {
		if (playerId != shooter && maths::Intersects(shootRay, playerStaticAABB + player.GetPosition())) {
			if (!shootLanded) {
				shootLanded = true;
				shooterPlayer->GetStats().m_ShootSuccessCount++;
			}
			DamagePlayer(player, *shooterPlayer);
		}
	}
}

void ServerGame::AddPlayer(game::PlayerID player, const std::string& name) {
	Game::AddPlayer(player, name);

	protocol::PlayerJoinPacket joinPacket(player, name);
	m_Server->BroadcastPacket(&joinPacket);

	std::string joinMessage = utils::Format("%s a rejoint la partie !", name.c_str());

	utils::LOG("[Server] " + joinMessage);
	m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + joinMessage);

	if (m_GameState == game::gsWaiting && m_Players.size() > 1) {
		StartGame();
	}
}

void ServerGame::RemovePlayer(game::PlayerID playerID) {

	game::Player* player = GetPlayerById(playerID);

	if (!player)
		return;

	protocol::PlayerLeavePacket packet(playerID);
	m_Server->BroadcastPacket(&packet);

	std::string leaveMessage = utils::Format("%s a quitte la partie !", player->GetName().c_str());

	utils::LOG("[Server] " + leaveMessage);
	m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + leaveMessage);

	Game::RemovePlayer(playerID);

	if ((m_GameState == game::gsGame || m_GameState == game::gsPreparing) && m_Players.size() <= 1) {
		CancelGame();
	}
}

void ServerGame::DamagePlayer(game::Player& player, game::Player& shooter) {
	static float MAX_HP = 100;
	static float GUN_DAMAGE = 50;

	UpdateHP(player, player.GetHP() - GUN_DAMAGE);

	// player is dead
	if (player.GetHP() <= 0.0f) {
		UpdateHP(player, MAX_HP);
		player.GetStats().m_Deaths++;
		shooter.GetStats().m_Kills++;
		player.SetPosition({utils::GetRandomReal(-10.0f, 10.0f), 0.0f, utils::GetRandomReal(-10.0f, 10.0f)});

		UpdatePlayerStats();
	}
}

void ServerGame::UpdateHP(game::Player& player, float newHP) {
	player.SetHP(newHP);

	if (player.IsBot())
		return;

	protocol::UpdateHealthPacket packet(player.GetHP());

	auto it = m_Server->GetConnexions().find(player.GetID());
	if (it != m_Server->GetConnexions().end())
		it->second->SendPacket(&packet);
}

void ServerGame::UpdatePlayerStats() {
	for (auto& [playerId, player] : GetPlayers()) {
		protocol::PlayerStatsPacket packet(playerId, player.GetStats());
		m_Server->BroadcastPacket(&packet);
	}
}

void ServerGame::ResetPlayerStats() {
	for (auto& [playerId, player] : GetPlayers()) {
		player.GetStats() = {};
	}
}

void ServerGame::SetGameState(game::GameState gameState, std::uint64_t duration) {
	m_GameState = gameState;
	m_GameTimer.SetInterval(duration);
	m_GameTimer.Reset();

	if (gameState == game::gsGame) {
		ResetPlayerStats();
		UpdatePlayerStats();
	}

	protocol::UpdateGameStatePacket packet(gameState, duration);
	m_Server->BroadcastPacket(&packet);
}

void ServerGame::SendServerConfig() {
	protocol::ServerConfigPacket packet(m_Config);
	m_Server->BroadcastPacket(&packet);
}


}  // namespace server
}  // namespace blitz
