#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/ConnectionInfoPacket.h"
#include "blitz/protocol/packets/DisconnectPacket.h"
#include "blitz/protocol/packets/KeepAlivePacket.h"
#include "blitz/protocol/packets/PlayerJoinPacket.h"
#include "blitz/protocol/packets/PlayerLoginPacket.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);
}

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);
	unsigned int red = hash >> 16 & 0xFF;
	unsigned int green = hash >> 8 & 0xFF;
	unsigned int blue = hash & 0xFF;

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

void ServerConnexion::HandlePacket(const protocol::PlayerLoginPacket* packet) {
	game::Player newPlayer{m_ID};
	newPlayer.SetName(packet->GetPlayerName());

	m_Server->GetGame()->GetPlayers().insert({m_ID, newPlayer});

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

	protocol::PlayerJoinPacket joinPacket(m_ID, m_Player->GetName());
	m_Server->BroadcastPacket(&joinPacket);

	InitPlayerChatColor();

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

	utils::LOG(joinMessage);
	m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + joinMessage);
}

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::Start() {
	InitConnection();
	SendKeepAlive();
}

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

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

	GetDispatcher()->UnregisterHandler(this);

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

		utils::LOG(leaveMessage);
		m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + leaveMessage);
	}
}

}  // namespace server
}  // namespace blitz
