#include "RTLogger.hpp"
#include <iostream>
#include "printf.h"

RTLogger& RTLogger::get() {
    if(instance) {
        return *instance;
    }
    RTLogger * maybeInstance = new RTLogger();
    RTLogger * expected = nullptr;
    if(!instance.compare_exchange_strong(expected, maybeInstance)) {
        // wasn't us
        delete maybeInstance;
        return *instance;
    }
    // was us, start thread
    maybeInstance->start();
    return *instance;
}

void RTLogger::log(const char* format, ...) {
#ifdef DEBUG
    constexpr const size_t bufsz = 512;
    char buf[bufsz];
    va_list va;
    va_start(va, format);
    const int numbytes = vsnprintf_(buf, bufsz, format, va);
    va_end(va);
    RTLogger& inst = RTLogger::get();
    if(numbytes < 0) {
        inst.q.try_emplace(message("(formatting failed)"));
    } else {
        inst.q.try_emplace(std::string(buf, std::min(bufsz, (size_t) numbytes)));
    }
    try {
        inst.sema.release();
    } catch(std::system_error&) { }
#endif
}

RTLogger::RTLogger() : q(1024), sema(0) { }
RTLogger::~RTLogger() { }

void RTLogger::start() {
#ifdef DEBUG
    thread = std::make_unique<std::thread>([this]{
        message msg;
        for(;;) {
            try {
                (void)sema.try_acquire_for(std::chrono::milliseconds(100));
            }
            catch (std::system_error&) { }
            auto gotMsg = q.try_pop(msg);
            if(!gotMsg) {
                continue;
            }
            if(msg.shouldExit) {
                break;
            }
            std::cerr << msg.val << "\n";
        }
    });
    atexit([]{
        RTLogger::get().stop();
    });
#endif
}

void RTLogger::stop() {
#ifdef DEBUG
    if(!thread || !thread->joinable()) {
        return;
    }
    q.emplace(message());
    thread->join();
    thread.reset();
#endif
}

std::atomic<RTLogger*> RTLogger::instance{nullptr};
