#pragma once

#include <cstdint>
#include <map>
#include <thread>

#include "network/TCPListener.h"
#include "protocol/Protocol.h"
#include "protocol/PacketDispatcher.h"
#include "protocol/PacketHandler.h"
#include "ServerGame.h"
#include "ServerConnexion.h"
#include "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;
    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 = (float)SERVER_TPS / (float)(timeElapsedSinceLast20Ticks / 1000.0f);
            m_TickCount = 0;
            m_LastTPSTime = td::utils::getTime();
            return true;
        }
        return false;
    }

    float getTPS() const { return m_TPS; }
};

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(const std::string& worldFilePath);
    virtual ~Server() {}

    bool start(std::uint16_t port);
    void stop();

    void lauchGame();

    void removeConnexion(std::uint8_t connexionID);

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

    float getTPS() const { return m_TickCounter.getTPS(); }

    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 startThread();
    void stopThread();
    void tick(std::uint64_t delta);

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

} // namespace server
} // namespace td