//
//      tardy - a tar post-processor
//      Copyright (C) 1991-1999, 2001, 2002, 2004, 2008, 2009, 2011 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 <libtardy/ac/assert.h>
#include <libtardy/ac/errno.h>
#include <libtardy/ac/stdio.h>
#include <libtardy/ac/stdlib.h>
#include <libtardy/ac/string.h>
#include <libexplain/output.h>
#include <libexplain/realloc.h>

#include <libtardy/mprintf.h>
#include <libtardy/rcstring.h>

//
// size to grow memory by
//
#define QUANTUM 200

//
// maximum width for numbers
//
#define MAX_WIDTH (QUANTUM - 1)

//
// the buffer for storing results
//
static size_t   tmplen;
static size_t   length;
static char     *tmp;


//
// NAME
//      bigger - grow dynamic memory buffer
//
// SYNOPSIS
//      int bigger(void);
//
// DESCRIPTION
//      The bigger function is used to grow the dynamic memory buffer
//      used by vmprintf to store the formatting results.
//      The buffer is increased by QUANTUM bytes.
//
// RETURNS
//      int; zero if failed to realloc memory, non-zero if successful.
//
// CAVEATS
//      The existing buffer is still valid after failure.
//

static int
bigger(void)
{
    size_t nbytes = tmplen + QUANTUM;
    char *hold = (char *)explain_realloc_or_die(tmp, nbytes);
    tmplen = nbytes;
    tmp = hold;
    return 1;
}


//
// NAME
//      build fake - construct formatting specifier string
//
// SYNOPSIS
//      void build_fake(char *fake, size_t fakesize, int flag, int width,
//      int prec, int qual, int spec);
//
// DESCRIPTION
//      The build_fake function is used to construct a format
//      specification string from the arguments presented.  This is
//      used to guarantee exact replication of printf behaviour.
//
// ARGUMENTS
//      fake    - buffer to store results
//      flag    - the flag specified (zero if not)
//      width   - the width specified (zero if not)
//      prec    - the precision specified (zero if not)
//      qual    - the qualifier specified (zero if not)
//      spec    - the formatting specifier specified
//

static void
build_fake(char *fake, size_t fakesize, int flag, int width, int precision,
    int qualifier, int specifier)
{
    char *fp = fake;
    *fp++ = '%';
    if (flag)
        *fp++ = flag;
    if (width > 0)
    {
        snprintf(fp, fake + fakesize - fp, "%d", width);
        fp += strlen(fp);
    }
    *fp++ = '.';
    snprintf(fp, fake + fakesize - fp, "%d", precision);
    fp += strlen(fp);
    if (qualifier)
        *fp++ = qualifier;
    *fp++ = specifier;
    *fp = 0;
}


//
// NAME
//      vmprintf_errok - build a formatted string in dynamic memory
//
// SYNOPSIS
//      char *vmprintf_errok(char *fmt, va_list ap);
//
// DESCRIPTION
//      The vmprintf_errok function is used to build a formatted string
//      in memory.  It understands all of the ANSI standard printf
//      formatting directives.  Additionally, "%S" may be used to
//      manipulate (string_ty *) strings.
//
// ARGUMENTS
//      fmt     - string specifying formatting to perform
//      ap      - arguments of types as indicated by the format string
//
// RETURNS
//      char *; pointer to buffer containing formatted string
//              NULL if there is an error (sets errno)
//
// CAVEATS
//      The contents of the buffer pointed to will change between calls
//      to vmprintf_errok.  The buffer itself may move between calls to
//      vmprintf_errok.  DO NOT hand the result of vmprintf_errok to
//      free().
//

