// -*- c-file-style: "bsd" -*-

#include "message.h"

#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>

#define BACKTRACE_ON_PANIC 1
#if BACKTRACE_ON_PANIC
#include <execinfo.h>
#endif

#define TIMESTAMP_BASE62 0
#define TIMESTAMP_NUMERIC 1

void __attribute__((weak))
Message_VA(enum Message_Type type,
           const char *fname, int line, const char *func,
           const char *fmt, va_list args)
{
        _Message_VA(type, stderr, fname, line, func, fmt, args);
}

void
_Message(enum Message_Type type,
         const char *fname, int line, const char *func,
         const char *fmt, ...)
{
        va_list args;
        va_start(args, fmt);
        Message_VA(type, fname, line, func, fmt, args);
        va_end(args);

        Message_DoFrees();
}

void
_Message_VA(enum Message_Type type, FILE *fp,
            const char *fname, int line, const char *func,
            const char *fmt, va_list args)
{
        static int haveColor = -1;
        static const struct {
                const char *prefix;
                const char *color;
        } descs[MSG_NUM_TYPES + 1] = {
                [MSG_PANIC]     = {"PANIC", "1;31"},
                [MSG_WARNING]   = {"!",     "1;33"},
                [MSG_NOTICE]    = {"*",     0},
                [MSG_DEBUG]     = {" ",     "22;37"},
                [MSG_NUM_TYPES] = {"<Invalid message type>", 0},
        };

        if (haveColor == -1)
                haveColor = isatty(fileno(fp));

        int nDesc = type & (~MSG_PERROR);
        if (nDesc > MSG_NUM_TYPES)
                nDesc = MSG_NUM_TYPES;

        if (haveColor && descs[nDesc].color)
                fprintf(fp, "\033[%sm", descs[nDesc].color);

        if (TIMESTAMP_NUMERIC || TIMESTAMP_BASE62) {
                struct timeval tv;
                if (gettimeofday(&tv, NULL) >= 0) {
                        if (TIMESTAMP_NUMERIC) {
                                struct tm *tm = localtime(&tv.tv_sec);
                                fprintf(fp,
                                        "%04d%02d%02d-%02d%02d%02d-%04d %05d ",
                                        1900+tm->tm_year, tm->tm_mon, tm->tm_mday,
                                        tm->tm_hour, tm->tm_min, tm->tm_sec,
                                        (int)(tv.tv_usec / 100), getpid());
                        } else if (TIMESTAMP_BASE62) {
                                static const char base62[] =
                                        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                        "abcdefghijklmnopqrstuvwxyz";
                                // Convert to tenths of milliseconds.
                                uint64_t val = (uint64_t)tv.tv_sec * 10000 +
                                        tv.tv_usec / 100;
                                char buf[5];
                                for (int i = 0; i < sizeof buf; ++i) {
                                        buf[sizeof buf - i - 1] =
                                                base62[val % 62];
                                        val /= 62;
                                }
                                fprintf(fp, "%.*s %05d ", (int)sizeof buf, buf,
                                        getpid());
                        }
                }
        }

        fprintf(fp, "%s ", descs[nDesc].prefix);

        if (fname) {
                const char *fbasename = strrchr(fname, '/');
                if (fbasename)
                        ++fbasename;
                else
                        fbasename = fname;
                char filepos[32];
                snprintf(filepos, sizeof(filepos)/sizeof(filepos[0]),
                         "(%s:%d):", fbasename, line);
                fprintf(fp, "%-15s %-19s ",
                        func, filepos);
        }

        vfprintf(fp, fmt, args);

        if (type & MSG_PERROR)
                fprintf(fp, ": %s", strerror(errno));

        if (haveColor && descs[nDesc].color)
                fputs("\033[0m", fp);
        fprintf(fp, "\n");
        fflush(fp);
}

void _Panic(void)
{
#if BACKTRACE_ON_PANIC
        void *bt[100];
        size_t size = backtrace(bt, 100);
        char **strings = backtrace_symbols(bt, size);
        if (strings) {
                for (int i = 0; i < size; ++i) {
                        Warning("%s", strings[i]);
                }
        }
#endif
        abort();
        exit(1);
}

