#include "server/Server.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/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"

namespace blitz {
namespace server {

Server::Server() : m_TickCounter(SERVER_TPS), m_Game(this), m_ServerRunning(false) {}

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(static_cast<float>(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("[Server] Failed to bind port %u !", port));
		return false;
	}
	if (!m_Listener.SetBlocking(false)) {
		utils::LOGE("[Server] Failed to block server socket !");
		return false;
	}
	utils::LOG(utils::Format("[Server] 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] 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);
	}
	m_Game.Tick(delta);
}

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

game::PlayerID Server::GetNewPlayerID() {
	static game::PlayerID lastPlayerID = 0;
	game::PlayerID newPlayerID = lastPlayerID;
	lastPlayerID++;
	return newPlayerID;
}

void Server::UpdateSockets() {
	static std::vector<std::uint8_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);
}

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

void Server::AddBot() {
	game::PlayerID botID = GetNewPlayerID();

	m_Game.AddPlayer(botID, "Bot " + std::to_string(static_cast<unsigned int>(botID)));

	// set the bot at a random location to be able to see several of them
	game::Player* botPlayer = m_Game.GetPlayerById(botID);
	botPlayer->SetPosition({utils::GetRandomReal(-10.0f, 10.0f), 0.0f, utils::GetRandomReal(-10.0f, 10.0f)});
	botPlayer->SetBot();
}

void Server::KickPlayer(game::PlayerID playerID) {
	auto it = m_Connections.find(playerID);

	if (it != m_Connections.end()) {
		protocol::DisconnectPacket packet("Tu dois disparaître (in game) !");
		m_Connections.at(playerID)->SendPacket(&packet);
	}

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

	if (!player)
		return;

	m_Game.RemovePlayer(playerID);

	if (player->IsBot()) {
	} else {
		RemoveConnexion(playerID);
	}
}

}  // namespace server
}  // namespace blitz
