/**************************************************************************
 *{@C
 *      Copyright:      1988-2024 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;
}
