/**\file n_log.c
 * generic logging system
 *\author Castagnier Mickael
 *\date 2013-03-12
 */

#include "nilorea/n_common.h"
#include "nilorea/n_log.h"

#include <pthread.h>
#include <string.h>
#include <inttypes.h>

#ifndef __windows__
#include <syslog.h>
#else
#include <time.h>
#endif

/*! internal struct to handle log types */
typedef struct LOG_LEVELS {
    /*! string of log type */
    char* c_name;
    /*! numeric value of log type */
    int c_val;
    /*! event log value */
    char* w_name;

} LOG_LEVELS;

/*! array of log levels */
static LOG_LEVELS prioritynames[] =
    {
        {"EMERG", LOG_EMERG, "ERROR"},
        {"ALERT", LOG_ALERT, "ERROR"},
        {"CRITICAL", LOG_CRIT, "ERROR"},
        {"ERR", LOG_ERR, "ERROR"},
        {"WARNING", LOG_WARNING, "WARNING"},
        {"NOTICE", LOG_NOTICE, "SUCCESS"},
        {"INFO", LOG_INFO, "INFORMATION"},
        {"DEBUG", LOG_DEBUG, "INFORMATION"},
        {NULL, -1, NULL}};

/*! static global maximum wanted log level value */
static int LOG_LEVEL = LOG_NULL;

/*! static global logging type ( STDERR, FILE, SYSJRNL ) */
static int LOG_TYPE = LOG_STDERR;

/*! static FILE handling if logging to file is enabled */
static FILE* log_file = NULL;

/*! static proc name, for windows event log */
char* proc_name = NULL;

/*!\fn open_sysjrnl( char *identity )
 *\brief Open connection to syslog or create internals for event log
 *\param identity Tag for syslog or NULL to use argv[0]
 *\return NULL or identity if success
 */
char* open_sysjrnl(char* identity) {
    __n_assert(identity, return NULL);
#ifndef __windows__
    /* LOG_CONS: log to console if no syslog available
     * LOG_PID: add pid of calling process to log
     * Local use 7 : compat with older logging systems
     */
    openlog(identity, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL7);
#endif
    proc_name = strdup(identity);
    return proc_name;
} /* open_sysjrnl */

/*!\fn void close_sysjrnl( void )
 * \brief Close syslog connection or clean internals for event log
 */
void close_sysjrnl(void) {
#ifndef __windows__
    closelog();
#endif
    FreeNoLog(proc_name);
} /* close_sysjrnl */

/*!\fn void set_log_level( int log_level )
 *\brief Set the global log level value ( static int LOG_LEVEL )
 *\param log_level Log level value. Supported: NOLOG,LOG_NOTICE/INFO/ERR/DEBUG,LOG_FILE/STDERR/SYSJRNL
 */
void set_log_level(const int log_level) {
    if (log_level == LOG_FILE) {
        LOG_TYPE = LOG_FILE;
        return;
    }
    if (log_level == LOG_STDERR) {
        LOG_TYPE = LOG_STDERR;
        return;
    }
    if (log_level == LOG_SYSJRNL) {
        LOG_TYPE = LOG_SYSJRNL;
        return;
    }
    LOG_LEVEL = log_level;
} /* set_log_level() */

/*!\fn int get_log_level( )
 *\brief Get the global log level value
 *\return static int LOG_LEVEL
 */
int get_log_level(void) {
    return LOG_LEVEL;
} /* get_log_level() */

/*!\fn int set_log_file( char *file )
 *\brief Set the logging to a file instead of stderr
 *\param file The filename where to log
 *\return TRUE or FALSE
 */
int set_log_file(char* file) {
    __n_assert(file, return FALSE);

    if (!log_file)
        log_file = fopen(file, "a+");
    else {
        fclose(log_file);
        log_file = fopen(file, "a+");
    }

    set_log_level(LOG_FILE);

    __n_assert(log_file, return FALSE);

    return TRUE;
} /* set_log_file */

/*!\fn FILE *get_log_file()
 *\brief return the current log_file
 *\return a valid FILE handle or NULL
 */
FILE* get_log_file(void) {
    return log_file;
} /*get_log_level() */

#ifndef _vscprintf
/*!\fn int _vscprintf_so(const char * format, va_list pargs)
 *\brief compute the size of a string made with format 'fmt' and arguments in the va_list 'ap', helper for vasprintf
 *\param format format to pass to printf
 *\param pargs va_list to use as printf parameter
 *\return -1 or the size of the string
 */
int _vscprintf_so(const char* format, va_list pargs) {
    int retval;
    va_list argcopy;
    va_copy(argcopy, pargs);
    retval = vsnprintf(NULL, 0, format, argcopy);
    va_end(argcopy);
    return retval;
}
#endif

#ifndef vasprintf
/*!\fn int vasprintf(char **strp, const char *fmt, va_list ap)
 *\brief snprintf from a va_list, helper for asprintf
 *\param strp string holding the result
 *\param fmt format to pass to printf
 *\param ap va_list to use as printf parameter
 *\return -1 or the size of the string
 */
