#include "server/Server.h"

#include "blitz/game/Game.h"
#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"
#include "blitz/misc/Time.h"
#include "blitz/protocol/PacketFactory.h"
#include "blitz/protocol/packets/ChatPacket.h"
#include "blitz/protocol/packets/DisconnectPacket.h"
#include "blitz/protocol/packets/PlayerLeavePacket.h"
#include "blitz/protocol/packets/ServerTpsPacket.h"

#define SERVER_TPS 20
#define SERVER_TICK 1000 / SERVER_TPS

namespace blitz {
namespace server {

Server::Server() : m_ServerRunning(false), m_TickCounter(SERVER_TPS), m_Game(std::make_unique<game::Game>()) {}

Server::~Server() {
	StopThread();

	if (m_Thread.joinable())
		m_Thread.join();
}

void Server::ServerLoop() {
	std::uint64_t lastTime = utils::GetTime();
	while (m_ServerRunning) {
		std::uint64_t time = utils::GetTime();

		std::uint64_t delta = time - lastTime;

		if (delta >= SERVER_TICK) {
			Tick(delta);
			lastTime = utils::GetTime();
			m_TickCounter.SetMSPT(lastTime - time);
			std::uint64_t sleepTime = SERVER_TICK - (delta - SERVER_TICK);
			std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
		}
	}
	Clean();
}

void Server::StartThread() {
	m_Thread = std::thread([this]() { ServerLoop(); });
}

void Server::Restart() {}

void Server::StopThread() {
	m_ServerRunning = false;
}

bool Server::Start(std::uint16_t port, bool blocking) {
	if (!m_Listener.Listen(port, 10)) {
		utils::LOGE(utils::Format("Failed to bind port %u !", port));
		return false;
	}
	if (!m_Listener.SetBlocking(false)) {
		utils::LOGE("Failed to block server socket !");
		return false;
	}
	utils::LOG(utils::Format("Server started at port %u !", m_Listener.GetListeningPort()));
	m_TickCounter.Reset();
	m_ServerRunning = true;
	if (blocking) {
		ServerLoop();
	} else {
		StartThread();
	}
	return true;
}

void Server::Clean() {
	m_Listener.Close();
	m_Listener.Destroy();

	m_Connections.clear();

	utils::LOG("Server successfully stopped !");
}

void Server::Stop() {
	if (!m_ServerRunning)
		return;

	protocol::DisconnectPacket packet("Server closed");
	BroadcastPacket(&packet);

	StopThread();
}

void Server::Tick(std::uint64_t delta) {
	Accept();
	UpdateSockets();
	if (m_TickCounter.Update()) {
		protocol::ServerTpsPacket packet(m_TickCounter.GetTPS(), m_TickCounter.GetMSPT(), utils::GetTime());
		BroadcastPacket(&packet);
	}
}

void Server::Accept() {
	static std::uint8_t newPlayerID = 0;
	network::TCPSocket newSocket;
	if (m_Listener.Accept(newSocket)) {
		auto con = std::make_unique<ServerConnexion>(this, newSocket, newPlayerID);
		m_Connections.insert(std::move(ConnexionMap::value_type{newPlayerID, std::move(con)}));
		m_Connections[newPlayerID]->Start();
		newPlayerID++;
	}
}

void Server::UpdateSockets() {
	static std::vector<std::int16_t> closeConnexions;
	for (auto& connection : m_Connections) {
		auto& con = connection.second;
		if (con->GetSocketStatus() != network::TCPSocket::Status::Connected) {
			closeConnexions.push_back(connection.first);
		} else {
			con->UpdateSocket();
		}
	}

	for (auto id : closeConnexions) {
		RemoveConnexion(id);
	}

	closeConnexions.clear();
}

void Server::BroadcastPacket(const protocol::Packet* packet) {
	for (auto& connection : m_Connections) {
		connection.second->SendPacket(packet);
	}
}

void Server::BroadcastChatMessage(const std::string& msg) {
	protocol::ChatPacket packet(msg);
	BroadcastPacket(&packet);

#ifdef BLITZ_HEADLESS
	utils::LOG("[CHAT]\t" + msg);
#endif
}

void Server::RemoveConnexion(std::uint8_t connexionID) {
	m_Connections.erase(connexionID);

	protocol::PlayerLeavePacket packet(connexionID);
	BroadcastPacket(&packet);
}

std::uint16_t Server::GetListeningPort() {
	return m_Listener.GetListeningPort();
}

}  // namespace server
}  // namespace blitz
