#include <string.h>

#include <util/constexpr_hash.h>

#include "assert.h"
#include "logger.h"
#include "printf/printf.h"

namespace logs {
#define LOG(name, lvl) \
    const log::area_t name = #name ## _h; \
    const char * name ## _name = #name;
#include <j6/tables/log_areas.inc>
#undef LOG
}

namespace log {

logger *logger::s_log = nullptr;
const char *logger::s_level_names[] = {"", "debug", "info", "warn", "error", "fatal"};

logger::logger(logger::immediate_cb output) :
    m_buffer(nullptr, 0),
    m_immediate(output),
    m_flush(nullptr),
    m_sequence(0)
{
    memset(&m_levels, 0, sizeof(m_levels));
    memset(&m_names, 0, sizeof(m_names));
    s_log = this;
}

logger::logger(uint8_t *buffer, size_t size, logger::immediate_cb output) :
    m_buffer(buffer, size),
    m_immediate(output),
    m_flush(nullptr),
    m_sequence(0)
{
    memset(&m_levels, 0, sizeof(m_levels));
    memset(&m_names, 0, sizeof(m_names));
    s_log = this;

#define LOG(name, lvl) \
    register_area(logs::name, logs::name ## _name, log::level::lvl);
#include <j6/tables/log_areas.inc>
#undef LOG
}

void
logger::set_level(area_t area, level l)
{
    unsigned uarea = static_cast<unsigned>(area);
    uint8_t ulevel = static_cast<uint8_t>(l) & 0x0f;
    uint8_t &flags = m_levels[uarea / 2];
    if (uarea & 1)
        flags = (flags & 0x0f) | (ulevel << 4);
    else
        flags = (flags & 0xf0) | ulevel;
}

level
logger::get_level(area_t area)
{
    unsigned uarea = static_cast<unsigned>(area);
    uint8_t &flags = m_levels[uarea / 2];
    if (uarea & 1)
        return static_cast<level>((flags & 0xf0) >> 4);
    else
        return static_cast<level>(flags & 0x0f);
}

void
logger::register_area(area_t area, const char *name, level verbosity)
{
    m_names[area] = name;
    set_level(area, verbosity);
}

void
logger::output(level severity, area_t area, const char *fmt, va_list args)
{
    uint8_t buffer[256];
    entry *header = reinterpret_cast<entry *>(buffer);
    header->bytes = sizeof(entry);
    header->area = area;
    header->severity = severity;
    header->sequence = m_sequence++;

    header->bytes +=
        vsnprintf(header->message, sizeof(buffer) - sizeof(entry), fmt, args);

    util::scoped_lock lock {m_lock};

    if (m_immediate) {
        buffer[header->bytes] = 0;
        m_immediate(area, severity, header->message);
        return;
    }

    uint8_t *out;
    size_t n = m_buffer.reserve(header->bytes, reinterpret_cast<void**>(&out));
    if (n < sizeof(entry)) {
        m_buffer.commit(0); // Cannot even write the header, abort
        return;
    }

    if (n < header->bytes)
        header->bytes = n;

    memcpy(out, buffer, n);
    m_buffer.commit(n);

    if (m_flush)
        m_flush();
}

size_t
logger::get_entry(void *buffer, size_t size)
{
    util::scoped_lock lock {m_lock};

    void *out;
    size_t out_size = m_buffer.get_block(&out);
    if (out_size == 0 || out == 0)
        return 0;

    kassert(out_size >= sizeof(entry), "Couldn't read a full entry");
    if (out_size < sizeof(entry))
        return 0;

    entry *ent = reinterpret_cast<entry *>(out);
    if (size >= ent->bytes) {
        memcpy(buffer, out, ent->bytes);
        m_buffer.consume(ent->bytes);
    }

    return ent->bytes;
}

#define LOG_LEVEL_FUNCTION(name) \
    void name (area_t area, const char *fmt, ...) { \
        logger *l = logger::s_log; \
        if (!l) return; \
        level limit = l->get_level(area); \
        if (limit == level::none || level::name < limit) return; \
        va_list args; \
        va_start(args, fmt); \
        l->output(level::name, area, fmt, args); \
        va_end(args); \
    }

LOG_LEVEL_FUNCTION(debug);
LOG_LEVEL_FUNCTION(info);
LOG_LEVEL_FUNCTION(warn);
LOG_LEVEL_FUNCTION(error);

void fatal(area_t area, const char *fmt, ...)
{
    logger *l = logger::s_log;
    if (!l) return;

    va_list args;
    va_start(args, fmt);
    l->output(level::fatal, area, fmt, args);
    va_end(args);

    kassert(false, "log::fatal");
}

} // namespace log