int vasprintf(char** strp, const char* fmt, va_list ap) {
    long long int len = _vscprintf_so(fmt, ap);
    if (len == -1)
        return -1;
    char* str = NULL;
    Malloc(str, char, (size_t)len + 1 + sizeof(void*));  // len + EndOfString + padding
    if (!str)
        return -1;
    int r = vsnprintf(str, (size_t)(len + 1), fmt, ap); /* "secure" version of vsprintf */
    if (r == -1)
        return free(str), -1;
    *strp = str;
    return r;
}
#endif

#ifndef asprintf
/*!\fn int asprintf(char *strp[], const char *fmt, ...)
 *\brief snprintf from a va_list
 *\param strp string holding the result
 *\param fmt format to pass to printf
 *\param ... list of format parameters
 *\return -1 or the size of the string
 */
int asprintf(char* strp[], const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vasprintf(strp, fmt, ap);
    va_end(ap);
    return r;
}
#endif

/*!\fn void _n_log( int level , const char *file , const char *func , int line , const char *format , ... )
 *\brief Logging function. log( level , const char *format , ... ) is a macro around _log
 *\param level Logging level
 *\param file File containing the emmited log
 *\param func Function emmiting the log
 *\param line Line of the log
 *\param format Format and string of the log, printf style
 */
void _n_log(int level, const char* file, const char* func, int line, const char* format, ...) {
    FILE* out = NULL;

    if (level == LOG_NULL)
        return;

    if (!log_file)
        out = stderr;
    else
        out = log_file;

    int log_level = get_log_level();

    if (level <= log_level) {
        va_list args;
        char* syslogbuffer = NULL;
        char* eventbuffer = NULL;
#ifdef __windows__
        size_t needed = 0;
        char* name = "NULL";
        if (proc_name)
            name = proc_name;
#endif

        switch (LOG_TYPE) {
            case LOG_SYSJRNL:
                va_start(args, format);
                vasprintf(&syslogbuffer, format, args);
                va_end(args);
#ifdef __windows__
                needed = (unsigned long long)snprintf(NULL, 0, "start /B EventCreate /t %s /id 666 /l APPLICATION /so %s /d \"%s\" > NUL 2>&1", prioritynames[level].w_name, name, syslogbuffer);
                Malloc(eventbuffer, char, needed + 4);
                sprintf(eventbuffer, "start /B EventCreate /t %s /id 666 /l APPLICATION /so %s /d \"%s\" > NUL 2>&1", prioritynames[level].w_name, name, syslogbuffer);
                system(eventbuffer);
#else
                syslog(level, "%s->%s:%d %s", file, func, line, syslogbuffer);
#endif
                FreeNoLog(syslogbuffer);
                FreeNoLog(eventbuffer);
                break;
            default:
                fprintf(out, "%s:%jd:%s->%s:%d ", prioritynames[level].c_name, (intmax_t)time(NULL), file, func, line);
                va_start(args, format);
                vfprintf(out, format, args);
                va_end(args);
                fprintf(out, "\n");
                break;
        }
        fflush(out);
    }
} /* _n_log( ... ) */

/*!\fn open_safe_logging( TS_LOG **log , char *pathname , char *opt )
 *\brief Open a thread-safe logging file
 *\param log A TS_LOG handler
 *\param pathname The file path (if any) and name
 *\param opt Options for opening (please never forget to use "w")
 *\return TRUE on success , FALSE on error , -1000 if already open
 */
int open_safe_logging(TS_LOG** log, char* pathname, char* opt) {
    if ((*log)) {
        if ((*log)->file)
            return -1000; /* already open */

        return FALSE;
    }

    if (!pathname)
        return FALSE; /* no path/filename */

    Malloc((*log), TS_LOG, 1);

    if (!(*log))
        return FALSE;

    pthread_mutex_init(&(*log)->LOG_MUTEX, NULL);

    (*log)->file = fopen(pathname, opt);

    if (!(*log)->file)
        return FALSE;

    return TRUE;

} /* open_safe_logging(...) */

/*!\fn write_safe_log( TS_LOG *log , char *pat , ... )
 *\brief write to a thread-safe logging file
 *\param log A TS_LOG handler
 *\param pat Pattern for writting (i.e "%d %d %s")
 *\return TRUE on success, FALSE on error
 */
int write_safe_log(TS_LOG* log, char* pat, ...) {
    /* argument list */
    va_list arg;
    char str[2048] = "";

    if (!log)
        return FALSE;

    va_start(arg, pat);

    vsnprintf(str, sizeof(str), pat, arg);

    va_end(arg);

    pthread_mutex_lock(&log->LOG_MUTEX);
    fprintf(log->file, "%s", str);
    fflush(log->file);
    pthread_mutex_unlock(&log->LOG_MUTEX);

    return TRUE;

} /* write_safe_log( ... ) */

/*!\fn close_safe_logging( TS_LOG *log )
 *\brief close a thread-safe logging file
 *\param log A TS_LOG handler
 *\return TRUE on success, FALSE on error
 */
int close_safe_logging(TS_LOG* log) {
    if (!log)
        return FALSE;

    pthread_mutex_lock(&log->LOG_MUTEX);
    fflush(log->file);
    pthread_mutex_unlock(&log->LOG_MUTEX);

    pthread_mutex_destroy(&log->LOG_MUTEX);

    fclose(log->file);

    return TRUE;

} /* close_safe_logging( ...) */
