/*
 *       File name:  common/log-facility.cc
 *         Project:  Aghermann
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2009-06-28 (in aghermann since 2013-09-28)
 *
 *         Purpose:  Simple log facility
 *
 *         License:  GPL
 */

#include <sys/time.h>
#include <cstdarg>
#include <cstring>
#include <cmath>
#include <stdexcept>
#include <gsl/gsl_math.h>
#include "lang.hh"
#include "string.hh"

#if HAVE_CONFIG_H && !defined(VERSION)
#  include "config.h"
#endif

#include "log-facility.hh"

using namespace std;
using namespace agh::log;


const char*
agh::log::
level_s( const TLevel l)
{
        switch (l) {
        case TLevel::debug  : return "DEBUG";
        case TLevel::info   : return "INFO";
        case TLevel::warning: return "WARN";
        case TLevel::error  : return "ERROR";
        case TLevel::self   : return "info";
        default: return "eh?";
        }
}


CLogFacility::
CLogFacility( const string& log_fname,
              TLevel level_, bool compact_repeated_messages_,
              unsigned short sec_dec_places_)
      : level (level_),
        compact_repeated_messages (compact_repeated_messages_),
        sec_dec_places (sec_dec_places_),
        _repeating_last (0)
{
        if ( sec_dec_places > 8 )
                throw invalid_argument ("Seconds decimal places too big (>8)");
        if ( log_fname == "-" )
                _f = stdout;
        else if ( !(_f = fopen( log_fname.c_str(), "w")) )
                throw runtime_error ("Failed to open log file for writing");
        msg( level, "log-facility", "init");
}

CLogFacility::
~CLogFacility()
{
        msg( level, "log-facility", "closing");  // will cause any withheld "above message repeated" message to be printed
        if ( _f != stdout )
                fclose(_f);
}



void
CLogFacility::
msg( TLevel this_level, const char *issuer, const char* fmt, ...)
{
        if ( level < this_level && this_level != TLevel::self )
                return;

        va_list ap;
        va_start (ap, fmt);
        vmsg( this_level, issuer, fmt, ap);
        va_end (ap);
}


void
CLogFacility::
vmsg( TLevel this_level, const char *issuer, const char* fmt, va_list ap)
{
        if ( level < this_level && this_level != TLevel::self )
                return;

        char timestampbuf[32 + 8 + 3];
        {
                struct timeval tp;
                gettimeofday( &tp, nullptr);
                _last_tp = tp;
                size_t n = strftime( timestampbuf, 31, "%F %T", localtime( &tp.tv_sec));
                snprintf( timestampbuf+n, sec_dec_places+2, ".%0*u", sec_dec_places,
                          (unsigned)round( tp.tv_usec / gsl_pow_int( 10., 6-sec_dec_places-1)));
        }

        string the_line = agh::str::svasprintf( fmt, ap);

        // treat multiline message as a series of
        if ( compact_repeated_messages && the_line == _last_line )
                ++_repeating_last;  // defer printing
        else {
                if ( _repeating_last ) {
                        fprintf( _f, "%s (above message repeated %zu time(s))\n", timestampbuf, _repeating_last);
                        _repeating_last = 0;
                }

                // puncturing this_line will incidentally cause any multiline messages to never match _last_line,
                // -- but it's just ok, because it avoids confusion as to what exactly "above message" can refer to
                char *line = strtok( &the_line[0], "\n");
                string to_print;
                int mline = 0;
                do {
                        if ( mline > 0 )
                                fprintf( _f,
                                         "%s %s %+*d %s\n",
                                         timestampbuf, level_s(this_level),
                                         (int)strlen(issuer), mline,
                                         line);
                        else
                                fprintf( _f,
                                         "%s %s %s %s\n",
                                         timestampbuf, level_s(this_level),
                                         issuer,
                                         line);
                        ++mline;
                } while ( (line = strtok( nullptr, "\n")) );
        }

        _last_line = the_line;
}


void
agh::log::SLoggingClient::
log( agh::log::TLevel level, const char* issuer, const char* fmt, ...)
{
        if (_log_facility) {
                va_list ap;
                va_start (ap, fmt);
                _log_facility->vmsg( level, issuer, fmt, ap);
                va_end (ap);
        }
}


// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