char *
vmprintf_errok(const char *fmt, va_list ap)
{

    //
    // Build the result string in a temporary buffer.
    // Grow the temporary buffer as necessary.
    //
    // It is important to only make one pass across the variable argument
    // list.  Behaviour is undefined for more than one pass.
    //
    if (!tmplen)
    {
        tmplen = 500;
        errno = ENOMEM;
        tmp = (char *)malloc(tmplen);
        if (!tmp)
            return 0;
    }

    length = 0;
    const char *s = fmt;
    while (*s)
    {
        int c = *s++;
        if (c != '%')
        {
            normal:
            if (length >= tmplen && !bigger())
                return 0;
            tmp[length++] = c;
            continue;
        }
        c = *s++;

        //
        // get optional flag
        //
        int flag = 0;
        switch (c)
        {
        case '+':
        case '-':
        case '#':
        case '0':
        case ' ':
            flag = c;
            c = *s++;
            break;

        default:
            break;
        }

        //
        // get optional width
        //
        int width = 0;
        int width_set = 0;
        switch (c)
        {
        case '*':
            width = va_arg(ap, int);
            if (width < 0)
            {
                flag = '-';
                width = -width;
            }
            c = *s++;
            width_set = 1;
            break;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            for (;;)
            {
                width = width * 10 + c - '0';
                c = *s++;
                switch (c)
                {
                default:
                    break;

                case '0': case '1': case '2': case '3':
                case '4': case '5': case '6': case '7':
                case '8': case '9':
                    continue;
                }
                break;
            }
            width_set = 1;
            break;

        default:
            break;
        }

        //
        // get optional precision
        //
        int prec = 0;
        int prec_set = 0;
        if (c == '.')
        {
            c = *s++;
            switch (c)
            {
            default:
                prec_set = 1;
                break;

            case '*':
                c = *s++;
                prec = va_arg(ap, int);
                if (prec < 0)
                {
                    prec = 0;
                    break;
                }
                prec_set = 1;
                break;

            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                for (;;)
                {
                    prec = prec * 10 + c - '0';
                    c = *s++;
                    switch (c)
                    {
                    default:
                        break;

                    case '0': case '1': case '2': case '3':
                    case '4': case '5': case '6': case '7':
                    case '8': case '9':
                        continue;
                    }
                    break;
                }
                prec_set = 1;
                break;
            }
        }

        //
        // get the optional qualifier
        //
        int qualifier = 0;
        switch (c)
        {
        default:
            break;

        case 'l':
        case 'h':
        case 'L':
            qualifier = c;
            c = *s++;
            break;
        }

        //
        // get conversion specifier
        //
        switch (c)
        {
        default:
            errno = EINVAL;
            return 0;

        case '%':
            goto normal;

        case 'c':
            {
                int a = (unsigned char)va_arg(ap, int);
                if (!prec_set)
                    prec = 1;
                if (width > MAX_WIDTH)
                    width = MAX_WIDTH;
                if (prec > MAX_WIDTH)
                    prec = MAX_WIDTH;
                char fake[QUANTUM - 1];
                build_fake(fake, sizeof(fake), flag, width, prec, 0, c);
                char num[MAX_WIDTH + 1];
                snprintf(num, sizeof(num), fake, a);
                size_t len = strlen(num);
                assert(len < QUANTUM);
                if (length + len > tmplen && !bigger())
                    return 0;
                memcpy(tmp + length, num, len);
                length += len;
            }
            break;

        case 'd':
        case 'i':
            {
                long a = 0;
                switch (qualifier)
                {
                case 'l':
                    a = va_arg(ap, long);
                    break;

                case 'h':
                    a = (short)va_arg(ap, int);
                    break;

                default:
                    a = va_arg(ap, int);
                    break;
                }
                if (!prec_set)
                    prec = 1;
                if (width > MAX_WIDTH)
                    width = MAX_WIDTH;
                if (prec > MAX_WIDTH)
                    prec = MAX_WIDTH;
                char fake[QUANTUM - 1];
                build_fake(fake, sizeof(fake), flag, width, prec, 'l', c);
                char num[MAX_WIDTH + 1];
                snprintf(num, sizeof(num), fake, a);
                size_t len = strlen(num);
                assert(len < QUANTUM);
                if (length + len > tmplen && !bigger())
                    return 0;
                memcpy(tmp + length, num, len);
                length += len;
            }
            break;

        case 'e':
        case 'f':
        case 'g':
        case 'E':
        case 'F':
        case 'G':
            {
                //
                // Ignore "long double" for now,
                // traditional implementations no grok.
                //
                double a = va_arg(ap, double);
                if (!prec_set)
                    prec = 6;
                if (width > MAX_WIDTH)
                    width = MAX_WIDTH;
                if (prec > MAX_WIDTH)
                    prec = MAX_WIDTH;
                char fake[QUANTUM - 1];
                build_fake(fake, sizeof(fake), flag, width, prec, 0, c);
                char num[MAX_WIDTH + 1];
                snprintf(num, sizeof(num), fake, a);
                size_t len = strlen(num);
                assert(len < QUANTUM);
                if (length + len > tmplen && !bigger())
                    return 0;
                memcpy(tmp + length, num, len);
                length += len;
            }
            break;

        case 'n':
            switch (qualifier)
            {
            case 'l':
                {
                    long *a = va_arg(ap, long *);
                    *a = length;
                }
                break;

            case 'h':
                {
                    short *a = va_arg(ap, short *);
                    *a = length;
                }
                break;

            default:
                {
                    int *a = va_arg(ap, int *);
                    *a = length;
                }
                break;
            }
            break;

        case 'u':
        case 'o':
        case 'x':
        case 'X':
            {
                unsigned long a = 0;
                switch (qualifier)
                {
                case 'l':
                    a = va_arg(ap, unsigned long);
                    break;

                case 'h':
                    a = (unsigned short)va_arg(ap, unsigned int);
                    break;

                default:
                    a = va_arg(ap, unsigned int);
                    break;
                }
                if (!prec_set)
                    prec = 1;
                if (prec > MAX_WIDTH)
                    prec = MAX_WIDTH;
                if (width > MAX_WIDTH)
                    width = MAX_WIDTH;
                char fake[QUANTUM - 1];
                build_fake(fake, sizeof(fake), flag, width, prec, 'l', c);
                char num[MAX_WIDTH + 1];
                snprintf(num, sizeof(num), fake, a);
                size_t len = strlen(num);
                assert(len < QUANTUM);
                if (length + len > tmplen && !bigger())
                    return 0;
                memcpy(tmp + length, num, len);
                length += len;
            }
            break;

        case 's':
            {
                char *a = va_arg(ap, char *);
                size_t len = 0;
                if (prec_set)
                {
                    char *ep = (char *)memchr(a, 0, prec);
                    if (ep)
                        len = ep - a;
                    else
                        len = prec;
                }
                else
                    len = strlen(a);
                if (!prec_set || (int)len < prec)
                    prec = len;
                if (!width_set || width < prec)
                    width = prec;
                len = width;
                while (length + len > tmplen)
                {
                    if (!bigger())
                        return 0;
                }
                if (flag != '-')
                {
                    while (width > prec)
                    {
                        tmp[length++] = ' ';
                        width--;
                    }
                }
                memcpy(tmp + length, a, prec);
                length += prec;
                width -= prec;
                if (flag == '-')
                {
                    while (width > 0)
                    {
                        tmp[length++] = ' ';
                        width--;
                    }
                }
            }
            break;

        case 'S':
            {
                string_ty *a = va_arg(ap, string_ty *);
                size_t len = a->str_length;
                if (!prec_set)
                    prec = len;
                if ((int)len < prec)
                    prec = len;
                if (!width_set)
                    width = prec;
                if (width < prec)
                    width = prec;
                len = width;
                while (length + len > tmplen)
                {
                    if (!bigger())
                        return 0;
                }
                if (flag != '-')
                {
                    while (width > prec)
                    {
                        tmp[length++] = ' ';
                        width--;
                    }
                }
                memcpy(tmp + length, a->str_text, prec);
                length += prec;
                width -= prec;
                if (flag == '-')
                {
                    while (width > 0)
                    {
                        tmp[length++] = ' ';
                        width--;
                    }
                }
            }
            break;
        }
    }

    //
    // append a trailing NUL
    //
    if (length >= tmplen && !bigger())
            return 0;
    tmp[length] = 0;

    //
    // return the temporary string
    //
    return tmp;
}


