#include "game/server/Server.h"
#include <iostream>
#include "protocol/PacketFactory.h"

namespace td {
namespace server {

Server::Server(const std::string& worldFilePath) : m_ServerRunning(false) {
    m_Game.GetWorld()->LoadMapFromFile(worldFilePath);
}

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

void Server::StartThread() {
    m_Thread = std::thread([this]() {
        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();
                std::uint64_t sleepTime = SERVER_TICK - (delta - SERVER_TICK);
                std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
            }

        }
        Clean();
        });
}

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

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

bool Server::Start(std::uint16_t port) {
    if (!m_Listener.Listen(port, 10)) {
        std::cout << "Failed to bind port " << port << " !\n";
        return false;
    }
    if (!m_Listener.SetBlocking(false)) {
        std::cout << "Failed to block server socket !\n";
        return false;
    }
    std::cout << "Server started at port " << port << " !\n";
    m_TickCounter.Reset();
    m_ServerRunning = true;
    StartThread();
    return true;
}

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

    m_Connections.clear();
    GetPlayers().clear();

    std::cout << "Server successfully stopped !\n";
}

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(), 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() {
    std::int16_t closedConnexionID = -1;
    for (auto& connection : m_Connections) {
        ServerConnexion& con = connection.second;
        if (con.GetSocketStatus() != network::Socket::Status::Connected) {
            closedConnexionID = connection.first;
        } else {
            con.UpdateSocket();
        }
    }
    if (closedConnexionID != -1) {
        RemoveConnexion(closedConnexionID);
    }
}

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) {
    GetPlayers().erase(GetPlayers().find(connexionID));
    m_Connections.erase(connexionID);
    m_Lobby.OnPlayerLeave(connexionID);
    OnPlayerLeave(connexionID);
}

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

    GetPlayers().insert({ id, game::Player{id} });
}

void Server::OnPlayerLeave(std::uint8_t id) {
    protocol::PlayerLeavePacket packet(id);
    BroadcastPacket(&packet);
}

} // namespace server
} // namespace td
