/** @file  psLogMsg.c
 *  @brief Procedures for logging messages.
 *  \ingroup LogTrace
 *
 *  This file will hold the prototypes for defining procedure which set
 *  message log levels, messahe log formats, message log destinations, and
 *  for generating the messages themselves.
 *  @ingroup LogTrace
 *
 *  @author Robert Lupton, Princeton University
 *  @author George Gusciora, MHPCC
 *
 *  @version $Revision: 1.35 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2004-11-04 01:05:00 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 */

/*****************************************************************************
NOTES: currently, the prototype code has the following global variables:
    static psS32 p_psGlobalLogDest;
    static psS32 p_psGlobalLogLevel;
    static psS32 p_psLogTime;
    static psS32 p_psLogHost;
    static psS32 p_psLogLevel;
    static psS32 p_psLogName;
    static psS32 p_psLogMsg;
 *****************************************************************************/
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>

#include "psLogMsg.h"
#include "psError.h"
#include "psTrace.h"

#include "psSysUtilsErrors.h"

#define MIN_LOG_LEVEL 0
#define MAX_LOG_LEVEL 9

#define MAX_LOG_LINE_LENGTH 128


static FILE *logDest = (FILE *) 1;      // flag to initialize to stderr before using.
static psS32 globalLogLevel = PS_LOG_INFO;        // log all messages at this or above
static psBool logTime = true;     // Flag to include time info
static psBool logHost = true;     // Flag to include host info
static psBool logLevel = true;    // Flag to include level info
static psBool logName = true;     // Flag to include name info
static psBool logMsg = true;      // Flag to include message info

/*****************************************************************************
psLogSetLevel(): Set the current log level and return old level.
Input:
 level (psS32): the new log level.
Output:
 none
Return:
 The old log level.
 *****************************************************************************/
psS32 psLogSetLevel(psS32 level)
{
    // Save old global log level for changing it.
    psS32 oldLevel = globalLogLevel;

    if ((level < MIN_LOG_LEVEL) || (level > MAX_LOG_LEVEL)) {
        psLogMsg("logmsg", PS_LOG_WARN, "Attempt to set invalid logMsg level: %d", level);
        level = (level < MIN_LOG_LEVEL) ? MIN_LOG_LEVEL : MAX_LOG_LEVEL;
    }
    // Set new global log level
    globalLogLevel = level;

    // Return old global log level
    return oldLevel;
}

/*****************************************************************************
psLogSetDestination(): sets the destination where log messages will be
sent to.
 
Input:
 dest (psS32): the new log destination
Output:
 None.
Return:
 An integer specifying the old log destination.
 *****************************************************************************/
psBool psLogSetDestination(const char *dest)
{
    char protocol[5];
    char location[257];

    // if logDest has not been initialized, do so before using it
    if (logDest == (FILE *) 1) {
        logDest = stderr;
    }

    if (dest == NULL || strcmp(dest, "none") == 0) {
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = NULL;
        return true;
    }

    if (sscanf(dest, "%4s:%256s", protocol, location) < 2) {
        psError(PS_ERR_LOCATION_INVALID, true,
                PS_ERRORTEXT_psLogMsg_DESTINATION_MALFORMED,
                dest);
        return false;
    }

    if (strcmp(protocol, "dest") == 0) {
        if (strcmp(location, "stderr") == 0) {
            if (logDest != NULL && logDest != stderr && logDest != stdout) {
                fclose(logDest);
            }
            logDest = stderr;
            return true;
        }
        if (strcmp(location, "stdout") == 0) {
            if (logDest != NULL && logDest != stderr && logDest != stdout) {
                fclose(logDest);
            }
            logDest = stdout;
            return true;
        }
        psError(PS_ERR_LOCATION_INVALID, true,
                PS_ERRORTEXT_psLogMsg_DEST_LOCATION_INVALID,
                location);
        return 1;
    } else if (strcmp(protocol, "file") == 0) {
        FILE *file = fopen(location, "w");

        if (file == NULL) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psLogMsg_OPEN_FILE_FAILED,
                    location);
            return false;
        }
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = file;
        return true;
    }

    psError(PS_ERR_LOCATION_INVALID, true,
            PS_ERRORTEXT_psLogMsg_UNSUPPORTED_PROTOCOL,
            protocol);
    return false;
}

/*****************************************************************************
psLogSetFormat(): Set the format of psLogMsg output.  More precisely,
    provide a string consisting of the letters {H (host), L (level), M
    (message), N (name), T (time)}.  The default is "HLMNT".  This string
    determines whether or not they associated type of information will be
    included in message logs.  It does not determine the order in which that
    information will appear (that order is fixed).
 
Input:
    fmt: a string specifying the format.
Output:
    none.
Return:
    NULL.
 *****************************************************************************/
