#include "common.hpp"
#include "config.hpp"
#include "hooks.hpp"
#include <sys/syscall.h>
#include <sys/time.h>
#include <execinfo.h>
#include <fcntl.h>


struct loglevels_s loglevels = {
    "[io]  ",
    "[dbg] ",
    "[info]",
    "[note]",
    TERM_RED "[err] " TERM_RESET,
};
loglevel_t* loglevel = &loglevels.io; // the default

const logctx_t* logctx = NULL;

static bool loglevel_set(void*& ctx, char* s) {
    for (loglevel_t* l = (loglevel_t*)&loglevels; l < (loglevel_t*)((char*)&loglevels + sizeof(loglevels)); ++l) {
        if (strstr((const char*)*l, s)) {
            #ifndef DEBUG
                if (l < &loglevels.info) return false;
            #endif
            loglevel = l;
            return true;
        }
    }
    return false;
}

static bool pidfile_set(void*& ctx, char* s) {
    int pid = (int)getpid();
    int fd = open(s, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
    if (fd == -1) {
        log_errno(error, "cannot open pidfile %s", s);
        return false;
    }
    (void)dprintf(fd, "%d\n", pid);
    EINTR_RETRY(close(fd));

    log(info, "wrote %d to %s", pid, s);
    ctx = (void*)strdup(s);
    return true;
}

static void pidfile_unset(void*& ctx) {
    if (ctx) {
        if (unlink((char*)ctx) == -1) {
            log_errno(error, "unlink(%s)", (char*)ctx); // but pretend success
        } else {
            log(debug, "removed %s", (char*)ctx);
        }
        safe_free(ctx);
    }
}

CONF_DEF(config) {
    ConfigKey loglevel;
    ConfigKey pid_file;
};
CONF_INIT(config) {
    CONF_KEY_INIT(loglevel, true, false, &loglevel_set, NULL);
    CONF_KEY_INIT(pid_file, false, false, &pidfile_set, &pidfile_unset);
}


#define gettid() syscall(SYS_gettid) // not in glibc
bool in_main_thread() {
    return getpid() == gettid();
}


int null_fd() {
    static int fd = open("/dev/null", O_RDWR);
    assert(fd != -1);
    return fd; // dup?
}


time_t* BEFORE_NOW = &NOW - 1;
time_t NOW = 0;
msec_t NOW_MSEC;
char NOW_STR[cstrlen("Sun Sep 16 01:03:52 1973")+1] = {};
void update_now() {
    #if 0
        NOW = time(NULL);
        NOW_MSEC = NOW*1000;
    #else
        struct timeval tv;
        (void)gettimeofday(&tv, NULL);
        NOW = tv.tv_sec; // TODO: round?
        NOW_MSEC = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
    #endif
    ctime_r(&NOW, NOW_STR);
    NOW_STR[cstrlen("Sun Sep 16 01:03:52 1973")] = '\0';
}
HOOK_ADD(&update_now, INIT, HOOK_PRIO_EARLY);


int RECONFIGURE=0;
int CLEANUP=0;
shutdown_e SHUTDOWN=SHUTDOWN_NONE;
static void signal_handler(int sig) {
    if (sig == SIGSEGV) {
        log_bt(STDERR_FILENO);
        raise(sig); // SA_RESETHAND
    } else if (sig == SIGHUP) {
        ++RECONFIGURE;
    } else if (sig == SIGUSR1) {
        ++CLEANUP;
    } else {
        if (SHUTDOWN != SHUTDOWN_NOW) {
            SHUTDOWN = (shutdown_e)((int)SHUTDOWN + 1);
        }
    }
}
HOOK(INIT, HOOK_PRIO_EARLY) { ///< registers signal handler(s)
    static struct sigaction sa = {};
    sigemptyset(&sa.sa_mask);

    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, NULL);

    sa.sa_handler = &signal_handler;
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL); // SA_RESETHAND?
    #ifdef DEBUG
        sa.sa_flags = SA_RESETHAND;
        sigaction(SIGSEGV, &sa, NULL);
    #endif
}


const char* src_basename(const char* fn) {
    static int off = -1;
    if (unlikely(off == -1)) {
        const char* a = __FILE__;
        const char* b = strrchr(a, '/');
        if (b) {
            off = b-a;
        } else {
            off = 0;
        }
    }
    return fn + off;
}


void log_bt(int fd) {
    static void* arr[4096];
    int n = backtrace(arr, 4096);

    if (fd != -1) {
        backtrace_symbols_fd(arr, n, fd);
    } else {
        char** bt = backtrace_symbols(arr, n);
        if (!bt) return;
        while (n-- > 0) log(error, "backtrace: %s", *bt++);
    }
}