/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1991-1994, 1998, 2000, 2002 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 = 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 = 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 < 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 < 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(fmt, ap);
	if (!result)
		nfatal_raw("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_raw("mprintf \"%s\"", fmt);
	return str_n_from_c(tmp, length);
}