#define MAX_DEFERRED_FREES 16

static void *deferredFrees[MAX_DEFERRED_FREES];
static int nDeferredFrees;

const char *
Message_DFree(char *buf)
{
        if (buf) {
                if (nDeferredFrees == MAX_DEFERRED_FREES)
                        Panic("Too many deferred frees");
                deferredFrees[nDeferredFrees++] = buf;
        }
        return buf;
}

void
Message_DoFrees(void)
{
        for (int i = 0; i < nDeferredFrees; ++i)
                free(deferredFrees[i]);
        nDeferredFrees = 0;
}

bool
_Message_DebugEnabled(const char *fname)
{
        static bool parsed;
        static char *buf;
        static char **pats;
        static int nPats;
        if (!parsed) {
                parsed = true;
                const char *env = getenv("DEBUG");
                if (env && strlen(env)) {
                        buf = strdup(env);
                        if (!buf)
                                Panic("Failed to allocate buffer");
                        nPats = 1;
                        for (int i = 0; i < strlen(buf); ++i) {
                                if (buf[i] == ',' || buf[i] == ' ')
                                        ++nPats;
                        }
                        pats = malloc(nPats * sizeof *pats);
                        if (!pats)
                                Panic("Failed to allocate buffer");
                        pats[0] = buf;
                        int patOut = 1;
                        for (int i = 0; i < strlen(buf); ++i) {
                                if (buf[i] == ',' || buf[i] == ' ') {
                                        pats[patOut] = &buf[i+1];
                                        buf[i] = '\0';
                                }
                        }
                }
        }
        if (!buf)
                return false;

        bool result = false;
        if (strcmp(pats[0], "all") == 0 || pats[0][0] == '^')
                result = true;

        const char *fbasename = strrchr(fname, '/');
        if (fbasename)
                ++fbasename;
        for (int i = 0; i < nPats; ++i) {
                char *pat = pats[i];
                if (pat[0] == '^')
                        ++pat;

                if (fnmatch(pat, fname, FNM_PATHNAME) == 0 ||
                    (fbasename &&
                     fnmatch(pat, fbasename, FNM_PATHNAME) == 0)) {
                        if (pats[i][0] == '^')
                                result = false;
                        else
                                result = true;
                }
        }
        return result;
}

void
_Message_Hexdump(const void *data, int len)
{
        const unsigned char *d = data;
        char buf[80];
        char *out;

        for (int base = 0; base < len; base += 16) {
                out = buf;
                sprintf(out, "%08x", base);
                out += 8;
                for (int offset = 0; offset < 16; ++offset) {
                        if (offset == 8)
                                *(out++) = ' ';
                        if (base + offset >= len) {
                                strcpy(out, "   ");
                        } else {
                                sprintf(out, " %02x", d[base+offset]);
                        }
                        out += 3;
                }
                strcpy(out, " |");
                out += 2;
                for (int offset = 0; offset < 16; ++offset) {
                        if (base + offset >= len)
                                break;
                        else if (isprint(d[base+offset]))
                                *out = d[base+offset];
                        else
                                *out = '.';
                        ++out;
                }
                strcpy(out, "|");
                _Message(MSG_DEBUG, NULL, 0, NULL, "%s", buf);
        }
}

char *
Message_FmtBlob(const void *data, int len)
{
        static int blobmax = -1;
        if (blobmax == -1) {
                const char *env = getenv("BLOBMAX");
                if (!env) {
                        blobmax = 32;
                } else {
                        blobmax = atoi(env);
                }
        }

        int plen = len;
        if (plen > blobmax)
                plen = blobmax;

        char *buf = malloc(plen + 3);
        if (!buf)
                return NULL;

        buf[0] = '|';
        int i;
        for (i = 0; i < plen; ++i) {
                if (isprint(((char*)data)[i]))
                        buf[i+1] = ((char*)data)[i];
                else
                        buf[i+1] = '.';
        }
        if (len != plen)
                buf[i+1] = '>';
        else
                buf[i+1] = '|';
        buf[i+2] = '\0';
        return buf;
}

void
PanicOnSignal(int signo)
{
        Panic("Received signal %d", signo);
}