void psLogSetFormat(const char *fmt)
{
    // assume nothing desired unless specified
    logHost = false;
    logLevel = false;
    logMsg = false;
    logName = false;
    logTime = false;

    // if fmt is NULL, no logging is desired.
    if (fmt == NULL) {
        return;
    }

    if (strlen(fmt) == 0) {
        fmt = "THLNM";
    }
    // Step through each character in the format string.  For each letter
    // in that string, set/unset the appropriate logging.

    for (const char *ptr = fmt; *ptr != '\0'; ptr++) {
        switch (*ptr) {
        case 'H':
        case 'h':
            logHost = true;
            break;
        case 'L':
        case 'l':
            logLevel = true;
            break;
        case 'M':
        case 'm':
            logMsg = true;
            break;
        case 'N':
        case 'n':
            logName = true;
            break;
        case 'T':
        case 't':
            logTime = true;
            break;
        default:
            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                    PS_ERRORTEXT_psLogMsg_UNKNOWN_KEY, *ptr);
            break;
        }
    }

    if (!logMsg) {
        psTrace("utils.logMsg", 1, "You must at least log error messages (You chose \"%s\")", fmt);
    }
}

#if !defined(HOST_NAME_MAX)                // should be in limits.h
#define HOST_NAME_MAX 256
#endif

/*****************************************************************************
    psVLogMsg(): This routine sends the message, which is a printf style
 string specified in the "..." argument, to the current message log
 destination with the severity specified by the "level" argument.
    Input:
 name
 level
 fmt
 ap
    Output:
 none
    Return:
 NULL.
 *****************************************************************************/
void psLogMsgV(const char *name, psS32 level, const char *fmt, va_list ap)
{
    static psS32 first = 1;       // Flag for calling gethostname()
    static char hostname[HOST_NAME_MAX + 1];

    // Buffer for hostname.
    char clevel = 0;            // letter-name for level
    char head[MAX_LOG_LINE_LENGTH + 2]; // the added two are for the ending | and \0
    char *head_ptr = head;      // where we've got to in head
    psS32 maxLength = MAX_LOG_LINE_LENGTH;
    time_t clock = time(NULL);  // The current time.
    struct tm *utc = gmtime(&clock);    // The current gm time.

    // if logDest has not been initialized, do so before using it
    if (logDest == (FILE *) 1) {
        logDest = stderr;
    }
    // If logging is off, or if the level is too high, return immediately.
    if ((level > globalLogLevel) || (logDest == NULL)) {
        return;
    }
    // If I have not been here yet, determine my hostname and save it.
    if (first) {
        first = 0;
        gethostname(hostname, HOST_NAME_MAX);
    }

    switch (level) {
    case PS_LOG_ABORT:
        clevel = 'A';
        break;

    case PS_LOG_ERROR:
        clevel = 'E';
        break;

    case PS_LOG_WARN:
        clevel = 'W';
        break;

    case PS_LOG_INFO:
        clevel = 'I';
        break;

    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
        clevel = level + '0';
        break;

    default:
        psTrace("utils.logMsg", 2, "Invalid logMsg level: %d (%s)\n", level, fmt);
        level = (level < 0) ? 0 : 9;
        clevel = level + '0';
        break;
    }

    // Create the various log fields...
    if (logTime) {
        maxLength -= snprintf(head_ptr, maxLength, "%4d:%02d:%02d %02d:%02d:%02dZ",
                              utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
                              utc->tm_hour, utc->tm_min, utc->tm_sec) - 1;
        head_ptr += strlen(head_ptr);
    }
    // Hostname should be 20 characters.
    if (logHost) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%-20s", hostname);
        head_ptr += strlen(head_ptr);
    }
    if (logLevel) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%c", clevel);
        head_ptr += strlen(head_ptr);
    }
    if (logName) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%s", name);

        head_ptr += strlen(head_ptr);
    }

    if (head_ptr > head) {
        *head_ptr++ = '\n';
    } else if (!logMsg) {                  // no output desired
        return;
    }
    *head_ptr = '\0';

    fputs(head, logDest);
    if (logMsg) {
        char msg[1024];
        char* msgPtr;
        vsnprintf(msg,1024, fmt, ap);  // create message

        // detect multiple lines in message and indent each line by 4 spaces.
        char* line = strtok_r(msg,"\n",&msgPtr);
        while (line != NULL) {
            fprintf(logDest,"    %s\n",line);
            line = strtok_r(NULL,"\n",&msgPtr);
        }
    } else {
        fputc('\n', logDest);
    }
}

/*****************************************************************************
    psLogMsg(): This routine sends the message, which is a printf style
 string specified in the "..." argument, to the current message log
 destination with the severity specified by the "level" argument.
    Input:
 name: Indicates the source of this log message.
 level: The severity of this log message.
 fmt: The printf-stype formatted string, followed by the arguments
  to that string.
 ... The arguments to the above printf-style string.
    Output:
 none
    Return:
 NULL
 *****************************************************************************/
void psLogMsg(const char *name, psS32 level, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    psLogMsgV(name, level, fmt, ap);
    va_end(ap);
}
void psWarning(const char* name, const char* fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    psLogMsgV(name, PS_LOG_WARN, fmt, ap);
    va_end(ap);
}
