/****************************************************************************/
/*	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	Conversion( SCANF *stat );
static int	Float( SCANF *stat );
static int	Consumed( SCANF *stat );
static int	Percent(  SCANF *stat );
static int	Integer( SCANF *stat, int sign, int base );
static int	Pointer( SCANF *stat );

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

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

/**
*	Processing formatted input core without floating point conversions.
*
*	This function has the exact same semantics and behaviour as the scnan_flt()
*	function, with the following exceptions:
*
*	- The 'a', 'e', 'f', and 'g' specifiers are not recognised.
*	  If such specifiers occur in the format string, they will
*	  trigger a format error and the function will stop.
*
*	- The l size qualifier for 'c', 's' and '[' conversions is ignored and the
*	  passed wide character pointer is treated as a normal char pointer.
*
*	Due to the omission of the floating point, this function is much smaller
*	than using scan_flt(). For details on the format string and usage
*	examples see scan_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	get		The function that reads the next character from the input.
*	@param	ung		The function to call when a character must be pushed back.
*	@param	par		This is the parameter that is passed to \p get and \p ung.
*	@param	m2w		An unused parameter. It is only here to make the prototypes
*					of scan_flt() and scan_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 specifiers successfully matched. The
*					%n specifier is <b>not</b> included in the count.
*					EOF is returned if the input was exhausted or there
*					was a character mismatch before the first conversion,
*					0 is returned if the first conversion fails and a negative
*					error code, other than EOF, is returned if the \p get
*					function reported a read error from the input.
*/

int		scan_int( const char *fmt, va_list arg, BLC_FMT_GET get,
				  BLC_FMT_UNG ung, void **par,
				  int(*m2w)(wchar_t**,const char*,size_t) )
{
SCANF stat;

	(void) m2w;

	stat.form = (unsigned char *) fmt;
	stat.cbuf = EOF;
	stat.args = arg;
	stat.fget = get;
	stat.fung = ung;
	stat.parm = par;
	stat.real = Float;
	stat.chrs = _blc_scan_chars;
	stat.mtow = NULL;
	return( _blc_scan( &stat ) );
}

/** @} */

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

/*
*	The actual scan function
*	~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_scan( SCANF *stat )
{
int		c;

	stat->cons = 0;
	stat->conv = 0;

	while ( ( c = *stat->form++ ) ) {

		if ( isspace( c ) ) {

			// Explicit command to skip whitespace

			if ( ! _blc_scan_space( stat ) ) break;
			continue;
		}

		if ( c != '%' ) {

			// A character that must explicitely match

			if ( _blc_scan_get( stat ) != c )

				break;
			else
				continue;
		}

		// It is a conversion, process it

		if ( ! Conversion( stat ) ) break;
		if ( stat->conv < 0 ) break;
	}

	if ( stat->cbuf != EOF ) (*stat->fung)( stat->cbuf, stat->parm );
	return( stat->conv );
}

/*
*	Reads the next input character
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_scan_get( SCANF *stat )
{
int		c;

	if ( ( c = stat->cbuf ) == EOF ) {

		if ( ( c = (stat->fget)( stat->parm ) ) < 0 ) {

			if ( c != EOF ) stat->conv = c;
			return( c );
		}
	}

	stat->cbuf = EOF;
	stat->cons++;
	return( c );
}

/*
*	Pushes a character back to the input
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

void	_blc_scan_ung( SCANF *stat, int c )
{
	stat->cbuf = c;
	stat->cons--;
}

/*
*	Numeric conversion
*	~~~~~~~~~~~~~~~~~~
*	It is just a loop around the string to integer parser automaton.
*/

int		_blc_scan_digits( SCANF *stat, BLC_S2I *conv )
{
int		c;

	for ( ;; ) {

		if ( ! stat->fldw-- ) {

			// We ran out of field width. Finish the conversion

			(*conv->func)( conv, 0 );
			break;
		}

		if ( ( c = _blc_scan_get( stat ) ) < 0 ) {

			// End of the input stream, or read error. The end of the
			// input is OK, so we have to check if it was EOF or not

			if ( c != EOF ) return( 0 );

			// It was an EOF, we're done

			(*conv->func)( conv, 0 );
			break;
		}

		// Pass the character to the processing core

		if ( (*conv->func)( conv, c ) > 0 ) {

			// The conversion has finished, the last character
			// was not used.

			_blc_scan_ung( stat, c );
			break;
		}
	}

	return( conv->fnum );
}

