#pragma once

#include <map>
#include <thread>

#include "td/network/TCPListener.h"
#include "td/protocol/Protocol.h"
#include "td/protocol/PacketDispatcher.h"
#include "td/protocol/PacketHandler.h"
#include "server/game/ServerGame.h"
#include "server/ServerConnexion.h"
#include "server/Lobby.h"

#define SERVER_TPS 20
#define SERVER_TICK 1000 / SERVER_TPS

namespace td {
namespace server {

typedef std::map<std::uint8_t, ServerConnexion> ConnexionMap;

class TickCounter {
private:
	float m_TPS;
	float m_MSPT;
	std::uint64_t m_LastTPSTime;
	std::uint8_t m_TickCount;
public:
	TickCounter() {}

	void Reset() {
		m_TPS = SERVER_TPS;
		m_LastTPSTime = utils::GetTime();
		m_TickCount = 0;
	}

	bool Update() { // return true when tps is updated
		m_TickCount++;
		if (m_TickCount >= SERVER_TPS) {
			std::uint64_t timeElapsedSinceLast20Ticks = td::utils::GetTime() - m_LastTPSTime;
			m_TPS = static_cast<float>(SERVER_TPS) / static_cast<float>(timeElapsedSinceLast20Ticks / 1000.0f);
			m_TickCount = 0;
			m_LastTPSTime = td::utils::GetTime();
			return true;
		}
		return false;
	}

	float GetTPS() const { return m_TPS; }
	float GetMSPT() const { return m_MSPT; }

	void SetMSPT(float mspt) { m_MSPT = mspt; }
};

class Server {
private:
	network::TCPListener m_Listener;
	ConnexionMap m_Connections;
	ServerGame m_Game{ this };
	Lobby m_Lobby{ this };
	TickCounter m_TickCounter;

	std::thread m_Thread;
	bool m_ServerRunning;
public:
	Server();
	virtual ~Server();

	bool Start(std::uint16_t port, bool blocking);
	void Stop(); // force the server to stop
	void Close(); // at the end of a game

	void Restart();  // go back to lobby state

	bool LoadMap(const std::string& worldFilePath);
	bool IsMapLoaded();

	void RemoveConnexion(std::uint8_t connexionID);

	void BroadcastPacket(const protocol::Packet* packet);

	float GetMSPT() const { return m_TickCounter.GetMSPT(); }
	float GetTPS() const { return m_TickCounter.GetTPS(); }

	bool IsRunning() { return m_ServerRunning; }

	const ServerGame& GetGame() const { return m_Game; }
	ServerGame& GetGame() { return m_Game; }

	const Lobby& GetLobby() const { return m_Lobby; }
	const ConnexionMap& GetConnexions() const { return m_Connections; }
	ConnexionMap& GetConnexions() { return m_Connections; }

	const game::PlayerList& GetPlayers() const { return m_Game.GetPlayers(); }
	game::PlayerList& GetPlayers() { return m_Game.GetPlayers(); }

private:
	void Accept();
	void UpdateSockets();

	void Clean();
	void StartThread();
	void StopThread();
	void ServerLoop();
	void Tick(std::uint64_t delta);

	void OnPlayerJoin(std::uint8_t id);
	void OnPlayerLeave(std::uint8_t id);
};

} // namespace server
} // namespace td