#include <mutex>
#include <fstream>
#include <atomic>

#include <Utilities/ServerSessionTable.hpp>
#include <Log/Log.hpp>

static std::mutex mutex;
static std::vector<ServerSession> sessions;
static std::atomic<uint64_t> sessionsGeneration;

std::string ServerSession::getLabel() const
{
    return formatString("$+@$+", username, server);
}

bool ServerSession::operator==(ServerSession other)
{
    return username == other.username && server == other.server;
}

void initServerSessionTable()
{
    std::ifstream ifs("servers.dat", std::ios::binary);
    if (!ifs.good())
    {
        Log::info("Failed to open `servers.dat`, assuming server list empty.");
        return;
    }

    ifs.seekg(0, std::ios_base::end);
    size_t size = (size_t) ifs.tellg();
    ifs.seekg(0, std::ios_base::beg);

    uint8_t *data = new uint8_t[size];
    ifs.read((char*) data, size);
    ifs.close();

    deserialize(data, size, sessions);
    delete[] data;
}

static void flushSessions()
{
    std::ofstream ofs("servers.dat", std::ios::binary);

    std::vector<uint8_t> data = serialize(sessions);

    ofs.write((char*) data.data(), data.size());
    ofs.close();
}

void setServerSession(std::string server, std::string username, UserDB::SessionID sid)
{
    std::unique_lock<std::mutex> lock(mutex);
    sessionsGeneration++;

    std::vector<ServerSession>::iterator it;
    for (it=sessions.begin(); it!=sessions.end(); ++it)
    {
        if (it->server == server && it->username == username)
        {
            it->sid = sid;
            std::iter_swap(it, sessions.begin());
            flushSessions();
            return;
        }
    }

    ServerSession session;
    session.server = server;
    session.username = username;
    session.sid = sid;
    sessions.push_back(session);
    
    if (sessions.size() != 1)
    {
        std::iter_swap(sessions.begin(), sessions.end()-1);
    }
    
    flushSessions();
}

UserDB::SessionID getLastKnownSessionID(std::string server, std::string username)
{
    std::unique_lock<std::mutex> lock(mutex);

    for (ServerSession &session : sessions)
    {
        if (session.server == server && session.username == username)
        {
            return session.sid;
        }
    }

    return UserDB::SessionID{};
}

std::vector<ServerSession> getServerSessions()
{
    std::unique_lock<std::mutex> lock(mutex);
    return sessions;
}

uint64_t getServerSessionsGeneration()
{
    return sessionsGeneration;
}