#include "server/Server.h"
#include "td/protocol/PacketFactory.h"

#include "td/protocol/packets/DisconnectPacket.h"
#include "td/protocol/packets/ServerTpsPacket.h"
#include "td/protocol/packets/PlayerLeavePacket.h"

#include "td/misc/Format.h"

namespace td {
namespace server {

Server::Server() : m_ServerRunning(false) {
	
}

Server::~Server() {
	if (m_Thread.joinable())
		m_Thread.join();
}

bool Server::LoadMap(const std::string& worldFilePath) {
	return m_Game.GetWorld()->LoadMapFromFile(worldFilePath);
}

bool Server::IsMapLoaded() {
	return !m_Game.GetWorld()->GetTilePalette().empty();
}

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

		std::uint64_t delta = time - lastTime;

		if (delta >= SERVER_TICK) {
			Tick(delta);
			lastTime = td::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() {
	m_Game.Reset();
	m_Lobby.OpenLobby();
	
	m_Game.NotifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::Lobby);

	utils::LOG("Game restarted !");
}

void Server::Close() {
	StopThread();
}

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

bool Server::Start(std::uint16_t port, bool blocking) {
	if (!IsMapLoaded()) {
		utils::LOGE(utils::format("No map loaded !"));
		return false;
	}
	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 !", port));
	m_TickCounter.Reset();
	m_ServerRunning = true;
	m_Lobby.OpenLobby();
	if (blocking) {
		ServerLoop();
	} else {
		StartThread();
	}
	return true;
}

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

	m_Connections.clear();
	GetPlayers().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();
	m_Lobby.Tick();
	m_Game.Tick(delta);
	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)) {
		ServerConnexion con(newSocket, newPlayerID);
		m_Connections.insert(std::move(ConnexionMap::value_type{ newPlayerID, std::move(con) }));
		OnPlayerJoin(newPlayerID);
		m_Connections[newPlayerID].SetServer(this);
		newPlayerID++;
	}
}

void Server::UpdateSockets() {
	static std::vector<std::int16_t> closeConnexions;
	for (auto& connection : m_Connections) {
		ServerConnexion& con = connection.second;
		if (con.GetSocketStatus() != network::Socket::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) {
		ServerConnexion& con = connection.second;
		con.SendPacket(packet);
	}
}

void Server::RemoveConnexion(std::uint8_t connexionID) {
	m_Connections.erase(connexionID);
	
	td::game::Player* player = GetGame().GetPlayerById(connexionID);
	SAFE_CHECK(player);

	std::string playerName = player->GetName();
	
	GetGame().RemovePlayer(connexionID);
	m_Lobby.OnPlayerLeave(connexionID);

	utils::LOG(utils::format("\t[%s] left !", playerName.c_str()));
	
	OnPlayerLeave(connexionID);
}

void Server::OnPlayerJoin(std::uint8_t id) {
	m_Lobby.OnPlayerJoin(id);
}

void Server::OnPlayerLeave(std::uint8_t id) {
	GetGame().NotifyListeners(&game::GameListener::OnPlayerLeave, id);

	if (GetPlayers().empty() && m_Game.GetGameState() != game::GameState::Lobby) {
		utils::LOG("All players left. Go back to lobby ...");
		Restart();
	}
}

} // namespace server
} // namespace td
