/*
 *	aegis - project change supervisor
 *	Copyright (C) 1991-1994, 1997, 1999, 2002, 2003 Peter Miller;
 *	All rights reserved.
 *
 *	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 2 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, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to printf into memory
 */

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

#include <error.h>
#include <mprintf.h>
#include <str.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)
{
    char            *hold;
    size_t          nbytes;

    nbytes = tmplen + QUANTUM;
    errno = 0;
    hold = (char *)realloc(tmp, nbytes);
    if (!hold)
    {
	if (!errno)
	    errno = ENOMEM;
	return 0;
    }
    tmplen = nbytes;
    tmp = hold;
    return 1;
}


/*
 * NAME
 *	build fake - construct formatting specifier string
 *
 * SYNOPSIS
 *	void build_fake(char *fake, 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 sprintf 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, int flag, int width, int precision, int qualifier,
    int specifier)
{
    char            *fp;

    fp = fake;
    *fp++ = '%';
    if (flag)
	*fp++ = flag;
    if (width > 0)
    {
	sprintf(fp, "%d", width);
	fp += strlen(fp);
    }
    *fp++ = '.';
    sprintf(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 sprintf
 *	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)
{
    int             width;
    int             width_set;
    int             prec;
    int             prec_set;
    int             c;
    const char      *s;
    int             qualifier;
    int             flag;
    char            fake[QUANTUM - 1];

    /*
     * 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 = 0;
	tmp = (char *)malloc(tmplen);
	if (!tmp)
	{
	    if (!errno)
		errno = ENOMEM;
	    return 0;
	}
    }

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

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

	default:
	    flag = 0;
	    break;
	}

	/*
	 * get optional width
	 */
	width = 0;
	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
	 */
	prec = 0;
	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
	 */
	switch (c)
	{
	default:
	    qualifier = 0;
	    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;
		char            num[MAX_WIDTH + 1];
		size_t          len;

		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;
		build_fake(fake, flag, width, prec, 0, c);
		sprintf(num, fake, a);
		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;
		char            num[MAX_WIDTH + 1];
		size_t          len;

		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;
		build_fake(fake, flag, width, prec, 'l', c);
		sprintf(num, fake, a);
		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':
	    {
		double          a;
		char            num[MAX_WIDTH + 1];
		size_t          len;

		/*
	         * Ignore "long double" for now,
	         * traditional implementations no grok.
	         */
		a = va_arg(ap, double);
		if (!prec_set)
		    prec = 6;
		if (width > MAX_WIDTH)
		    width = MAX_WIDTH;
		if (prec > MAX_WIDTH)
		    prec = MAX_WIDTH;
		build_fake(fake, flag, width, prec, 0, c);
		sprintf(num, fake, a);
		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;

		    a = va_arg(ap, long *);
		    *a = length;
		}
		break;

	    case 'h':
		{
		    short           *a;

		    a = va_arg(ap, short *);
		    *a = length;
		}
		break;

	    default:
		{
		    int             *a;

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

	case 'u':
	case 'o':
	case 'x':
	case 'X':
	    {
		unsigned long   a;
		char            num[MAX_WIDTH + 1];
		size_t          len;

		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;
		build_fake(fake, flag, width, prec, 'l', c);
		sprintf(num, fake, a);
		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;
		size_t          len;

		a = va_arg(ap, char *);
		if (prec_set)
		{
		    char            *ep;

		    ep = (char *)memchr(a, 0, prec);
		    if (ep)
			len = ep - a;
		    else
			len = prec;
		}
		else
		    len = strlen(a);
		if (!prec_set || len < (size_t)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;
		size_t          len;

		a = va_arg(ap, string_ty *);
		len = a->str_length;
		if (!prec_set)
		    prec = len;
		if (len < (size_t)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 sprintf
 *	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, ...)
{
    char            *result;
    va_list         ap;

    va_start(ap, fmt);
    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 sprintf 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;

    result = vmprintf_errok(fmt, ap);
    if (!result)
	nfatal("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 sprintf 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, ...)
{
    char            *result;
    va_list         ap;

    va_start(ap, fmt);
    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 sprintf 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))
	nfatal("mprintf \"%s\"", fmt);
    return str_n_from_c(tmp, length);
}
