/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss or reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <blc_stdio.h>

/****************************************************************************/
/*					Local prototypes										*/
/****************************************************************************/

static	int		PrintChars( PRINTF *stat, int length, const char ptr[ 32 ] );
static	void	CopyToOutput( PRINTF *stat );
static	void	DoConversion( PRINTF *stat );
static	int		PrintInt( PRINTF *stat, int conv );
static	int		PrintNum( PRINTF *stat, int conv );
static	int		PrintFlt( PRINTF *stat, int conv );

/****************************************************************************/
/*					Local data												*/
/****************************************************************************/

static const char	blnk[ 32 ] = "                                ";
static const char	zero[ 32 ] = "00000000000000000000000000000000";

/****************************************************************************/
/*					API functions											*/
/****************************************************************************/

/** @addtogroup stdio */
/**	@{ */

/**
*	Formatted print core without floating point conversions.
*
*	This function has the exact same semantics and behaviour as the print_flt()
*	function, with the following exceptions:
*
*	- The 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G' specifiers are not recognised.
*	  If such specifiers occur in the format string, they will be ignored and
*	  simply printed as they occur in the format string.
*
*	- The l size qualifier for 'c' and 's' conversions is ignored and the
*	  passed wide character or wide character string is treated as a normal
*	  char or char pointer.
*
*	Due to the omission of the floating point, this function is much smaller
*	than using print_flt(). For details on the format string and usage
*	examples see print_flt().
*
*	@param	fmt		The format string.
*	@param	arg		The argument list (after the format string). Before you
*					call this function, you have to call va_start() and pass
*					the resulting va_list object in this argument.
*	@param	len		The maximum number of characters sent to the output. If you
*					want no limit, pass -1.
*	@param	fun		The function that outputs a number of characters.
*	@param	par		This is the first parameter that is passed to fun.
*	@param	w2m		An unused parameter. It is only here to make the prototypes
*					of print_flt() and print_int() identical. It's best if you
*					specify NULL here so that you don't have to care about type
*					mismatches.
*
*	@return			The number of bytes written to the output or the number
*					of bytes that would have been written if the maximum number
*					of output characters were not limited. If the return value
*					is 0, then no output was generated. If the return value is
*					negative, it indicates an error code. If the number of
*					output characters were limited, then a return value greater
*					than the limit indicates that the output was truncated.
*
*	@warning
*	The print_int() and print_flt() functions both use a lot of stack. You
*	can expect a stack requirement of about 200 bytes.
*/

int		print_int( const char *fmt, va_list arg, size_t len,
					BLC_FMT_PUT fun, void **par, int (*w2m)(char*,wchar_t**) )
{
PRINTF	stat;

	(void) w2m;

	stat.form = fmt;
	stat.args = arg;
	stat.outf = fun;
	stat.outp = par;
	stat.maxl = len;
	stat.cchr = _blc_print_str;
	stat.cflt = PrintFlt;
	return( _blc_print( &stat ) );
}

/** @} */

/****************************************************************************/
/*					Library internal public functions						*/
/****************************************************************************/

/*
*	This is the function that does the formatting
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It receives a prepared PRINTF structure and does everything else.
*/

int		_blc_print( PRINTF *stat )
{
	// We have not written anything yet

	stat->nout = 0;

	for ( ;; ) {

		// Copy the format string as long as we can

		CopyToOutput( stat );

		// If there was an error, we have to abort and return the error code.

		if ( stat->nout < 0 ) break;

		// If we reached the end of the format string, return the
		// number of characters generated (but not necessarily emitted).

		if ( ! *stat->form ) break;

		// A conversion must take place.

		DoConversion( stat );

		// If there was an error, we have to abort and return the error code.

		if ( stat->nout < 0 ) break;
	}

	return( stat->nout );
}

/*
*	Outputs the given number of spaces
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns 1 on success, 0 on failure
*/

int		_blc_print_spc( PRINTF *stat, int length )
{
	return( PrintChars( stat, length, blnk ) >= 0 );
}

/*
*	Outputs the given number of zeros
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns 1 on success, 0 on failure.
*/

int		_blc_print_zro( PRINTF *stat, int length )
{
	return( PrintChars( stat, length, zero ) >= 0 );
}

/*
*	Outputs a given length string
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns >= 0 on success, a negative error code on failure. The
*	error code is whatever the underlying device dependent write
*	function returned. If there was an error, the nout field in
*	the PRINTF structure is set to the error code. Otherwise, the
*	nout field is incremented by the number of characters that were
*	written or should have been written, if there had not been a
*	length limit.
*/

