#include "server/ServerConnexion.h"

#include "blitz/game/Game.h"
#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"
#include "blitz/misc/Random.h"
#include "blitz/misc/Time.h"
#include "blitz/protocol/PacketDispatcher.h"
#include "blitz/protocol/PacketFactory.h"
#include "blitz/protocol/packets/ChatPacket.h"
#include "blitz/protocol/packets/ConnexionInfoPacket.h"
#include "blitz/protocol/packets/DisconnectPacket.h"
#include "blitz/protocol/packets/KeepAlivePacket.h"
#include "blitz/protocol/packets/PlayerJoinPacket.h"
#include "blitz/protocol/packets/PlayerListPacket.h"
#include "blitz/protocol/packets/PlayerLoginPacket.h"
#include "blitz/protocol/packets/PlayerPositionAndRotationPacket.h"
#include "blitz/protocol/packets/PlayerShootPacket.h"
#include "blitz/protocol/packets/ServerConfigPacket.h"
#include "blitz/protocol/packets/UpdateGameStatePacket.h"
#include "client/gui/ColorFulText.h"
#include "server/Server.h"
#include <unordered_map>

#define KEEP_ALIVE_TIMEOUT 10 * 1000  // 10s

namespace blitz {
namespace server {


ServerConnexion::ServerConnexion(Server* server, network::TCPSocket& socket, std::uint8_t id) :
	Connexion::Connexion(&m_Dispatcher, socket), m_Server(server), m_ID(id), m_Player(nullptr) {
	RegisterHandlers();
}

ServerConnexion::ServerConnexion(ServerConnexion&& move) :
	Connexion::Connexion(std::move(move)),
	m_Server(move.m_Server),
	m_ID(move.m_ID),
	m_KeepAlive(move.m_KeepAlive),
	m_Player(move.m_Player) {

	move.m_Server = nullptr;
	RegisterHandlers();
}

void ServerConnexion::RegisterHandlers() {
	GetDispatcher()->RegisterHandler(protocol::PacketType::Disconnect, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::KeepAlive, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerLogin, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::Chat, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerShoot, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerPositionAndRotation, this);
}

bool ServerConnexion::UpdateSocket() {
	CheckKeepAlive();
	return Connexion::UpdateSocket();
}

void ServerConnexion::CheckKeepAlive() {
	std::uint64_t time = utils::GetTime();
	if (time - m_KeepAlive.SendTime > KEEP_ALIVE_TIMEOUT) {
		if (m_KeepAlive.RecievedResponse) {
			SendKeepAlive();
		} else {
			protocol::DisconnectPacket packet("Time out");
			SendPacket(&packet);
			CloseConnection();
		}
	}
}

void ServerConnexion::SendKeepAlive() {
	m_KeepAlive.KeepAliveID = utils::GetRandomInt<std::uint64_t>(0, RAND_MAX);
	m_KeepAlive.RecievedResponse = false;

	protocol::KeepAlivePacket keepAlivePacket(m_KeepAlive.KeepAliveID);
	SendPacket(&keepAlivePacket);

	std::uint64_t time = utils::GetTime();
	m_KeepAlive.SendTime = time;
}

void ServerConnexion::InitPlayerChatColor() {
	const std::string& playerName = m_Player->GetName();

	std::size_t hash = std::hash<std::string>()(playerName);
	std::uint8_t red = hash >> 16 & 0xFF;
	std::uint8_t green = hash >> 8 & 0xFF;
	std::uint8_t blue = hash & 0xFF;

	m_ChatColor = protocol::ChatPacket::GetTextColor({red, green, blue});
}

void ServerConnexion::HandlePacket(const protocol::PlayerLoginPacket* packet) {

	SendPlayers();
	SendGameState();
	SendServerConfig();

	m_Server->GetGame().AddPlayer(m_ID, packet->GetPlayerName());

	m_Player = m_Server->GetGame().GetPlayerById(m_ID);

	InitPlayerChatColor();
}

void ServerConnexion::HandlePacket(const protocol::KeepAlivePacket* packet) {
	if (packet->GetAliveID() != m_KeepAlive.KeepAliveID)
		return;

	m_KeepAlive.RecievedResponse = true;
}

void ServerConnexion::HandlePacket(const protocol::DisconnectPacket* packet) {
	CloseConnection();
}

void ServerConnexion::HandlePacket(const protocol::ChatPacket* packet) {
	if (!m_Player)
		return;

	m_Server->BroadcastChatMessage(utils::Format("<%s%s%s> %s", m_ChatColor.c_str(), m_Player->GetName().c_str(),
		protocol::ChatPacket::GetTextColor(protocol::WHITE).c_str(),
		protocol::ChatPacket::GetColoredTextString(packet->GetMessage()).c_str()));
}

void ServerConnexion::HandlePacket(const protocol::PlayerPositionAndRotationPacket* packet) {
	if (!m_Player)
		return;

	m_Player->SetPosition(packet->GetPosition());
	m_Player->SetYaw(packet->GetYaw());
	m_Player->SetPitch(packet->GetPitch());
}

void ServerConnexion::HandlePacket(const protocol::PlayerShootPacket* packet) {
	protocol::PlayerShootPacket broadcastShoot(packet->GetPosition(), packet->GetYaw(), packet->GetPitch(), m_Player->GetID());
	m_Server->BroadcastPacket(&broadcastShoot);

	m_Server->GetGame().ProcessShoot(m_Player->GetID(), packet->GetPosition(), packet->GetYaw(), packet->GetPitch());
}

void ServerConnexion::Init() {
	InitConnection();
	SendKeepAlive();
}

void ServerConnexion::SendGameState() {
	protocol::UpdateGameStatePacket packet(m_Server->GetGame().GetGameState(), m_Server->GetGame().GetGameStateRemainingTime());
	SendPacket(&packet);
}

void ServerConnexion::SendPlayers() {
	protocol::PlayerList list;

	for (const auto& [playerID, player] : m_Server->GetGame().GetPlayers()) {
		list.insert({playerID, {player.GetName()}});
	}

	protocol::PlayerListPacket packet(list);
	SendPacket(&packet);
}

void ServerConnexion::SendServerConfig() {
	protocol::ServerConfigPacket packet(m_Server->GetGame().GetGameConfig());
	SendPacket(&packet);
}

void ServerConnexion::InitConnection() {
	protocol::ConnexionInfoPacket conPacket(m_ID);
	SendPacket(&conPacket);
}

ServerConnexion::~ServerConnexion() {
	if (GetDispatcher() == nullptr)
		return;

	GetDispatcher()->UnregisterHandler(this);

	m_Server->GetGame().RemovePlayer(m_ID);
}

}  // namespace server
}  // namespace blitz
