/*
 *      fhist - file history and comparison tools
 *      Copyright (C) 1991-1994, 1998-2000, 2002, 2008 Peter Miller
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      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 GNU General Public License
 *      along with this program. If not, see
 *      <http://www.gnu.org/licenses/>.
 */

#include <ac/ctype.h>
#include <ac/errno.h>
#include <ac/stdarg.h>
#include <ac/stddef.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>

#include <error.h>
#include <mprintf.h>
#include <progname.h>
#include <quit.h>


/*
 * NAME
 *      wrap - wrap s string over lines
 *
 * SYNOPSIS
 *      void wrap(char *);
 *
 * DESCRIPTION
 *      The wrap function is used to print error messages onto stderr
 *      wrapping ling lines.
 *
 * CAVEATS
 *      Line length is assumed to be 80 characters.
 */

#define PAGE_WIDTH 79

static void
wrap(char *s)
{
    static char escapes[] = "\rr\nn\ff\bb\tt";
    char    tmp[PAGE_WIDTH + 2];
    int     first_line;
    char    *tp;
    int     bomb_later;
    char    *progname;

    /*
     * Flush stdout so that errors are in sync with the output.
     * If you get an error doing this, whinge about it _after_ reporting
     * the originating error.  Also, clear the error on stdout to
     * avoid getting caught in an infinite loop.
     */
    if (fflush(stdout) || ferror(stdout))
    {
        bomb_later = errno;
        clearerr(stdout);
    }
    else
        bomb_later = 0;

    progname = progname_get();
    first_line = 1;
    while (*s)
    {
        char    *ep;
        int     ocol;

        /*
         * Work out how many characters fit on the line.
         */
        if (first_line)
            ocol = strlen(progname) + 2;
        else
            ocol = 8;
        for (ep = s; *ep; ++ep)
        {
            int     cw;
            int     c;

            c = (unsigned char)*ep;
            if (isprint(c))
                cw = 1 + (c == '\\');
            else
                cw = (strchr(escapes, c) ? 2 : 4);
            if (ocol + cw > PAGE_WIDTH)
                break;
            ocol += cw;
        }

        /*
         * see if there is a better place to break the line
         */
        if (*ep && *ep != ' ')
        {
            char    *mp;

            for (mp = ep; mp > s; --mp)
            {
                if (strchr(" /", mp[-1]))
                {
                    ep = mp;
                    break;
                }
            }
        }

        /*
         * ignore trailing blanks
         */
        while (ep > s && ep[-1] == ' ')
            ep--;

        /*
         * print the line
         */
        if (first_line)
            sprintf(tmp, "%.*s: ", (int)(sizeof(tmp) - 3), progname);
        else
            strcpy(tmp, "\t");
        tp = tmp + strlen(tmp);
        while (s < ep)
        {
            int     c;

            c = (unsigned char)*s++;
            if (isprint(c))
            {
                if (c == '\\')
                    *tp++ = '\\';
                *tp++ = c;
            }
            else
            {
                char    *esc;

                esc = strchr(escapes, c);
                if (esc)
                {
                    *tp++ = '\\';
                    *tp++ = esc[1];
                }
                else
                {
                    sprintf(tp, "\\%3.3o", c);
                    tp += strlen(tp);
                }
            }
        }
        *tp++ = '\n';
        *tp = 0;
        fputs(tmp, stderr);
        if (ferror(stderr))
            break;

        /*
         * skip leading spaces for subsequent lines
         */
        while (*s == ' ')
            s++;
        first_line = 0;
    }
    if (fflush(stderr) || ferror(stderr))
    {
        /* don't print why, there is no point! */
        quit(1);
    }
    if (bomb_later)
    {
        errno = bomb_later;
        nfatal_raw("(stdout)");
    }
}


static void
double_jeopardy(void)
{
    char    buffer[200];

    sprintf
    (
        buffer,
        "while attempting to construct an error message: %.100s (fatal)",
        strerror(errno)
    );
    wrap(buffer);
    quit(1);
}


static char *
copy_string(char *s)
{
    char    *cp;

    errno = 0;
    cp = malloc(strlen(s) + 1);
    if (!cp)
    {
        if (!errno)
            errno = ENOMEM;
        double_jeopardy();
    }
    strcpy(cp, s);
    return cp;
}


