//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	 USA
//-----------------------------------------------------------------------------
// package	libh/io
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "io_lib.h"

//-----------------------------------------------------------------------------
// local variables
//-----------------------------------------------------------------------------
static int		frac_digits	= 0;
static int		use_local	= 0;
static char		time_format	[64] = { 0 };
static char		program_name	[PATH_MAX+1] = { 0 };

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_set_local
//
// purpose	Set the mode to use the local timezone for message timestamps
//		to on or off based on the given value.
//
// arguments	(int) -1: do not change, 0: set mode off, else set mode on
//
// return	(int) current local mode setting
//-----------------------------------------------------------------------------
int
msg_set_local (
    int		arg_local
    )
    __PROTO_END__
{
    if ( arg_local >= 0 ) {
	use_local = arg_local;
    }
    return use_local;
}

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_set_local_off
//
// purpose	Set the mode to use the local timezone for message timestamps
//		to off.
//
// arguments	-none-
//
// return	(int) current local mode setting, 0
//-----------------------------------------------------------------------------
#define msg_set_local_off() (msg_set_local(0))

__FMACRO_END__

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_set_local_on
//
// purpose	Set the mode to use the local timezone for message timestamps
//		to on.
//
// arguments	-none-
//
// return	(int) current local mode setting, 1
//-----------------------------------------------------------------------------
#define msg_set_local_on() (msg_set_local(1))

__FMACRO_END__

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_get_local
//
// purpose	Get the mode to use the local timezone for message timestamps.
//
// arguments	-none-
//
// return	(int) current local mode setting
//-----------------------------------------------------------------------------
#define msg_get_local() (msg_set_local(-1))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_set_frac_digits
//
// purpose	Set the number of fraction digits to be used for the
//		fractions of seconds to be appended to the time_format.
//
// arguments	1 (int) number of digits to use, or negative to not change.
//
// returns	(int) number of digits to use
//-----------------------------------------------------------------------------
int
msg_set_frac_digits (
    int		arg_digits
    )
    __PROTO_END__
{
    if ( arg_digits >= 0 ) {
	frac_digits = arg_digits;
	if ( frac_digits > 12 ) frac_digits = 12;
    }
    return frac_digits;
}

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_clear_frac_digits
//
// purpose	Clear the number of fraction digits to be used for the
//		fractions of seconds to be appended to the time_format.
//
// arguments	-none-
//
// returns	(int) 0
//-----------------------------------------------------------------------------
#define msg_clear_frac_digits() (msg_set_frac_digits(0))

__FMACRO_END__

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_get_frac_digits
//
// purpose	Get the minimum number of fraction digits to be used for the
//		fractions of seconds to be appended to the time_format.
//
// arguments	-none-
//
// returns	(int) minimum number of digits to use
//-----------------------------------------------------------------------------
#define msg_get_frac_digits() (msg_set_frac_digits(-1))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_set_time_format
//
// purpose	Set the prefix format to be included in messages.  If the value
//		given is NULL, then unset the program name.
//
// arguments	1 (const char *) time format string to be set, empty string to
//				 unset, or NULL to leave unchanged
//
// returns	(char *) pointer to copied prefix format.
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//
// WARNING	While this setting is being done, other threads should not
//		use any functions that use this setting since the string is
//		not updated atomically.
//-----------------------------------------------------------------------------
char *
msg_set_time_format (
    const char *	arg_format
    )
    __PROTO_END__
{
    if ( arg_format ) {
	strncpy( time_format, arg_format, sizeof time_format - 1 );
	time_format[ sizeof time_format - 1 ] = 0;
    } else {
	time_format[0] = 0;
    }
    return time_format;
}

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_clear_time_format
//
// purpose	Clear the time format to be included in messages.
//
// arguments	-none-
//
// returns	(char *) pointer to cleared time format.
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//-----------------------------------------------------------------------------
#define msg_clear_time_format() (msg_set_time_format((const char*)""))

