#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <ctype.h>
#include <math.h>
#include <sysexits.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/time.h>

#define DEV

static char *log_level_names[] = {
    "ERROR",
    "WARN",
    "INFO",
    "L3",
    "L4",
    "L5",
    "L6",
    "L7",
    "DEBUG",
    "DEBUGEXT"
};

int g_log_level = 10;
int logfd;

const char *get_log_level_name(int log_level)
{
    return log_level_names[log_level];
}

void tlog(int level, const char *file, int line, const char *fun, const char *fmt, ...)
{
    va_list args;

    if (level > g_log_level)
        return;

    char timebuf[64] = "error?";
    struct tm ct;
    struct timeval tv = {0, 0};

    gettimeofday(&tv, NULL);
    localtime_r(&tv.tv_sec, &ct);

    if (logfd <= 0)
        return;

    char logline[2048];

    strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &ct);

    if (fun)
        snprintf(logline, sizeof(logline) - 1, "[%s.%06lu] %s:%d:%s [%s] ", timebuf, tv.tv_usec, file, line, fun, get_log_level_name(level));
    else
        snprintf(logline, sizeof(logline) - 1, "[%s.%06lu] [%s] ", timebuf, tv.tv_usec, get_log_level_name(level));

    int line_len = strlen(logline);

    va_start(args, fmt);
    vsnprintf(logline + line_len, sizeof(logline) - line_len - 2, fmt, args);
    va_end(args);

    line_len = strlen(logline);

    logline[line_len++] = '\n';

    /* Disable the warning locally */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
    write(logfd, logline, line_len);
#pragma GCC diagnostic pop
}

#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif

#define LDEBUGEX    9
#define LDEBUG      8

#define LINFO       2
#define LWARN       1
#define LERROR      0

#define LOGL(level, fmt, ...) tlog(level, __FILENAME__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__)

#define LOG(fmt, ...)   LOGL(LINFO, fmt, ##__VA_ARGS__)
#define LOG_S(fmt, ...)  slog(__FILENAME__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__)

#define GI2P(x) GINT_TO_POINTER((x))
#define GP2I(x) GPOINTER_TO_INT((x))

#define DO_PRAGMA(x) _Pragma(#x)
#define TODO(x) DO_PRAGMA(message "TODO: " #x)

#define DEBUGEX(fmt, ...) LOGL(LDEBUGEX, fmt, ##__VA_ARGS__)
#define DEBUG(fmt, ...) LOGL(LDEBUG, fmt, ##__VA_ARGS__)
#define INFO(fmt, ...)  LOGL(LINFO, fmt, ##__VA_ARGS__)
#define ERR(exit_code, fmt, ...) \
    do { \
        char *err_str = strerror(errno);\
        LOGL(LERROR, fmt ": %s", ##__VA_ARGS__, err_str);   \
        exit(exit_code); \
    } while (0)
#define ERRX(exit_code, fmt, ...) \
    do { \
        LOGL(LERROR, fmt, ##__VA_ARGS__);   \
        exit(exit_code); \
    } while (0)
#define WARN(fmt, ...) \
    do { \
        char *err_str = strerror(errno);\
        LOGL(LWARN, fmt": [%d] %s", ##__VA_ARGS__, errno, err_str);   \
    } while (0)
#define WARNX(fmt, ...) \
    do { \
        LOGL(LWARN, fmt, ##__VA_ARGS__);   \
    } while (0)


#ifdef DEV
#define mfree(p) {DEBUGEX("FREE: [%#lX]", p); free((p));}
#define mfree_s(p) {LOG_S("*** [DEBUG] FREE: [%#lX]", (p)); free((p));}
#define mmalloc(size) ({void *_nptr_ = malloc((size)); DEBUGEX("MALLOC: [%#lX], size %ld", _nptr_, (size)); _nptr_; })
#define mcalloc(nmemb, size) ({void *_nptr_ = calloc((nmemb), (size)); DEBUGEX("CALLOC: [%#lX], nmemb %ld, size %ld", _nptr_, (nmemb), (size)); _nptr_; })
#define mrealloc(ptr, size) ({ void *_nptr_; DEBUGEX("REALLOC before: [%#lX]", (ptr)); \
        _nptr_ = realloc((ptr), (size)); DEBUGEX("REALLOC after: [%#lX], size [%ld]", _nptr_, (size)); _nptr_; })
#else
#define mfree(p) free((p))
#define mfree_s(p) free((p))
#define mmalloc(size) malloc((size))
#define mcalloc(nmemb, size) calloc((nmemb), (size))
#define mrealloc(ptr, size)  realloc((ptr), (size))
#endif

#define nmemb   1
#define size    32
int main()
{
    logfd = STDOUT_FILENO;
    void *_nptr_ = calloc((nmemb), (size));
    LOGL(LDEBUGEX, "CALLOC: %#lX nmemb %ld, size %ld", _nptr_, (nmemb), (size));
    free(_nptr_);

    return 0;
}
