#include "mx_log.h"

#include <stdio.h>

#if MX_LOG_BACKEND_RTT
#include "SEGGER_RTT.h"
#endif

#ifndef MX_LOG_LINE_BUF_SIZE
#define MX_LOG_LINE_BUF_SIZE 256U
#endif

#ifndef MX_LOG_DEFAULT_LEVEL
#define MX_LOG_DEFAULT_LEVEL MX_LOG_LEVEL_DEBUG
#endif

static volatile mx_log_level_t s_log_level = MX_LOG_DEFAULT_LEVEL;
static mx_log_writer_t s_log_writer = 0;
static uint8_t s_log_inited = 0U;

static const char *mx_log_level_to_str(mx_log_level_t level)
{
    switch (level) {
    case MX_LOG_LEVEL_ERROR:
        return "E";
    case MX_LOG_LEVEL_WARN:
        return "W";
    case MX_LOG_LEVEL_INFO:
        return "I";
    case MX_LOG_LEVEL_DEBUG:
    default:
        return "D";
    }
}

static size_t mx_log_strnlen(const char *s, size_t maxlen)
{
    size_t len = 0U;
    if (s == 0) {
        return 0U;
    }
    while ((len < maxlen) && (s[len] != '\0')) {
        len++;
    }
    return len;
}

static void mx_log_output_line(const char *line)
{
    if (line == 0) {
        return;
    }

    if (s_log_writer != 0) {
        s_log_writer(line);
        return;
    }

#if MX_LOG_BACKEND_RTT
    SEGGER_RTT_WriteString(0, line);
#elif MX_LOG_BACKEND_STDIO
    (void)fputs(line, stdout);
#else
    (void)line;
#endif
}

void mx_log_init(void)
{
    if (s_log_inited) {
        return;
    }

#if MX_LOG_BACKEND_RTT
    SEGGER_RTT_Init();
    SEGGER_RTT_SetTerminal(0);
#endif

    s_log_inited = 1U;
}

void mx_log_set_level(mx_log_level_t level)
{
    s_log_level = level;
}

mx_log_level_t mx_log_get_level(void)
{
    return s_log_level;
}

void mx_log_set_writer(mx_log_writer_t writer)
{
    s_log_writer = writer;
}

void mx_log_vprintf(mx_log_level_t level, const char *tag, const char *fmt, va_list ap)
{
    char line[MX_LOG_LINE_BUF_SIZE];
    int prefix_len;
    int payload_len;
    size_t line_len;

    if ((fmt == 0) || (level > s_log_level)) {
        return;
    }

    if (!MX_LOG_ENABLE) {
        return;
    }

    if (!s_log_inited) {
        mx_log_init();
    }

    prefix_len = snprintf(line,
                          sizeof(line),
                          "[%s][%s] ",
                          mx_log_level_to_str(level),
                          (tag != 0) ? tag : "");
    if (prefix_len < 0) {
        return;
    }
    if ((size_t)prefix_len >= sizeof(line)) {
        line[sizeof(line) - 1U] = '\0';
        mx_log_output_line(line);
        return;
    }

    payload_len = vsnprintf(line + prefix_len, sizeof(line) - (size_t)prefix_len, fmt, ap);
    (void)payload_len;

    line_len = mx_log_strnlen(line, sizeof(line));
    if ((line_len + 2U) < sizeof(line)) {
        line[line_len++] = '\r';
        line[line_len++] = '\n';
        line[line_len] = '\0';
    } else {
        line[sizeof(line) - 1U] = '\0';
    }

    mx_log_output_line(line);
}

void mx_log_printf(mx_log_level_t level, const char *tag, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    mx_log_vprintf(level, tag, fmt, ap);
    va_end(ap);
}

void mx_log_hex_frame(const char *tag, const uint8_t *data, uint16_t len)
{
    static const char hex[] = "0123456789ABCDEF";
    char line[(32U * 3U) + 1U];
    uint16_t i = 0U;

    if ((data == 0) || (len == 0U)) {
        return;
    }

    while (i < len) {
        uint16_t chunk = (uint16_t)(len - i);
        uint16_t pos = 0U;
        uint16_t j;

        if (chunk > 32U) {
            chunk = 32U;
        }

        for (j = 0U; j < chunk; j++) {
            uint8_t v = data[(uint16_t)(i + j)];
            line[pos++] = hex[(v >> 4) & 0x0FU];
            line[pos++] = hex[v & 0x0FU];
            line[pos++] = ' ';
        }

        if (pos > 0U) {
            line[pos - 1U] = '\0';
        } else {
            line[0] = '\0';
        }

        MX_LOGD((tag != 0) ? tag : "HEX", "%s", line);
        i = (uint16_t)(i + chunk);
    }
}