int		_blc_print_out( PRINTF *stat, const char *str, int len )
{
int		out;

	if ( ( out = len ) <= 0 ) return( 0 );

	if ( (size_t) stat->nout + len > stat->maxl ) {

		if ( (size_t) stat->nout > stat->maxl )

			out = 0;
		else
			out = stat->maxl - stat->nout;
	}

	if ( out ) out = (*stat->outf)( stat->outp, str, out );

	if ( out < 0 ) {

		stat->nout = out;
		return( 0 );
	}
	else {

		stat->nout += len;
		return( 1 );
	}
}

/*
*	Prints a string or a character, ignoring the 'l' specifier
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_print_str( PRINTF *stat, int conv )
{
char	*arg;
char	chr;
int		len;
int		bef, aft;

	if ( conv == 'c' ) {

		// Character conversion. We turn it to a string
		// conversion with a 1 character long string.

		chr = va_arg( stat->args, int );
		arg = (void *) &chr;
		len = 1;
	}
	else if ( conv == 's' ) {

		// String conversion.

		arg = va_arg( stat->args, char * );

		// First we need to know how long the string is. If precision
		// is specified, we limit the string length to that value.

		if ( stat->prec < 0 )

			len = strlen( arg );
		else
			len = strnlen( arg, stat->prec );
	}
	else {

		// Not a string or character conversion specifier, nothing to do.

		return( FALSE );
	}

	bef = aft = 0;

	if ( stat->fldw > len ) {

		if ( stat->flag & FLAG_LEFT )

			aft = stat->fldw - len;
		else
			bef = stat->fldw - len;
	}

	// Send leading spaces, the string and trailing spaces.

	(void)
	(
		( ! bef || _blc_print_spc( stat, bef ) ) &&
		( ! len || _blc_print_out( stat, (void *) arg, len ) ) &&
		( ! aft || _blc_print_spc( stat, aft ) )
	);

	// Indicate that we processed the conversion

	return( TRUE );
}

/****************************************************************************/
/*					Lolcal functions										*/
/****************************************************************************/

/*
*	Prints the given number of identical characters
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The third argument must point to an arraoy of 32 identical characters.
*	Returns 0 on success, a negative error code on failure.
*/

static	int		PrintChars( PRINTF *stat, int length, const char ptr[ 32 ] )
{
int		tmp, err;

	while ( length > 0 ) {

		if ( length >= 32 )

			tmp = 32;
		else
			tmp = length;

		if ( ( err = _blc_print_out( stat, ptr, tmp ) ) < 0 ) return( err );

		length -= tmp;
	}

	return( 0 );
}