//
// NAME
//      mprintf_errok - build a formatted string in dynamic memory
//
// SYNOPSIS
//      char *mprintf_errok(char *fmt, ...);
//
// DESCRIPTION
//      The mprintf_errok function is used to build a formatted string
//      in memory.  It understands all of the ANSI standard printf
//      formatting directives.  Additionally, "%S" may be used to
//      manipulate (string_ty *) strings.
//
// ARGUMENTS
//      fmt     - string spefiifying formatting to perform
//      ...     - arguments of types as indicated by the format string
//
// RETURNS
//      char *; pointer to buffer containing formatted string
//              NULL if there is an error (sets errno)
//
// CAVEATS
//      The contents of the buffer pointed to will change between calls
//      to mprintf_errok.  The buffer itself may move between calls to
//      mprintf_errok.  DO NOT hand the result of mprintf_errok to
//      free().
//

char *
mprintf_errok(const char *fmt, ...)
{
        va_list ap;
        va_start(ap, fmt);
        char *result = vmprintf_errok(fmt, ap);
        va_end(ap);
        return result;
}


//
// NAME
//      vmprintf - build a formatted string in dynamic memory
//
// SYNOPSIS
//      char *vmprintf(char *fmt, va_list ap);
//
// DESCRIPTION
//      The vmprintf function is used to build a formatted string in memory.
//      It understands all of the ANSI standard printf formatting directives.
//      Additionally, "%S" may be used to manipulate (string_ty *) strings.
//
// ARGUMENTS
//      fmt     - string spefiifying formatting to perform
//      ap      - arguments of types as indicated by the format string
//
// RETURNS
//      char *; pointer to buffer containing formatted string
//
// CAVEATS
//      On error, prints a fatal error message and exists; does not return.
//
//      The contents of the buffer pointed to will change between calls
//      to vmprintf.  The buffer itself may move between calls to vmprintf.
//      DO NOT hand the result of vmprintf to free().
//