/*
*	Skips whitespace on the input
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_scan_space( SCANF *stat )
{
int		c;

	for ( ;; ) {

		if ( ( c = _blc_scan_get( stat ) ) < 0 ) return( 0 );
		if ( ! isspace( c ) ) break;
	}

	_blc_scan_ung( stat, c );
	return( 1 );
}

/*
*	Process character, character set or string conversions (no wide chars)
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_scan_chars( SCANF *stat )
{
char		*ptr;
int			chr, eof;
unsigned	set[ 8 ];

	ptr = stat->supp ? NULL : va_arg( stat->args, void * );

	// Process field width and leading whitespace and whatever
	// else is needed.

	switch ( stat->type ) {

		case 'c':

			if ( stat->fldw < 0 ) stat->fldw = 1;
			break;

		case '[':

			if ( stat->fldw < 0 ) stat->fldw = 1;
			if ( ! _blc_scan_cset( stat, set ) ) return( 0 );
			break;

		case 's':

			if ( stat->fldw < 0 ) stat->fldw = ( 1u << 31 ) - 1;
			if ( ! _blc_scan_space( stat ) ) return( 0 );
	}

	for ( eof = 1 ; stat->fldw-- ; eof = 0 ) {

		// Fetch the first byte of the sequence and see how many
		// bytes do we need for the conversion

		chr = _blc_scan_get( stat );

		if ( chr == EOF ) {

			// Input was exhausted, the conversion is successful if we've
			// processed at least one character

			if ( eof )

				return( 0 );
			else
				break;
		}

		if ( chr < 0 ) return( 0 );

		// Test if it the character is acceptable according
		// to the conversion's criteria.

		switch ( stat->type ) {

			case 'c':

					if ( ptr ) *ptr++ = chr;
					continue;

			case 's':

				if ( ! isspace( chr ) ) {

					if ( ptr ) *ptr++ = chr;
					continue;
				}

				break;

			case '[':

				if ( ( set[ INDX( chr ) ] & BPOS( chr ) ) ) {

					if ( ptr ) *ptr++ = chr;
					continue;
				}

				break;

			case ']':

				if ( ! ( set[ INDX( chr ) ] & BPOS( chr ) ) ) {

					if ( ptr ) *ptr++ = chr;
					continue;
				}

				break;
		}

		// The character failed the test, push it back
		// and report completion.

		_blc_scan_ung( stat, chr );
		break;
	}

	// The conversion was successful. If the conversion
	// was 's', then we must terminate the buffer.

	if ( stat->type == 's' && ptr ) *ptr = 0;
	if ( ptr ) stat->conv++;
	return( 1 );
}

/*
*	Processes a character set definition
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		_blc_scan_cset( SCANF *stat, unsigned *cset )
{
int		beg, lch;

	cset[ 0 ] = cset[ 1 ] = cset[ 2 ] = cset[ 3 ] = 0;
	cset[ 4 ] = cset[ 5 ] = cset[ 6 ] = cset[ 7 ] = 0;
	beg = 0;
	lch = 0;

	// First, we have to find the set
	// The first or first two characters are special. If the
	// first character is ']', then it means that it is in the
	// set. If the first character is '^' then the set is inverted,
	// in that case it is the second character that should be checked
	// for ']'.

	if ( *stat->form == '^' ) {

		stat->type = ']';
		stat->form++;
	}

	if ( *stat->form == ']' ) {

		lch = ']';
		cset[ INDX( ']' ) ] |= BPOS( ']' );
		stat->form++;
	}

	for ( ;; ) {

		if ( ! *stat->form ) {

			// Premature end of the format string.

			return( 0 );
		}

		if ( *stat->form == ']' ) {

			// End of the format string. If the previous char was
			// the '-' then we have to set it

			if ( lch == '-' ) cset[ INDX( '-' ) ] |= BPOS( '-' );
			stat->form++;
			break;
		}

		if ( lch == '-' && beg ) {

			// The current character is an end of set character

			if ( *stat->form > beg ) {

				lch = *stat->form++;
			}
			else {

				lch = beg;
				beg = *stat->form++;
			}

			while ( beg <= lch ) {

				cset[ INDX( beg ) ] |= BPOS( beg );
				beg++;
			}

			beg = 0;
			lch = 0;
			continue;
		}

		if ( *stat->form == '-' ) {

			// Range specification, keep collecting data

			beg = lch;
			lch = '-';
			stat->form++;
			continue;
		}

		// Simple case, just add the character to the set

		lch = *stat->form++;
		cset[ INDX( lch ) ] |= BPOS( lch );
	}

	// Indicate success

	return( 1 );
}

/****************************************************************************/
/*					Local functions 										*/
/****************************************************************************/