/*
*	Copies the format string to the output until '%' or '\0'
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	void	CopyToOutput( PRINTF *stat )
{
int		c;
char	*p;

	// Scan the format string until we find its end or we find a '%'.

	for ( p = (char *) stat->form ; ( c = *p ) && c != '%' ; p++ );

	// We can emit that much to the output

	if ( p - stat->form ) {

		if ( ! _blc_print_out( stat, stat->form, p - stat->form ) ) {

			// Output error

			return;
		}
	}

	// If it was a '%' move the pointer behind it

	stat->form = p + ( c ? 1 : 0 );
}

/*
*	Converts the next output item
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	void	DoConversion( PRINTF *stat )
{
char	*p;
int		c;

	p = (char *) stat->form;

	if ( ( c = *p ) == '%' ) {

		// We have to emit a single '%'.

		stat->form = p + 1;
		_blc_print_out( stat, p, 1 );
		return;
	}

	// Prepare for the conversion

	stat->flag = 0;
	stat->fldw = -1;
	stat->prec = -1;
	stat->size = SIZE_NONE;

	// Collect the conversion flags. There can be any number of them.

	for ( p++ ;; c = *p++ ) {

		switch ( c ) {

			case '#':

				stat->flag |= FLAG_ALTF;
				continue;

			case ' ':

				if ( ! ( stat->flag & FLAG_PLUS ) ) stat->flag |= FLAG_BLNK;
				continue;

			case '+':

				stat->flag |=  FLAG_PLUS;
				stat->flag &= ~FLAG_BLNK;
				continue;

			case '-':

				stat->flag |=  FLAG_LEFT;
				stat->flag &= ~FLAG_ZERO;
				continue;

			case '0':

				if ( ! ( stat->flag & FLAG_LEFT ) ) stat->flag |= FLAG_ZERO;
				continue;
		}

		break;
	}

	// Collect the field width specifier

	if ( c == '*' ) {

		// The field width is the next int argument. Read it.

		if ( ( stat->fldw = va_arg( stat->args, int ) ) < 0 ) {

			// Negative field width is taken as positive, with left
			// alignment turned on

			stat->flag |=  FLAG_LEFT;
			stat->flag &= ~FLAG_ZERO;
			stat->fldw  = -stat->fldw;
		}

		c = *p++;
	}
	else if ( c >= '1' && c <= '9' ) {

		// Field width is specified

		for ( stat->fldw = 0 ; c >= '0' && c <= '9' ; c = *p++ ) {

			stat->fldw *= 10;
			stat->fldw += c - '0';
		}
	}

	// If the next character is a period, then precision is also specified

	if ( c == '.' ) {

		if ( ( c = *p++ ) == '*' ) {

			// Precision should be obtained from the argument list

			if ( ( stat->prec = va_arg( stat->args, int ) ) < 0 ) {

				// Negative precision should be turned to 0

				stat->prec = 0;
			}

			c = *p++;
		}
		else {

			for ( stat->prec = 0 ; c >= '0' && c <= '9' ; c = *p++ ) {

				stat->prec *= 10;
				stat->prec += c - '0';
			}
		}
	}

	// Now we have to check for size specifiers

	switch ( c ) {

		case 'L':

			stat->size = SIZE_LDBL;
			break;

		case 'l':

			if ( *p == 'l' ) {

				stat->size = SIZE_HUGE;
				p++;
			}
			else {

				stat->size = SIZE_LONG;
			}

			break;

		case 'h':

			if ( *p == 'h' ) {

				stat->size = SIZE_CHAR;
				p++;
			}
			else {

				stat->size = SIZE_HALF;
			}

			break;

		case 'j':

			stat->size = SIZE_HUGE;
			break;

		case 't':
		case 'z':

			stat->size = SIZE_LONG;
			break;

		default:

			p--;
			break;
	}

	c = *p++;

	// We've collected everything about the conversion, now we have to
	// actually convert the next item.

	if ( PrintInt( stat, c ) || (*stat->cchr)( stat, c ) ||
		 PrintNum( stat, c ) || (*stat->cflt)( stat, c ) ) {

		// The format specifier was recognised, move the format string pointer

		stat->form = p;
	}
	else {

		// The format specificator was not recognised. We print the
		// format string verbatim by printing the leading '%' and
		// leaving the format string pointer to point to just after
		// the '%'.

		_blc_print_out( stat, stat->form - 1, 1 );
	}
}

/*
*	Prints an integer value
*	~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		PrintInt( PRINTF *stat, int conv )
{
long long	arg;
int			unsig;
int			upper;
int			radix;
int			post;
char		pre[ 3 ];

	// If precision is specified, then the '0' flag should be ignored

	if ( stat->prec >= 0 ) stat->flag &= ~FLAG_ZERO;

	unsig = TRUE;
	upper = FALSE;

	switch ( conv ) {

		case 'd':
		case 'i':

			unsig = FALSE;

		case 'u':

			radix = 10;
			break;

		case 'o':

			radix = 8;
			break;

		case 'X':

			upper = TRUE;

		case 'x':

			radix = 16;
			break;

		case 'p':

			stat->flag |= FLAG_ALTF;
			stat->size  = SIZE_LONG;
			radix = 16;
			break;

		default:

			return( FALSE );
	}

	// Fetch the argument, depending on the type

	switch ( stat->size ) {

		case SIZE_HUGE:

			arg = va_arg( stat->args, long long );
			break;

		case SIZE_LONG:

			arg = va_arg( stat->args, long );
			break;

		default:

			arg = va_arg( stat->args, int );
			break;
	}

	if ( unsig ) {

		// For unsigned conversions we might need to mask the numbers
		// On the other hand, we shall not have a signum, even if explicitely
		// asked for.

		stat->flag &= ~( FLAG_PLUS | FLAG_BLNK );

		switch ( stat->size ) {

			case SIZE_CHAR:

				arg &= (unsigned char) -1;
				break;

			case SIZE_HALF:

				arg &= (unsigned short) -1;
				break;

			case SIZE_LONG:

				arg &= (unsigned long) -1;
				break;

			case SIZE_HUGE:

				break;

			default:

				arg &= (unsigned int) -1;
				break;
		}

		// There is no signum indicator

		unsig = 0;
	}
	else {

		// For signed conversion we have to extract the signum and
		// make the number positive

		if ( arg < 0 ) {

			unsig = '-';
			arg = -arg;
		}

		if ( unsig != '-' ) {

			unsig = 0;
			if ( stat->flag & FLAG_PLUS ) unsig = '+';
			if ( stat->flag & FLAG_BLNK ) unsig = ' ';
		}
	}

	// The variable unsig now contains the signum character to be prepended,
	// or it is 0, if no signum character is present.
	// Convert the number itself and get the length of the result.
	// The length of the result will be stored in 'radix'.

	if ( radix == 10 )

		radix = ulltostr10( stat->buff, arg );
	else
		radix = ulltostr2n( stat->buff, arg, radix, upper );

	if ( stat->buff[ 0 ] == '0' ) {

		// The number was 0.

		if ( conv == 'p'  ) {

			// Special case: if pointer conversion is requested and the
			// pointer is NULL, then we must print the string "(nil)".

			stat->buff[ 0 ] = '(';
			stat->buff[ 1 ] = 'n';
			stat->buff[ 2 ] = 'i';
			stat->buff[ 3 ] = 'l';
			stat->buff[ 4 ] = ')';
			radix = 5;
			stat->flag &= ~( FLAG_ALTF | FLAG_ZERO );
			stat->prec  = -1;
		}
		else if ( stat->prec == 0 ) {

			// Other special case: if the number is zero and precision is
			// explicitely set to zero, then nothing should be printed.

			radix = 0;
			unsig = 0;
		}
	}

	// From now on a pointer is just a hex number

	if ( conv == 'p' ) conv = 'x';

	// Let's see if we need to create a prefix. The length of the prefix
	// will be stored in 'upper'.

	if ( unsig ) {

		pre[ 0 ] = unsig;
		unsig = 1;
	}

	if ( ( stat->flag & FLAG_ALTF ) && stat->buff[ 0 ] != '0' ) {

		pre[ unsig ] = '0';

		if ( conv == 'o' && stat->prec <= radix ) {

			unsig++;
		}

		if ( conv == 'x' || conv == 'X' ) {

			pre[ unsig + 1 ] = conv;
			unsig += 2;
		}
	}

	// At this point 'pre' contains the prefix, stat->buff contains the number,
	// radix contains the length of the number, unsig contains the length
	// of the prefix. Upper and conv can be used freely.
	// Work out the length of the final number. Since we do not need
	// conv any more, we use that.

	conv = radix + unsig;

	// If precision was specified and it is larger than the length of the
	// number, we have to insert filler 0-s. We will use upper to store
	// the number of 0's to insert.

	upper = 0;

	if ( stat->prec > radix ) {

		upper = stat->prec - radix;
		conv += upper;
	}

	// If we have right alignment and zero fill is specified, we might
	// have to modify the zero insert accordingly.

	if ( stat->fldw > conv ) {

		// We have a field that's wider than what we have so far.

		if ( stat->flag & FLAG_ZERO ) {

			upper += stat->fldw - conv;
			post = 0;
			conv = 0;
		}
		else if ( stat->flag & FLAG_LEFT ) {

			post = stat->fldw - conv;
			conv = 0;
		}
		else {

			post = 0;
			conv = stat->fldw - conv;
		}
	}
	else {

		post = 0;
		conv = 0;
	}

	if ( conv  && ! _blc_print_spc( stat, conv ) ) return( TRUE );
	if ( unsig && ! _blc_print_out( stat, pre, unsig ) ) return( TRUE );
	if ( upper && ! _blc_print_zro( stat, upper ) ) return( TRUE );
	if ( radix && ! _blc_print_out( stat, stat->buff, radix ) ) return( TRUE );
	if ( post  && ! _blc_print_spc( stat, post ) ) return( TRUE );
	return( TRUE );
}

/*
*	Processes the 'n' conversion specifier
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		PrintNum( PRINTF *stat, int c )
{
void	*ptr;

	if ( c != 'n' ) return( FALSE );
	if ( ! ( ptr = va_arg( stat->args, void * ) ) ) return( TRUE );

	switch ( stat->size ) {

		case SIZE_CHAR:

			*((char *) ptr) = stat->nout;
			break;

		case SIZE_HALF:

			*((short *) ptr) = stat->nout;
			break;

		case SIZE_LONG:

			*((long *) ptr) = stat->nout;
			break;

		case SIZE_HUGE:

			*((long long *) ptr) = stat->nout;
			break;

		default:

			*((int *) ptr) = stat->nout;
			break;
	}

	return( TRUE );
}

/*
*	Prints a float
*	~~~~~~~~~~~~~~
*	This is simply not recognised
*/

static	int		PrintFlt( PRINTF *stat, int conv )
{
	(void) stat;
	(void) conv;
	return( 0 );
}

