/**************************************************************************
 *{@C
 *	Copyright:	1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *	Module:		Utilities
 *	Filename:	UT_ErrRecover.c
 *
 *	Author:		Paul Obermeier
 *
 *	Description: 	Handling of recoverable errors.
 *
 *	Additional documentation:
 *			None.
 *
 *	Exported functions:
 *			UT_ErrSupply
 *			UT_ErrSetNum
 *			UT_ErrAppendStr
 *
 **************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

#include "UT_Compat.h"
#include "UT_Error.h"
#include "UT_ErrPrivate.h"

static char *outstr = NULL;	/* Current end of the output string
				   produced by append_outstr */

static Int32 outlen;		/* Number of bytes left in the output
				   string produced by append_outstr */

/* Append characters produced by format_expl to the end of UT_ErrAddStr. */

static void append_outstr (const char *str, Int32 len)
{
    while (outlen && len && *str) {
	*outstr = *str;
	++outstr;
	++str;
	--len;
	--outlen;
    }
    *outstr = '\0';
    return;
}

/* Mark an invalid format specification with a question mark in UT_ErrAddStr. */

static void mark_error (void)
{
    append_outstr ("?", 1);
    return;
}

/* Construct UT_ErrAddStr under control of the format string "fmt". We let the
sprintf function from the C standard I/O library do all the conversions in
order to mimick its behavior as closely as possible. */

static void format_expl (const char *fmt, va_list args)
{
    const char *s1, *s2, *fmtend;
    char *tmp, buf1[UT_ErrAddStrLen + 1], buf2[UT_ErrAddStrLen + 1];
    Int32 width, prec;
    UT_Bool gotdot;

    /* Initialization. */
    s1 = fmt;
    s2 = fmt;

    /* Find the end of the format string. */
    fmtend = fmt;
    while (*fmtend) {
	++fmtend;
    }

    /* Scan the format string for sequences of ordinary characters
    and for conversion specifications. */
    while (s1 < fmtend) {
	while (s2 < fmtend && *s2 != '%') {
	    ++s2;
	}
	if (s2 - s1) {
	    /* Found a run of ordinary characters, append it
	    to the output file or string. */
	    append_outstr (s1, s2 - s1);
	}
	if (s2 < fmtend && *s2 == '%') {
	    /* Found the start of a conversion specification; seach for
	    the end of the conversion specification. */
	    s1 = s2;
	    gotdot = UT_False;
	    width = 0;
	    prec = 0;
	    while (s2 < fmtend && !isalpha (*s2)) {
		if (*s2 == '.') {
		    gotdot = UT_True;
		}
		if (*s2 == '*') {
		    mark_error ();
		    return;
		} else if (isdigit (*s2)) {
		    if (gotdot) {
			width = width * 10 + (*s2 - '0');
		    } else {
			prec = prec * 10 + (*s2 - '0');
		    }
		} else if (s2 > s1 && *s2 == '%') {
		    break;
		}
		++s2;
	    }
	    if (s2 < fmtend) {
		++s2;
	    }
	    if (s2 - s1 < 2 || s1 - s2 >= UT_ErrAddStrLen ||
		width >= UT_ErrAddStrLen || prec >= UT_ErrAddStrLen) {
		mark_error ();
		return;
	    }

	    /* Found the end of the conversion specification, pass the
	    specification to sprintf and append result of the conversion
	    to the output file or string. */
	    tmp = buf1;
	    while (s1 < s2) {
		*tmp = *s1;
		++tmp;
		++s1;
	    }
	    *tmp = '\0';
	    switch (s2[-1]) {
		case '%':
		    append_outstr ("%", 1);
		    break;
		case 'u':
		case 'o':
		case 'x':
		case 'X': {
		    unsigned int arg;
		    arg = va_arg (args, unsigned int);

		    sprintf (buf2, buf1, arg);
		    append_outstr (buf2, UT_ErrAddStrLen);
		    break;
		}
		case 'd':
		case 'c': {
		    int arg;
		    arg = va_arg (args, int);

		    sprintf (buf2, buf1, arg);
		    append_outstr (buf2, UT_ErrAddStrLen);
		    break;
		}
		case 'f':
		case 'e':
		case 'E':
		case 'g':
		case 'G': {
		    double arg;
		    arg = va_arg (args, double);

		    sprintf (buf2, buf1, arg);
		    append_outstr (buf2, UT_ErrAddStrLen);
		    break;
		}
		case 's': {
		    char *arg;
		    arg = va_arg (args, char *);

                    sprintf (buf2, buf1, arg);
		    append_outstr (buf2, UT_ErrAddStrLen);
		    break;
		}
		default:
		    mark_error ();
		    return;
	    }
	}
	s1 = s2;
    }
    return;
}

