/*
 *      logger.c
 *
 *      Logger for motion
 *
 *      Copyright 2005, William M. Brack
 *      Copyright 2008 by Angel Carpintero  (motiondevelop@gmail.com)
 *      This software is distributed under the GNU Public License Version 2
 *      See also the file 'COPYING'.
 *
 */

#include "logger.h"   /* already includes motion.h */
#include <stdarg.h>

static int log_mode = LOGMODE_SYSLOG;
static FILE *logfile;
static unsigned int log_level = LEVEL_DEFAULT;
static unsigned int log_type = TYPE_DEFAULT;

static const char *log_type_str[] = {NULL, "COR", "STR", "ENC", "NET", "DBL", "EVT", "TRK", "VID", "ALL"};
static const char *log_level_str[] = {"EMG", "ALR", "CRT", "ERR", "WRN", "NTC", "INF", "DBG", "ALL", NULL};


/**
 * get_log_type
 *
 *
 * Returns: index of log type or 0 if not valid type.
 */
int get_log_type(const char *type)
{
    unsigned int i, ret = 0;
    unsigned int maxtype = sizeof(log_type_str)/sizeof(const char *);

    for (i = 1;i < maxtype; i++) {
        if (!strncasecmp(type, log_type_str[i], 3)) {
            ret = i;
            break;
        }
    }

    return ret;
}

/**
 * get_log_type_str
 *      Gets string value for type log level.
 *
 * Returns: name of type log level.
 */
const char* get_log_type_str(unsigned int type)
{
    return log_type_str[type];
}

/**
 * set_log_type
 *      Sets log type level.
 *
 * Returns: nothing.
 */
void set_log_type(unsigned int type)
{
    log_type = type;
    //printf("set log type %d\n", type);
}

/**
 * get_log_level_str
 *      Gets string value for log level.
 *
 * Returns: name of log level.
 */
const char* get_log_level_str(unsigned int level)
{
    return log_level_str[level];
}

/**
 * set_log_level
 *      Sets log level.
 *
 * Returns nothing.
 */
void set_log_level(unsigned int level)
{
    log_level = level;
    //printf("set log level %d\n", level);
}

/**
 * set_log_mode
 *      Sets mode of logging, could be using syslog or files.
 *
 * Returns: nothing.
 */
void set_log_mode(int mode)
{
    int prev_mode = log_mode;

    log_mode = mode;
    //printf("set log mode %d\n", mode);

    if (mode == LOGMODE_SYSLOG && prev_mode != LOGMODE_SYSLOG)
        openlog("motion", LOG_PID, LOG_USER);

    if (mode != LOGMODE_SYSLOG && prev_mode == LOGMODE_SYSLOG)
        closelog();
}

/**
 * set_logfile
 *      Sets logfile to be used instead of syslog.
 *
 * Returns: pointer to log file.
 */
FILE * set_logfile(const char *logfile_name)
{
    /* Setup temporary to let log if myfopen fails */
    set_log_mode(LOGMODE_SYSLOG);

    logfile = myfopen(logfile_name, "a");

    /* If logfile was opened correctly */
    if (logfile)
        set_log_mode(LOGMODE_FILE);

    return logfile;
}

/**
 * str_time
 *
 * Return: string with human readable time
 */
static char *str_time(void)
{
    static char buffer[16];
    time_t now = 0;

    now = time(0);
    strftime(buffer, 16, "%b %d %H:%M:%S", localtime(&now));
    return buffer;
}

/**
 * MOTION_LOG
 *
 *    This routine is used for printing all informational, debug or error
 *    messages produced by any of the other motion functions.  It always
 *    produces a message of the form "[n] {message}", and (if the param
 *    'errno_flag' is set) follows the message with the associated error
 *    message from the library.
 *
 * Parameters:
 *
 *     level           logging level for the 'syslog' function
 *
 *     type            logging type.
 *
 *     errno_flag      if set, the log message should be followed by the
 *                     error message.
 *     fmt             the format string for producing the message
 *     ap              variable-length argument list
 *
 * Returns:
 *                     Nothing
 */