__FMACRO_END__

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_get_time_format
//
// purpose	Get the time format to be included in messages.
//
// arguments	-none-
//
// returns	(char *) pointer to copied time format.
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//-----------------------------------------------------------------------------
#define msg_get_time_format() (msg_set_time_format((const char*)(NULL)))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_set_program_name
//
// purpose	Set the program name to be included in messages to the last
//		component of the name or full path given.  If the argument is
//		NULL, then unset the program name.
//
// arguments	1 (const char *) program name to be set, empty string to
//				 unset, or NULL to leave unchanged
//
// returns	(char *) pointer to copied program name.
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//
// WARNING	While this setting is being done, other threads should not
//		use any functions that use this setting since the string is
//		not updated atomically.
//-----------------------------------------------------------------------------
char *
msg_set_program_name (
    const char *	arg_program
    )
    __PROTO_END__
{
    const char *	name_ptr	;

    if ( arg_program ) {
	name_ptr = arg_program;
	while ( * arg_program ) if ( * arg_program ++ == '/' ) name_ptr = arg_program;
	program_name[ sizeof program_name - 1 ] = 0;
	strncpy( program_name, name_ptr, sizeof program_name - 1 );
    }
    return program_name;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_set_program_path
//
// purpose	Set the program name to be included in messages to the full
//		program path as given.  If the argument is NULL, then unset
//		the program name.
//
// arguments	1 (const char *) program path to be set, empty string to
//				 unset, or NULL to leave unchanged
//
// returns	(char *) pointer to copied program path.
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//
// WARNING	While this setting is being done, other threads should not
//		use any functions that use this setting since the string is
//		not updated atomically.
//-----------------------------------------------------------------------------
char *
msg_set_program_path (
    const char *	arg_program
    )
    __PROTO_END__
{
    if ( arg_program ) {
	program_name[ sizeof program_name - 1 ] = 0;
	strncpy( program_name, arg_program, sizeof program_name - 1 );
    }
    return program_name;
}

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// macro	msg_clear_program_name
//
// purpose	Clear the program name to be included in messages.
//
// arguments	-none-
//
// returns	(char *) pointer to cleared program name
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//-----------------------------------------------------------------------------
#define msg_clear_program_name() (msg_set_program_name((const char*)""))

__FMACRO_END__

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_get_program_name
//
// purpose	Get the program name to be included in messages.
//
// arguments	-none-
//
// returns	(char *) pointer to copied program name
//
// WARNING	This setting has process scope.  That means it will apply for
//		all threads in a multi-threading process.
//-----------------------------------------------------------------------------
#define msg_get_program_name() (msg_set_program_name((const char*)(NULL)))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	msg_vfprintf
//
// purpose	Format an output message according to registered and given
//		format specifications and output it to the specified file.
//
// arguments	1 (FILE *) destination output stream
//		2 (const char *) format for message content
//		3 (va_list) started variable argument list
//
// returns	(int) number of characters printed
//-----------------------------------------------------------------------------
int
msg_vfprintf (
    FILE *		arg_file
    ,
    const char *	arg_format
    ,
    va_list		arg_va_list
    )
    __PROTO_END__
{

    char *		msg_ptr		;
    size_t		msg_len		;
    char		msg_space	[PATH_MAX+1+4096];


    msg_ptr = msg_space;
    msg_len = sizeof msg_space - 1;

    //--------------------------------------------------
    // If the time stamp is registered, format that now.
    //--------------------------------------------------
    if ( time_format ) {
	struct tm	tm_now	;
	long		usec	;
	int		len	;

	//----------------------------------
	// Get and convert the current time.
	//----------------------------------
	{
	    struct timeval time_now;

	    gettimeofday( & time_now, NULL );
	    ( use_local ? localtime_r : gmtime_r )( (time_t *) & time_now.tv_sec, & tm_now );
	    usec = time_now.tv_usec;
	}

	//----------------------------------------
	// Format the basic part of the timestamp.
	//----------------------------------------
	len = strftime( msg_ptr, msg_len, time_format, & tm_now );
	if ( len > 0 ) {
	    msg_ptr += len;
	    msg_len -= len;

	    //-----------------------------------------------------
	    // If fractions of a second are specified, format that.
	    //-----------------------------------------------------
	    if ( frac_digits > 0 && frac_digits < msg_len ) {
		long	base	= 1000000;
		int	digits	= 0;

		* msg_ptr ++ = '.';
		-- msg_len;

		while ( msg_len && ++ digits <= frac_digits ) {
		    if ( base > 0 ) {
			* msg_ptr = "0123456789"[ usec / base ];
			usec %= base;
			base /= 10;
		    }
		    ++ msg_ptr;
		    -- msg_len;
		}
	    }
	}

	//----------------------------
	// Append the separator space.
	//----------------------------
	if ( msg_len ) {
	    * msg_ptr ++ = ' ';
	    -- msg_len;
	}
    }

    //----------------------------------------------------
    // If the program name is registered, append that now.
    //----------------------------------------------------
    if ( program_name ) {
	int len;

	len = snprintf( msg_ptr, msg_len, "%s: ", program_name );
	msg_ptr += len;
	msg_len -= len;
    }

    //---------------------------------------------------------------------------
    // Format the actual message content according to given format and arguments.
    //---------------------------------------------------------------------------
    {
	int len;

	len = vsnprintf( msg_ptr, msg_len, arg_format, arg_va_list );
	msg_ptr += len;
	msg_len -= len;
    }

    //----------------------
    // Append final newline.
    //----------------------
    * msg_ptr = '\n';
    ++ msg_ptr;
    -- msg_len;

    //----------------------------------------------------
    // Now actually output the given message and flush it.
    //----------------------------------------------------
    fwrite( msg_space, 1, msg_ptr - msg_space, arg_file );
    fflush( arg_file );

    //---------------------------------------
    // Return the count of output characters.
    //---------------------------------------
    return (int) ( msg_ptr - msg_space );
}