/***************************************************************************
 *[@e
 *	Name:  		UT_ErrSupply
 *
 *	Usage:		Every module that wants to use the error and signal
 *			handling functions first has to supply its set of
 *			error messages by calling UT_ErrSupply.
 *
 *	Synopsis:	void UT_ErrSupply
 *				(const char *const *const msg,
 *				Int32 n, UT_ErrBase *eb)
 *
 *	Description:	Msg must be a pointer to an array of strings
 *			containing the different error messages that can
 *			be produced by the module calling UT_ErrSupply.
 *			N is the number of entries in msg. 
 *			Eb is filled in by UT_ErrSupply in order to assign a
 *			set of error codes to the messages
 *			in msg (eb->base+i corresponds to msg[i]).
 *
 *	Return value:	None.
 *
 *	See also:
 *
 ***************************************************************************/

void UT_ErrSupply (const char *const *const msg, Int32 n, UT_ErrBase *eb)
{
    if (!UT_ErrInitialized) {
	UT_ErrInit ();
    }
    eb->base = UT_ErrGlobalList ?
               UT_ErrGlobalList->base + UT_ErrGlobalList->n : 0;
    eb->n = n;
    eb->msg = msg;
    eb->next = UT_ErrGlobalList;
    UT_ErrGlobalList = eb;
    return;
}

/***************************************************************************
 *[@e
 *	Name:  		UT_ErrSetNum
 *
 *	Usage:		Announce a recoverable error.
 *
 *	Synopsis:	void UT_ErrSetNum (Int32 code, const char *expl, ...)
 *
 *	Description:	Announce a recoverable error. Every function
 *			encountering a recoverable error should first call
 *			UT_ErrSetNum to store an error code in the global
 *			variable UT_ErrNum and then return false to inform its
 *			caller that something went wrong. Once the caller knows
 *			that an error occurred, it can examine UT_ErrNum to get
 *			more information.
 *			"Code" must be a number corresponding to an error
 *			message previously defined by a call to UT_ErrSupply.
 *			The error number will be stored in UT_ErrNum, so that
 *			it can be examined later. An error text intended to be
 *			shown to the user is stored in UT_ErrStr.
 *
 *			"Expl" should contain some text explaining the
 *			situation causing the error more precisely (e.g. when
 *			encountering a problem while reading a text file,
 *			"expl" should contain the file name and the line
 *			number in human-readable format). This text is stored
 *			in the global variable "UT_ErrAddStr".
 *			"Expl" can contain "printf"-like formatting control
 *			sequences followed by more arguments. For the exact
 *			syntax of the formatting control sequences, see the
 *			"printf" entry in the Unix Programmer's Reference
 *			Manual. Formatting differs from "printf" as follows:
 *
 *			- Using an asterisk, '*', in place of a field width or
 *			  precision is not currently supported.
 *			- Usage of an 'l' to indicate that the following
 *			  integer conversion operates on a "long" argument is
 *			  not currently supported.
 *			- UT_ErrAddStr has a limited length. If the explanatory
 *			  string becomes too long, it is truncated silently.
 *
 *	Return value:	None.
 *
 *	See also:
 *
 ***************************************************************************/

void UT_ErrSetNum (Int32 code, const char *expl, ...)
{
    UT_ErrBase *eb;
    va_list args;

    if (!UT_ErrInitialized) {
	UT_ErrInit ();
    }
    eb = UT_ErrGlobalList;
    while (UT_True) {
	if (code >= eb->base && code < eb->base + eb->n) {
	    UT_ErrNum = code;
	    UT_ErrStr = eb->msg[code - eb->base];
	    break;
	}
	if (!(eb = eb->next)) {
	    UT_ErrFatal ("UT_ErrSetNum", "undefined error code");
	}
    }
    UT_ErrAddStr[0] = '\0';
    outstr = UT_ErrAddStr;
    outlen = UT_ErrAddStrLen - 1;

    if (expl) {
	va_start (args, expl);
	format_expl (expl, args);
	va_end (args);
    }
    return;
}

/***************************************************************************
 *[@e
 *	Name:  		UT_ErrAppendStr
 *
 *	Usage:		Append explanatory text to an error message.
 *
 *	Synopsis:	void UT_ErrAppendStr
 *				(const char *sep, const char *expl, ...)
 *
 *	Description:	If UT_ErrAddStr is not empty, the contents of character
 *			string "sep" are appended to UT_ErrAddStr.
 *			"Expl" and the following optional arguments to
 *			UT_ErrAppendStr are treated as in UT_ErrSetNum, i.e. "Expl"
 *			is interpreted as a text string with embedded
 *			formatting control sequences; the remaining argments
 *			are formatted according to the control sequences in
 *			"expl". The result is appended to the contents of
 *			UT_ErrAddStr.
 *
 *	Return value:	None.
 *
 *	See also:
 *
 ***************************************************************************/

void UT_ErrAppendStr (const char *sep, const char *expl, ...)
{
    va_list args;

    if (!UT_ErrInitialized) {
	UT_ErrInit ();
    }
    if (!outstr) {
	UT_ErrAddStr[0] = '\0';
	outstr = UT_ErrAddStr;
	outlen = UT_ErrAddStrLen - 1;
    }
    if (UT_ErrAddStr[0]) {
	append_outstr (sep, strlen (sep));
    }

    va_start (args, expl);
    format_expl (expl, args);
    va_end (args);
    return;
}