/*
*	Execute a single conversion
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int	Conversion( SCANF *stat )
{
int		c;

	// There was a '%', process the rest

	stat->supp = 0;
	stat->fldw = -1;
	stat->size = SIZE_NONE;

	// Process the assignment suppression

	if ( ( c = *stat->form ) == '*' ) {

		// Suppression required

		stat->supp = 1;
		c = *++stat->form;
	}

	// Process the field width

	if ( c == '$' ) {

		// It is our extension, field width specification by argument.

		c = *++stat->form;
		stat->fldw = va_arg( stat->args, int );
	}
	else if ( isdigit( c ) ) {

		stat->fldw = c - '0';

		while ( isdigit( ( c = *++stat->form ) ) ) {

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

	// Process the size modifiers

	switch ( c ) {

		case 'l':

			stat->size  = SIZE_LONG;

			if ( stat->form[ 1 ] == 'l' ) {

				stat->form++;
				stat->size  = SIZE_HUGE;
			}

			break;

		case 'h':

			stat->size  = SIZE_HALF;

			if ( stat->form[ 1 ] == 'h' ) {

				stat->form++;
				stat->size  = SIZE_CHAR;
			}

			break;

		case 'L':
		case 'q':
		case 'j':

			stat->size = SIZE_HUGE;
			break;

		case 't':
		case 'z':

			stat->size = SIZE_LONG;
			break;

		default:

			stat->form--;
			break;
	}

	// Process the actual conversion

	c = *++stat->form;
	stat->form++;
	stat->type = c;

	switch ( c ) {

		case 'a':
		case 'e':
		case 'f':
		case 'g':	return( (*stat->real)( stat ) );
		case 's':
		case 'c':
		case '[':	return( (*stat->chrs)( stat ) );
		case 'x':
		case 'X':	return( Integer( stat, 0, 16 ) );
		case 'o':	return( Integer( stat, 0,  8 ) );
		case 'u':	return( Integer( stat, 0, 10 ) );
		case 'd':	return( Integer( stat, 1, 10 ) );
		case 'i':	return( Integer( stat, 1,  0 ) );
		case 'p':	return( Pointer( stat ) );
		case 'n':	return( Consumed( stat ) );
		case '%':	return( Percent( stat ) );
	}

	// We could not match the format specifier, it is an error.

	return( 0 );
}

/*
*	Matches a single '%' on the input
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int	Percent(  SCANF *stat )
{
	return( _blc_scan_get( stat ) == '%' );
}
/*
*	Store the number of characters consumed
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int	Consumed( SCANF *stat )
{
void	*ptr;

	if ( stat->supp ) return( 1 );
	ptr = va_arg( stat->args, void * );

	switch ( stat->size ) {

		case SIZE_HUGE:	*((long long *)	ptr ) = stat->cons;	break;
		case SIZE_LONG:	*((long *)		ptr ) = stat->cons;	break;
		case SIZE_HALF:	*((short *)		ptr ) = stat->cons;	break;
		case SIZE_CHAR:	*((char *)		ptr ) = stat->cons;	break;
		default:		*((int *)		ptr ) = stat->cons;	break;
	}

	return( 1 );
}

/*
*	Integer conversions
*	~~~~~~~~~~~~~~~~~~~
*/

static int	Integer( SCANF *stat, int sign, int base )
{
void		*ptr;
int			c;
BLC_S2I		conv;

	if ( ! _blc_scan_space( stat ) ) return( 0 );
	ptr = stat->supp ? NULL : va_arg( stat->args, void * );
	if ( ! stat->fldw ) return( 0 );
	if ( stat->fldw < 0 ) stat->fldw = ( 1u << 31 ) - 1;
	_blc_StrToInt( &conv, base );

	if ( ! sign ) {

		// Unsigned conversion, check that the
		// next character is not a '-'.

		if ( ( c = _blc_scan_get( stat ) ) < 0 ) return( 0 );
		_blc_scan_ung( stat, c );

		if ( c == '-' ) return( 0 );
	}

	// Read the characters until we run out of them or
	// the conversion core says that it has finished.

	if ( ! _blc_scan_digits( stat, &conv ) ) return( 0 );

	// The rest is only important if we have to store the result

	if ( ptr ) {

		// We don't check for overflows, as we can't do anything with them
		// anyway. We have to process the signum, though.

		if ( conv.fneg == '-' ) {

			// We have to negate the number. Since the 64 bit number is stored
			// in 2 32-bit halves, we do the negation step by step.

			conv.hval = ~conv.hval;

			if ( conv.lval )

				conv.lval = ~conv.lval + 1;
			else
				conv.hval++;
		}

		// Now we store the result, depending on the width

		switch ( stat->size ) {

			case SIZE_HUGE:

				*((long long *)	ptr) = ((long long)conv.hval << 32) | conv.lval;
				break;

			case SIZE_HALF:

				*((short *) ptr) = conv.lval;
				break;

			case SIZE_CHAR:

				*((char *) ptr) = conv.lval;
				break;

			default:

				*((int *) ptr) = conv.lval;
				break;
		}

		stat->conv++;
	}

	return( 1 );
}

/*
*	Pointer conversion
*	~~~~~~~~~~~~~~~~~~
*/

static int	Pointer( SCANF *stat )
{
int		c, i;

	if ( ! _blc_scan_space( stat ) ) return( 0 );

	if ( ( c = _blc_scan_get( stat ) ) != '(' ) {

		// We have to process it as a hex number

		stat->size = SIZE_NONE;
		_blc_scan_ung( stat, c );
		return( Integer( stat, 0, 16 ) );
	}

	// We saw a '(', so now we scan for the rest of "(nil)"

	for ( i = 0 ; i < 4 ; i++ ) {

		if ( tolower( ( c = _blc_scan_get( stat ) ) ) != i["nil)"] ) {

			// Failed to match

			_blc_scan_ung( stat, c );
			return( 0 );
		}
	}

	if ( ! stat->supp ) {

		*va_arg( stat->args, void ** ) = NULL;
		stat->conv++;
	}

	return( 1 );
}

/*
*	Float conversion, this instance always fails
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int	Float( SCANF *stat )
{
	stat->conv = EOF;
	return( 0 );
}

