#include "uv_log.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <uv.h>

#include "mybuf.h"

static uv_log_level_t g_level = uv_log_level_info;
static char g_logfile[1024] = {0};

uv_log_level_t uv_log_level_from_string(const char* str) {
#define XX(name, value)            \
    if (strcmp(str, #name) == 0) { \
        return value;              \
    }
    UV_LOG_LEVELS_MAP(XX)
#undef XX

    return uv_log_level_invalid;
}

const char* uv_log_level_to_string(uv_log_level_t level) {
    switch (level) {
#define XX(name, value) \
    case value: {       \
        return #name;   \
    }
        UV_LOG_LEVELS_MAP(XX)
#undef XX
        default:
            return "invalid level";
            break;
    }
}

void uv_log_set_level(uv_log_level_t level) {
    g_level = level;
}

void uv_log_set_log_file(const char* file) {
    if (file) {
        strncpy(g_logfile, file, sizeof(g_logfile));
    } else {
        g_logfile[0] = '\0';
    }
}

uv_log_level_t uv_log_get_level() {
    return g_level;
}

void uv_log_raw(uv_log_level_t level, const char* msg) {
    size_t off;
    uv_timeval64_t now;
    char buf[64];
    const char* c = "AVDIWEF";
    int log_to_stdout = g_logfile[0] == '\0';
    FILE* fp = NULL;

    if (level < g_level || level > uv_log_level_fatal) return;
    fp = log_to_stdout ? stdout : fopen(g_logfile, "a");
    if (!fp) return;

    uv_gettimeofday(&now);
    off = strftime(buf, sizeof(buf), "%b %d %H:%M:%S.", localtime(&now.tv_sec));
    snprintf(buf + off, sizeof(buf) - off, "%03d", (int)(now.tv_usec / 1000));
    fprintf(fp, "%s %c %s\n", buf, c[level], msg);
    fflush(fp);
    if (!log_to_stdout) fclose(fp);
}

void uv_log_hexdump(uv_log_level_t level, const char* buf, size_t len,
                    int show_header, int show_address, int show_ascii, char def_ascii,
                    const char* newline) {
    mybuf_t mybuf;
    if (level < g_level || level > uv_log_level_fatal) return;
    mybuf_init(&mybuf);
    mybuf_cat_printf(&mybuf, "hex dump of %p, len=%zu\n", buf, len);
    mybuf_append_hexdump(&mybuf, buf, len, show_header, show_address, show_ascii, def_ascii, newline);
    uv_log_raw(level, mybuf.buf);
    mybuf_clear(&mybuf);
}

static void uv_log_vprintf(uv_log_level_t level, const char* fmt, va_list ap) {
    va_list cpy;
    char msg[1024], *p = msg;
    size_t len = sizeof(msg);
    int l = 0;

    while (1) {
        va_copy(cpy, ap);
        l = vsnprintf(p, len, fmt, cpy);
        va_end(cpy);

        if (l < 0) {
            return;
        } else if ((size_t)l >= len) {
            if (p != msg) free(p);
            len = (size_t)l + 1;
            p = malloc(len);
            if (p == NULL) {
                return;
            }
            continue;
        }
        break;
    }

    uv_log_raw(level, p);
    if (p != msg)
        free(p);
}

void uv_log(uv_log_level_t level, const char* fmt, ...) {
    va_list ap;
    if (level < g_level) return;
    va_start(ap, fmt);
    uv_log_vprintf(level, fmt, ap);
    va_end(ap);
}