/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         Utilities
 *      Filename:       UT_ErrPrint.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions to print error messages to
 *                      the standard error output.
 *
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      UT_ErrPrintStr
 *                      UT_ErrPrintAddStr
 *
 **************************************************************************/

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

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

#define TTY_LINE_LEN    79      /* Length of a line on the terminal in
                                   characters, minus some safety margin */

#define BREAK_LEN       30      /* Maximum word length. Words shorter than
                                   "BREAK_LEN" characters are never broken into
                                   pieces by print_expl. */

/* Test if a given character is a good place where to break a text string. */

static UT_Bool nicebreak (char c)
{
    return (isspace (c) || c == '-' || c == '/')? UT_True: UT_False;
}

/* Read character string starting at "start" and try to find the longest prefix
of it which fits nicely onto a line of "llen" characters. Return a pointer to
the first address after the prefix in "next" and a pointer to the first address
after the last non white-space character in the prefix in "end". */

static void find_span (Int32 llen, const char *start,
                       const char **end, const char **next)
{
    const char *c;
    Int32 pos, brklen;

    brklen = llen - BREAK_LEN;
    if (brklen < 0) {
        brklen = 0;
    }
    pos = 0;
    c = start;
    while (*c && pos < llen) {
        if (*c == '\n') {
            ++c;
            break;
        } else if (*c == '\t') {
            ++pos;
            while (pos % 8) {
                ++pos;
            }
        } else if (!iscntrl (*c)) {
            ++pos;
        }
        ++c;
    }
    *next = c;
    if (*c) {
        --c;
        while (c >= start + brklen && !nicebreak (*c)) {
            --c;
        }
        if (c <= start + brklen) {
            c = *next - 1;
        } else {
            *next = c + 1;
        }
    } else {
        --c;
    }
    while (c >= start && (*c == '\t' || *c == '\n')) {
        --c;
    }
    *end = c + 1;
    return;
}

/* Print an array of characters to stderr. Expand tabs into sequences of
spaces. Copy spaces, newlines and ordinary characters as they are. Discard
all other characters.
"Start" and "end" are pointers to the first character in the array to be
printed and to the first address after the last character in the array.
Function "print_span" assumes that "pos" is the distance of the cursor from the
left border of the terminal screen before printing. "Print_string" returns the
distance of the cursor from the left border after printing. */

static Int32 print_span (Int32 pos, const char *start, const char *end)
{
    const char *c;

    c = start;
    while (c < end) {
        if (*c == '\t') {
            putc (' ', stderr);
            ++pos;
            while (pos % 8) {
                putc (' ', stderr);
                ++pos;
            }
        } else if (*c == '\n') {
            putc ('\n', stderr);
            pos = 0;
        } else if (isspace (*c)) {
            putc (' ', stderr);
            ++pos;
        } else if (!iscntrl (*c)) {
            putc (*c, stderr);
            ++pos;
        }
        ++c;
    }
    return pos;
}

/* Print the contents of text string "str" to stderr. Try to break "str" into
pieces of less than "linelen" characters and append a newline character at the
end of every piece. Do not append a newline character at the end of the last
piece if "nl" is "UT_False". Function "print_string" assumes that "pos" is the
distance of the cursor from the left border of the terminal screen before
printing. "Print_string" returns the distance of the cursor from the left
border after printing. */

static Int32 print_string (Int32 linelen, Int32 pos, const char str[], UT_Bool nl)
{
    const char *start, *end, *next;

    start = str;
    while (*start) {
        if (start > str) {
            putc ('\n', stderr);
            pos = 0;
        }
        find_span (linelen - pos, start, &end, &next);
        if (end <= start) {
            putc ('\n', stderr);
            pos = 0;
            find_span (linelen - pos, start, &end, &next);
        }
        pos = print_span (pos, start, end);
        start = next;
    }
    if (nl) {
        putc ('\n', stderr);
        pos = 0;
    }
    return pos;
}

/***************************************************************************
 *[@e
 *      Name:           UT_ErrPrintStr
 *
 *      Usage:          Print the contents of UT_ErrStr.
 *
 *      Synopsis:       void UT_ErrPrintStr (const char *txt)
 *
 *      Description:    If "txt" is not an empty string or NULL, "txt" is
 *                      printed to the standard error output file, followed
 *                      by a colon and a space.
 *                      UT_ErrStr is then printed, independently of the
 *                      contents of "txt".
 *                      UT_ErrPrintStr breaks the output up into lines by
 *                      inserting newline characters between words where
 *                      necessary.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_ErrPrintStr (const char *txt)
{
    Int32 pos;

    if (!UT_ErrInitialized) {
        UT_ErrInit ();
    }
    if (stdout) {
        fflush (stdout);
    }
    if (txt && txt[0]) {
        pos = print_string (TTY_LINE_LEN -2, 0,   (char *) txt,  UT_False);
        pos = print_string (TTY_LINE_LEN,    pos, (char *) ": ", UT_False);
    } else {
        pos = 0;
    }
    print_string (TTY_LINE_LEN, pos, (char *) UT_ErrStr, UT_True);
    fflush (stderr);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_ErrPrintAddStr
 *
 *      Usage:          Print the contents of UT_ErrAddStr.
 *
 *      Synopsis:       void UT_ErrPrintAddStr (void)
 *
 *      Description:    If UT_ErrAddStr contains a non-empty string, it is
 *                      broken up nicely into lines (newline characters are
 *                      inserted between words where necessary). A '(' and a
 *                      ')' character are appended at the beginning and at the
 *                      end. The result is printed to the the standard error
 *                      output file.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_ErrPrintAddStr (void)
{
    Int32 pos;

    if (!UT_ErrInitialized) {
        UT_ErrInit ();
    }
    if (UT_ErrAddStr[0]) {
        if (stdout) {
            fflush (stdout);
        }
        pos = print_string (TTY_LINE_LEN -1, 0,   (char *) "(", UT_False);
        pos = print_string (TTY_LINE_LEN -1, pos, (char *) UT_ErrAddStr, UT_False);
        print_string (TTY_LINE_LEN, pos, (char *) ")", UT_True);
        fflush (stderr);
    }
    return;
}
