/*
   <p>Utility procedures to minimise stderr output.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the Lesser GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include "environ.h"

#ifdef OS9
#include <varargs.h>
#include <strings.h>
#endif

#ifdef MSDOS
#include <stdlib.h>
#include <string.h>
#endif

#ifdef LINUX
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#endif

/*
    Define the buffer structure
*/
typedef
   struct
   {  /* one buffer entry */
      time_t timestamp;
      char   msg[MAXLINE+1];
   } logent;
               
logent   **_msgbuf;               /* the circular buffer */
int      _msgbufsize = 21;        /* the buffer size */
int      _msgbufptr;              /* buffer update pointer */
time_t   _last_dump;              /* time of last dump */
int      _first_msg;              /* first message to dump */
int      _first_time  = 1;        /* initialisation control flag */
int      _show_time = FALSE;      /* timestamp output control */ 
int      _runtype = DIRECT_LOG;   /* selects direct or indirect output */
int      _fileno = STDERR_LOG;    /* selects stdout or stderr */

#define  INCPTR(p) (p+1)%_msgbufsize

/*
   Select the operational mode, circular buffer size, and output file
   for logging.
    
   <u>r</u> is DIRECT_LOG if all tracing output is to be sent straight
   to the file and INDIRECT_LOG if it is to be written to the circular
   buffer for later output.

   <u>f</u> is the output file. It must be STDOUT_LOG or STDERR_LOG.

   <u>s</u> is the circular buffer size. Its default size is 20 log
   messages.

   If this function is not called, default run conditions will be used.
   This is equivalent to the call <i>logsetup(DIRECT_LOG, STDERR_LOG, 20);</i>

   This is one of a set of functions that provide a means of reducing
   tracing output being used to diagnose problems in an operational
   program. Tracing information is written to a circular buffer,
   overwriting old information, until an error condition is discovered
   At this point the contents of the circular buffer is dumped to show
   the events leading up to the error. This would typically be followed
   a diagnostic message to describe the error. After this the program can
   continue to run, refilling the circular buffer, or it can terminate.

   <b>See also:</b> <i>logdump(), logmsg(), logtime()</i>   
*/
void logsetup(int r, int f, int s)
{
    _runtype = r;
    _fileno  = f;
    _msgbufsize = s + 1;
}

/*
   Determines whether <i>logdump()</i> preceeds each message with
   a timestamp showing when it was written to the circular buffer
   by <i>logmsg()</i>.
    
   <u>t</u> is TRUE if a timestamp is to preceed each message and
   FALSE no timestamp is shown.

   If this function is not called, default run conditions will be used.
   This is equivalent to the call <i>logtime(FALSE);</i>

   This is one of a set of functions that provide a means of reducing
   tracing output being used to diagnose problems in an operational
   program. Tracing information is written to a circular buffer,
   overwriting old information, until an error condition is discovered
   At this point the contents of the circular buffer is dumped to show
   the events leading up to the error. This would typically be followed
   a diagnostic message to describe the error. After this the program can
   continue to run, refilling the circular buffer, or it can terminate.

   <b>See also:</b> <i>logdump(), logmsg(), logsetup()</i>   
*/
void logtime(int t)
{
    _show_time = t;
}
   