void motion_log(int level, unsigned int type, int errno_flag,int fncname, const char *fmt, ...){
    int errno_save, n;
    char buf[1024];
    char usrfmt[1024];

/* GNU-specific strerror_r() */
#if (!defined(XSI_STRERROR_R))
    char msg_buf[100];
#endif
    va_list ap;
    int threadnr;

    static int flood_cnt = 0;
    static char flood_msg[1024];
    char flood_repeats[1024];


    /* Exit if level is greater than log_level */
    if ((unsigned int)level > log_level)
        return;

    /* Exit if type is not equal to log_type and not TYPE_ALL */
    if ((log_type != TYPE_ALL) && (type != log_type))
        return;

    //printf("log_type %d, type %d level %d\n", log_type, type, level);

    threadnr = (unsigned long)pthread_getspecific(tls_key_threadnr);

    /*
     * First we save the current 'error' value.  This is required because
     * the subsequent calls to vsnprintf could conceivably change it!
     */
    errno_save = errno;

    char threadname[32];
    util_threadname_get(threadname);

    /*
     * Prefix the message with the thread number and name,
     * log level string, log type string, and time.
     * e.g. [1:enc] [ERR] [ALL] [Apr 03 00:08:44] blah
     */
    if (log_mode == LOGMODE_FILE) {
        n = snprintf(buf, sizeof(buf), "[%d:%s] [%s] [%s] [%s] ",
                     threadnr, threadname, get_log_level_str(level), get_log_type_str(type),
                     str_time());
    } else {
    /*
     * Prefix the message with the thread number and name,
     * log level string and log type string.
     * e.g. [1:trk] [DBG] [ALL] blah
     */
        n = snprintf(buf, sizeof(buf), "[%d:%s] [%s] [%s] ",
                     threadnr, threadname, get_log_level_str(level), get_log_type_str(type));
    }

    /* Prepend the format specifier for the function name */
    if (fncname){
        snprintf(usrfmt, sizeof (usrfmt),"%s: %s", "%s", fmt);
    } else {
        snprintf(usrfmt, sizeof (usrfmt),"%s",fmt);
    }

    /* Next add the user's message. */
    va_start(ap, fmt);
    n += vsnprintf(buf + n, sizeof(buf) - n, usrfmt, ap);
    va_end(ap);
    buf[1023] = '\0';

    /* If errno_flag is set, add on the library error message. */
    if (errno_flag) {
      size_t buf_len = strlen(buf);

      // just knock off 10 characters if we're that close...
      if (buf_len + 10 > 1024) {
          buf[1024 - 10] = '\0';
          buf_len = 1024 - 10;
      }

      strncat(buf, ": ", 1024 - buf_len);
      n += 2;
        /*
         * This is bad - apparently gcc/libc wants to use the non-standard GNU
         * version of strerror_r, which doesn't actually put the message into
         * my buffer :-(.  I have put in a 'hack' to get around this.
         */
#if defined(XSI_STRERROR_R)
        /* XSI-compliant strerror_r() */
        strerror_r(errno_save, buf + n, sizeof(buf) - n);    /* 2 for the ': ' */
#else
        /* GNU-specific strerror_r() */
        strncat(buf, strerror_r(errno_save, msg_buf, sizeof(msg_buf)), 1024 - strlen(buf));
#endif
    }

    if ((!strcmp(buf,flood_msg)) && (flood_cnt <= 5000)){
        flood_cnt++;
    } else {
        if (flood_cnt > 1){
            snprintf(flood_repeats,1024,"[%d:%s] [%s] [%s] Above message repeats %d times",
                     threadnr, threadname, get_log_level_str(level)
                     , get_log_type_str(type), flood_cnt-1);
            switch (log_mode) {
            case LOGMODE_FILE:
                strncat(flood_repeats, "\n", 1024 - strlen(flood_repeats));
                fputs(flood_repeats, logfile);
                fflush(logfile);
                break;

            case LOGMODE_SYSLOG:
                syslog(level, "%s", flood_repeats);
                strncat(flood_repeats, "\n", 1024 - strlen(flood_repeats));
                fputs(flood_repeats, stderr);
                fflush(stderr);
                break;
            }
        }
        flood_cnt = 1;
        snprintf(flood_msg,1024,"%s",buf);
        switch (log_mode) {
        case LOGMODE_FILE:
            strncat(buf, "\n", 1024 - strlen(buf));
            fputs(buf, logfile);
            fflush(logfile);
            break;

        case LOGMODE_SYSLOG:
            syslog(level, "%s", buf);
            strncat(buf, "\n", 1024 - strlen(buf));
            fputs(buf, stderr);
            fflush(stderr);
            break;
        }
    }

}