/*
 *  NAME
 *      error - place a message on the error stream
 *
 *  SYNOPSIS
 *      void error(char *s, ...);
 *
 *  DESCRIPTION
 *      Error places a message on the error output stream.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a newline, automatically.
 *
 *  CAVEAT
 *      Things like "error(filename)" blow up if the filename
 *      contains a '%' character.
 */

void
error_raw(char *s, ...)
{
    va_list         ap;
    char            *buffer;

    va_start(ap, s);
    buffer = vmprintf_errok(s, ap);
    if (!buffer)
        double_jeopardy();
    va_end(ap);
    wrap(buffer);
}


/*
 *  NAME
 *      nerror - place a system fault message on the error stream
 *
 *  SYNOPSIS
 *      void nerror(char *s, ...);
 *
 *  DESCRIPTION
 *      Nerror places a message on the error output stream.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a text description of the error
 *      indicated by the 'errno' global variable, automatically.
 *
 *  CAVEAT
 *      Things like "nerror(filename)" blow up if the filename
 *      contains a '%' character.
 */

void
nerror_raw(char *s, ...)
{
    va_list         ap;
    int             n;
    char            *buf1;
    char            *buf2;

    n = errno;
    va_start(ap, s);
    buf1 = vmprintf_errok(s, ap);
    if (!buf1)
        double_jeopardy();
    va_end(ap);
    buf1 = copy_string(buf1);
    buf2 = mprintf_errok("%s: %s", buf1, strerror(n));
    if (!buf2)
        double_jeopardy();
    free(buf1);
    wrap(buf2);
}


/*
 *  NAME
 *      nfatal - place a system fault message on the error stream and exit
 *
 *  SYNOPSIS
 *      void nfatal(char *s, ...);
 *
 *  DESCRIPTION
 *      Nfatal places a message on the error output stream and exits.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a text description of the error
 *      indicated by the 'errno' global variable, automatically.
 *
 *  CAVEAT
 *      Things like "nfatal(filename)" blow up if the filename
 *      contains a '%' character.
 *
 *      This function does NOT return.
 */

void
nfatal_raw(char *s, ...)
{
    va_list         ap;
    int             n;
    char            *buf1;
    char            *buf2;

    n = errno;
    va_start(ap, s);
    buf1 = vmprintf_errok(s, ap);
    if (!buf1)
        double_jeopardy();
    va_end(ap);
    buf1 = copy_string(buf1);
    buf2 = mprintf_errok("%s: %s", buf1, strerror(n));
    if (!buf2)
        double_jeopardy();
    wrap(buf2);
    quit(1);
}


/*
 *  NAME
 *      fatal - place a message on the error stream and exit
 *
 *  SYNOPSIS
 *      void fatal(char *s, ...);
 *
 *  DESCRIPTION
 *      Fatal places a message on the error output stream and exits.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a newline, automatically.
 *
 *  CAVEAT
 *      Things like "error(filename)" blow up if the filename
 *      contains a '%' character.
 *
 *      This function does NOT return.
 */

void
fatal_raw(char *s, ...)
{
    va_list         ap;
    char            *buffer;

    va_start(ap, s);
    buffer = vmprintf_errok(s, ap);
    if (!buffer)
        double_jeopardy();
    va_end(ap);
    wrap(buffer);
    quit(1);
}


/*
 *  NAME
 *      assert - make an assertion
 *
 *  SYNOPSIS
 *      void assert(int condition);
 *
 *  DESCRIPTION
 *      Assert is a handy tool for a programmer to guarantee the internal
 *      consistency of their program. If "-DDEBUG" is specified on
 *      the compiler's command line, then assert will generate code to verify
 *      the assertios made. If no DEBUG is defined, assertions will generate
 *      no code.
 *
 *  CAVEAT
 *      If the assertion fails, a fatal diagnostic is issued.
 *
 *      The #define's which control the compilation may be found in "error.h".
 *
 */

#ifdef my_assert
#undef my_assert
#endif

void
my_assert(int c, char *s, char *file, int line)
{
    if (c)
        return;
    error_raw("%s: %d: assertion \"%s\" failed (bug)", file, line, s);
    abort();
}
