/****************************************************************************/
/*	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 of 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.																	*/
/****************************************************************************/

#define	BLC_CTYPE_INLINE
#include <ctype.h>
#include <misc/multiply.h>
#include <blc_stdlib.h>

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

static	int		ConvSpace( BLC_S2I *stat, int next );
static	int		ConvWait0( BLC_S2I *stat, int next );
static	int		ConvSeen0( BLC_S2I *stat, int next );
static	int		ConvIsHex( BLC_S2I *stat, int next );
static	int		ConvDigit( BLC_S2I *stat, int next );

/****************************************************************************/
/*					Interface functions										*/
/****************************************************************************/

/*
*	Prepares a numeric conversion
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	This function initialises a BLC_S2I structure. After this function
*	returns, the upper level has to call the function pointed by the
*	"func" field of the structure, passing the address of the structure
*	as first parameter and the next character as second. If the function
*	returns 0, it means that it needs more character from the input. If
*	it returns 1 or 2, it means that the conversion is finished and the
*	number of input characters that have not been used is the returned
*	value. Note that 2 is only returned if the conversion is hexadecimal
*	and the string starts with 0x? where ? is not a hex digit. In that
*	case not only the character represented by the ? should be pushed
*	back but also the 'x'.
*
*	The convesion skips all leading whitespace, then processes the '+' or
*	'-' signum, if present. Then it converts the string to the number in the
*	given base. If the base is 16, then the number can start with an optional
*	0x or 0X. The conversion stops when a character that is not a digit in
*	the given base is reached. If the radix is set to 0, then if the number
*	starts with 0x or 0X then a hexadecimal conversion will be attempted, if
*	it starts with 0 then octal conversion will be attempted, otherwise a
*	base-10 conversion is attempted.
*
*	When the function returns, the "sign" field of the structure contains
*	either the '+' or '-' character if they were present in front of the
*	number, or 0 if no signum sign was present. The "fnum" field contains
*	one more than the number of digits processed after the signum, possible
*	radix prefix and leading zeroes, zero if the conversion failed. Thus,
*	-0x0 set this field to 1 (0 digits processed), 0x0012 will set it to 3
*	(2 useful digits) but -0x on its own will set it to 0, because the
*	conversion failed. If the conversion was successful, then the "hval"
*	and "lval" fields contain the upper and lower 32 bits of the 64-bit
*	unsigned value represented by the digit sequence.
*	If there was a numeric overflow during the conversion, then the "fovf"
*	field is set to non-zero. In that case the "hval" and "lval" fields
*	contain the numeric value of the digit sequence just before the next
*	digit caused it to overflow.
*
*	stat	Pointer to the conversion state structure
*	base	Conversion radix, 2-36 or 0.
*/

void	_blc_StrToInt( BLC_S2I *stat, int base )
{
	stat->hval = 0;
	stat->lval = 0;
	stat->base = base;
	stat->fneg = 0;
	stat->fovf = 0;
	stat->fnum = 0;
	stat->func = ConvSpace;
}

/****************************************************************************/
/*					Conversion state machine states							*/
/****************************************************************************/

/*
*	Initial state, skip spaces and wait for signum
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		ConvSpace( BLC_S2I *stat, int next )
{
	if ( isspace( next ) ) return( 0 );

	if ( next == '+' || next == '-' ) {

		stat->fneg = next;
		stat->func = ConvWait0;
		return( 0 );
	}

	return( ConvWait0( stat, next ) );
}

/*
*	Waiting for the first digit
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		ConvWait0( BLC_S2I *stat, int next )
{
	if ( next == '0' ) {

		stat->func = ConvSeen0;
		stat->fnum = 1;
		return( 0 );
	}
	else {

		if ( stat->base == 0 ) stat->base = 10;
		stat->func = ConvDigit;
		return( ConvDigit( stat, next ) );
	}
}

/*
*	Zero was seen
*	~~~~~~~~~~~~~
*/

static	int		ConvSeen0( BLC_S2I *stat, int next )
{
	if ( next == 'x' || next == 'X' ) {

		if ( stat->base == 0 ) stat->base = 16;

		if ( stat->base != 16 ) {

			return( 1 );
		}
		else {

			stat->func = ConvIsHex;
			return( 0 );
		}
	}

	if ( stat->base == 0 ) stat->base = 8;
	return( ConvDigit( stat, next ) );
}

/*
*	0x was seen, check if the next character is a hex digit
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		ConvIsHex( BLC_S2I *stat, int next )
{
	if ( ! isxdigit( next ) ) return( 2 );
	stat->func = ConvDigit;
	return( ConvDigit( stat, next ) );
}

/*
*	Process digits
*	~~~~~~~~~~~~~~
*/

static	int		ConvDigit( BLC_S2I *stat, int next )
{
unsigned long long	ltmp;
unsigned long		stmp;

	if ( isdigit( next ) ) {

		next = next - '0';
	}
	else if ( isalpha( next ) ) {

		next = toupper( next ) -'A' + 10;
	}
	else {

		return( 1 );
	}

	if ( next >= stat->base ) {

		// The next character is not a digit in the given base

		return( 1 );
	}
	else {

		// We have at least one valid character

		if ( ! stat->fnum ) stat->fnum = 1;
		if ( next || stat->fnum > 1 ) stat->fnum++;

		// If we are already overflown, just return

		if ( stat->fovf ) return( 0 );

		// Calculate the numeric value after the next digit, on 96 bits

		ltmp = umul32x32_64( stat->lval, stat->base ) + next;
		stmp = ltmp;
		ltmp = umul32x32_64( stat->hval, stat->base ) + ( ltmp >> 32 );

		if ( ltmp >> 32 ) {

			// The result would be more than 64 bits, we
			// have an overflow. We do not store the result.

			stat->fovf = 1;
		}
		else {

			// The new value still fits on 64 bits, store it.

			stat->lval = stmp;
			stat->hval = ltmp;
		}

		// Indicate that we are not done yet

		return( 0 );
	}
}