char *
vmprintf(const char *fmt, va_list ap)
{
    char *result = vmprintf(fmt, ap);
    if (!result)
        explain_output_error_and_die("mprintf \"%s\"", fmt);
    return result;
}


//
// NAME
//      mprintf - build a formatted string in dynamic memory
//
// SYNOPSIS
//      char *mprintf(char *fmt, ...);
//
// DESCRIPTION
//      The mprintf function is used to build a formatted string in memory.
//      It understands all of the ANSI standard printf formatting directives.
//      Additionally, "%S" may be used to manipulate (string_ty *) strings.
//
// ARGUMENTS
//      fmt     - string spefiifying formatting to perform
//      ...     - arguments of types as indicated by the format string
//
// RETURNS
//      char *; pointer to buffer containing formatted string
//
// CAVEATS
//      On error, prints a fatal error message and exists; does not return.
//
//      The contents of the buffer pointed to will change between calls
//      to mprintf.  The buffer itself may move between calls to mprintf.
//      DO NOT hand the result of mprintfe to free().
//

char *
mprintf(const char *fmt, ...)
{
        va_list ap;
        va_start(ap, fmt);
        char *result = vmprintf(fmt, ap);
        va_end(ap);
        return result;
}


//
// NAME
//      vmprintf_str - build a formatted string in dynamic memory
//
// SYNOPSIS
//      char *vmprintf_str(char *fmt, va_list ap);
//
// DESCRIPTION
//      The vmprintf_str function is used to build a formatted string in memory.
//      It understands all of the ANSI standard printf formatting directives.
//      Additionally, "%S" may be used to manipulate (string_ty *) strings.
//
// ARGUMENTS
//      fmt     - string spefiifying formatting to perform
//      ap      - arguments of types as indicated by the format string
//
// RETURNS
//      string_ty *; string containing formatted string
//
// CAVEATS
//      On error, prints a fatal error message and exists; does not return.
//
//      It is the resposnsibility of the caller to invoke str_free to release
//      the results when finished with.
//

string_ty *
vmprintf_str(const char *fmt, va_list ap)
{
    if (!vmprintf_errok(fmt, ap))
        explain_output_error_and_die("mprintf \"%s\"", fmt);
    return str_n_from_c(tmp, length);
}