/*
   If the operational mode is DIRECT_LOG and the output file is
   STDERR_LOG, this call is equivalent to
   <i>fprintf(stderr, format, ....);</i> except that, because
   <i>logmsg()</i> always adds a newline to the message, the message
   format defined by <u>fmt</u> should not end with one unless the
   message is to be followed by a blank line.
   
   If the operational mode is INDIRECT_LOG, each message is timestamped
   and written to a circular buffer from where it is printed by
   <i>logdump()</i>.

   This is one of a set of functions that provide a means of reducing
   tracing output being used to diagnose problems in an operational
   program. Tracing information is written to a circular buffer,
   overwriting old information, until an error condition is discovered
   At this point the contents of the circular buffer is dumped to show
   the events leading up to the error. This would typically be followed
   a diagnostic message to describe the error. After this the program can
   continue to run, refilling the circular buffer, or it can terminate.

   <b>See also:</b> <i>logdump(), logsetup(), logtime()</i>   
*/
#ifdef OS9
void logmsg(fmt, va_alist)
char    *fmt;
va_dcl
#else
void logmsg(char* fmt, ...)
#endif
{
    va_list   apx;
    char      linebuff[MAXLINE];
    time_t    now;
    struct tm *tblock;
    int       i;
    int       lth;

    /*
        Set up the message to be logged.
    */
    va_start(apx, fmt);
    lth = vsnprintf(linebuff, MAXLINE, fmt, apx);
    if (lth == MAXLINE)
        linebuff[lth-1] = 0;
    va_end(apx);

    /*
        This code handles direct output.
    */
    if (_runtype == DIRECT_LOG)
    {
        if (_fileno == STDOUT_LOG)
            fprintf(stdout, "%s\n", linebuff);
        else
            fprintf(stderr, "%s\n", linebuff);
            
        return;
    }

    /*
        Indirect output.

        Initialise the buffer the first time through
    */
    if (_first_time)
    {
        _msgbuf = get_space(sizeof(logent*) * _msgbufsize);
        for (i=0; i<_msgbufsize; i++)
        {
            _msgbuf[i] = get_space(sizeof(logent));
            _msgbuf[i]->timestamp = 0;
        }
        
        _msgbufptr = 0;
        _last_dump = 0;
        _first_time = 0;
    }

    /*
        Get the time and add the message to the buffer.
    */
    _msgbuf[_msgbufptr]->timestamp = time(NULL);
    strncpy(_msgbuf[_msgbufptr]->msg, linebuff, MAXLINE);
    _msgbuf[_msgbufptr]->msg[MAXLINE] = 0;
    
    /*
        Adjust the pointers. The storage pointer is incremented
        modulo the buffer size. If this catches up with the dump
        pointer this is carried along with it.
    */
    _msgbufptr = INCPTR(_msgbufptr);
    if (_msgbufptr == _first_msg)
        _first_msg = INCPTR(_msgbufptr);
}

/*
   Print the circular buffer filled by <i>logmsg()</i>.

   If the operational mode is DIRECT_LOG this function does nothing.
   If it is INDIRECT_LOG all unprinted messages in the circular buffer
   are printed and the circular buffer is cleared. The output format is
   controlled by <i>logtime()</i>. If <i>logtime(TRUE)</i> was called
   each message will be preceeded by the time when <i>logmsg()</i>
   wrote it into the circular buffer. If <i>logtime()</i> was never
   called or if it was last called with the FALSE argument, the timestamp
   is not shown. 

   This is one of a set of functions that provide a means of reducing
   tracing output being used to diagnose problems in an operational
   program. Tracing information is written to a circular buffer,
   overwriting old information, until an error condition is discovered
   At this point the contents of the circular buffer is dumped to show the
   events leading up to the error. This would typically be followed a
   diagnostic message to describe the error. After this the program can
   continue to run, refilling the circular buffer, or it can terminate.

   <b>See also:</b> <i>logmsg(), logsetup(), logtime()</i>   
*/
void logdump(void)
{
    struct tm *tblock;
    char      tbuff[10];
    int       i;
    int       last;

    /*
        Do nothing if nothing has been logged or the
        direct output flag is on
    */
    if (_runtype == DIRECT_LOG || _first_time)
        return;

    /*
        Scan the buffer, outputting all unprinted messages
        in order of arrival. Keep track of the last message
        output.
    */
    i = _first_msg;
    do {
        if (_msgbuf[i]->timestamp > 0
            && _msgbuf[i]->timestamp >= _last_dump) {
            if (_show_time)
            {
               tblock = localtime(&_msgbuf[i]->timestamp);
               snprintf(tbuff, 10, "%02d:%02d:%02d ",
                        tblock->tm_hour,
                        tblock->tm_min,
                        tblock->tm_sec);
            }
            else
               strcpy(tbuff, "");
                        
            if (_fileno == STDOUT_LOG )
                fprintf(stdout, "%s%s\n", tbuff, _msgbuf[i]->msg);
            else
                fprintf(stderr, "%s%s\n", tbuff, _msgbuf[i]->msg);
            last = i;
        }
        i = INCPTR(i);
    }
    while (i != _msgbufptr);

    /*
        Reset the dump starting pointer and dump time.
    */
    _first_msg = INCPTR(last);
    _last_dump = time(NULL);
}
