/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Function related to logging service. It provides functions to print out log
 * message on stdout verbosity level.
 */   

/**
@page amdlibLog ICARE logging facility 

@section Description
Every program needs to display diagnostics: error messages, debugging output,
and so on. Traditionally, this is done using a number of printf() calls in
application in order to help trace execution paths or display helpful runtime
information. ICARE logging facility provides user with ways to do these things
while at the same time giving us great control over how much of the information
is printed and where it is directed (for the time being messages in only printed
out on stdout, but in future it will be possble to redirect them into a log
file).

It is important to have a convenient way to create debug statements. In this
modern age of graphical source-level debuggers, it might seem strange to pepper
your application with the equivalent of a bunch of print statements. However,
diagnostic statements are useful both during development and long after an
application is considered to be bug free. 

@li messages can be logged while the program is running and a debugger
isn't available or practical, such as with a server.
@li messages can recorded during testing for regression analysis.

The ICARE mechanisms allow, at runtime, to enable and disable these statements,
and to control verbosity level.  Thus, you don't have to pay for the overhead in
either CPU cycles or disk space under normal conditions. But if a problem
arises, you can easily cause copious amounts of debugging information to be
recorded to assist you in finding and fixing it. It is an unfortunate fact that
many bugs will never appear until the program is in the hands of the end user.

The messages are printed out on stdout according to the message level (see
amdlibLOG_LEVEL) and the current logging level, formatted as follows: 
@code
[<date>] -  [<file>:<line>] - <message>
@endcode

The <dfn>\<date\></dfn> and <dfn>\<file\>:\<line\></dfn> informations is optional, and can be controlled using amdlibLogSet() function, which also control log level.

amdlibLogError(), amdlibLogWarning(), amdlibLogInfo(), amdlibLogTest() and amdlibLogTrace()
convenient macros are provided to display diagnostic output from your code.

The amdlibLogTrace is reserved to trace execution paths and should be added at the
beginning og all functions ot methods, as shown in the following example, where
<dfn>mymod</dfn> is the name of the module where this function is defined :
@code
amdlibCOMPL_STAT mymodFunction(...)
{ 
    amdlibLogTrace("mymodFunction()");
    ... 
}
@endcode

*/

/* 
 * System Headers
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>

/* 
 * Local Headers
 */
#include "amdlib.h"
#include "amdlibProtected.h"

/**
 * Configuration for logging 
 */
static amdlibLOG_CONFIG amdlibLogConfig =
{
    amdlibTRUE,            /** Log disabled */
    amdlibLOG_INFO,   
    amdlibFALSE,           /** Print date/time */
    amdlibFALSE            /** Print file/line */
};

/**
 * Configure logging service.
 *
 * It enables/disables message logging, set verbose level and switches on/off
 * the display of date and file/line informations in logged messages.
 *
 * @param enabled flag to enable or disable message logging.
 * @param level lowest level of messages to be printed out.
 * @param printDateFileLine flag to whether print or not date and file/line
 * informations.
 */
void amdlibLogSet(amdlibBOOLEAN enabled, amdlibLOG_LEVEL level,
                  amdlibBOOLEAN printDateFileLine)
{
    amdlibLogConfig.enabled       = enabled;
    amdlibLogConfig.level         = level;
    amdlibLogConfig.printFileLine = printDateFileLine;
    amdlibLogConfig.printDate     = printDateFileLine;
}

/**
 * Return whether the logging service is enabled or not.
 *
 * @return amdlibTRUE if logging service is enabled, and amdlibFALSE
 * otherwise.
 */
amdlibBOOLEAN amdlibLogIsEnabled(void)
{
    return (amdlibLogConfig.enabled);
}

/**
 * Return the current log level.
 *
 * @return  current log level.
 */
amdlibLOG_LEVEL amdlibLogGetLevel(void)
{
    return (amdlibLogConfig.level);
}

/**
 * Prints out message on stdout.
 *
 * It prints out log message, according to the current logging setting (see
 * amdlibLogSet).
 *
 * @param level level of the given message
 * @param fileLine file name and line number from where function has been called
 * @param format printf-like string format to be expanded.
 *
 * @note
 * This function should never be called directly. amdlibLogError(),
 * amdlibLogWarning(), amdlibLogInfo(), amdlibLogTest() and amdlibLogTrace()
 * should be used instead of.
 */
void amdlibLogPrint(amdlibLOG_LEVEL level, amdlibBOOLEAN isDetail, const char *fileLine, 
                 const char *format, ...)
{
    va_list  argPtr;

    /* If log is enabled */
    if ((amdlibLogConfig.enabled == amdlibTRUE) && 
        (level <= amdlibLogConfig.level))
    {

        /* Print out date */
        if (amdlibLogConfig.printDate == amdlibTRUE)
        {
            char infoTime[32];
            /* Get UNIX-style time and display as number and string. */
            amdlibGetTimeStamp(infoTime, 6);

            /* Print it */
            printf("%s - ", infoTime);
        }

        /* Print out file/line */
        if ((fileLine != NULL ) &&
            (amdlibLogConfig.printFileLine == amdlibTRUE))
        {
            /* Print it */
            fprintf(stdout, "%s - ", fileLine);
        }

        /* It is not additional infos */
        if (isDetail == amdlibFALSE)
        {
            /* Added prefix */
            switch (level)
            {
                case amdlibLOG_ERROR:
                    fprintf(stdout, " ERROR : ");
                    break;
                case amdlibLOG_WARNING:
                    fprintf(stdout, " WARNING : ");
                    break;
                default:
                    fprintf(stdout, " ");
                    break;
            }
        }
        else
        {
            /* Indent */
            fprintf(stdout, "    ");
        }
        
        /* Print message */
        va_start(argPtr, format);
        vfprintf(stdout, format, argPtr);
        fprintf(stdout, "\n");
        fflush(stdout);
        va_end(argPtr);
    }
}

/**
 * Returns the current date and time.
 *
 * It returns the current date and time, to be used as time stamp in logs or
 * errors. It generates the string corresponding to the current date,
 * expressed in Coordinated Universal Time (UTC), using the following format
 * YYYY-MM-DDThh:mm:ss[.ssssss], as shown in the following example :
 *    
 * @code
 *     2004-06-16T16:16:48.02941
 * @endcode
 *
 * @param timeStamp buffer where date will be written 
 * @param precision number of digits for milli-seconds.
 */
void amdlibGetTimeStamp(char timeStamp[32], unsigned int precision)
{
    struct timeval time;
    struct tm      *timeNow;
    char           tmpBuf[32];

    /* Get local time */
    gettimeofday(&time, NULL);

    /* Format the date */
    timeNow = gmtime(&time.tv_sec);
    strftime(timeStamp, 32, "%Y-%m-%dT%H:%M:%S", timeNow);

    /* Add milli-second and micro-second */
    if (precision != 0)
    {
        char format[8];
        sprintf(format, "%%.%df", precision);

        sprintf(tmpBuf, format, time.tv_usec/1e6);
        strcat(timeStamp, (tmpBuf + 1)); /* Remove first character; i.e 0 */
    }
}

/*___oOo___*/
