/*
 * Copyright (C) 1994-2018 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * PBS Pro is free software. You can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * PBS Pro 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * For a copy of the commercial license terms and conditions,
 * go to: (http://www.pbspro.com/UserArea/agreement.html)
 * or contact the Altair Legal Department.
 *
 * Altair’s dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of PBS Pro and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair’s trademarks, including but not limited to "PBS™",
 * "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
 * trademark licensing policies.
 *
 */
/**
 * @file	strTouL.c
 *
 * @brief
 *	returns the unsigned Long value representing the string whose
 * 	first character is *nptr, when interpreted as an integer in base,
 *	base.
 */
#include <pbs_config.h>   /* the master config generated by configure */

#include <ctype.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>

#include "Long.h"
#include "Long_.h"

#undef strTouL

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

static unsigned x_val;
static const char letters[] = "abcdefghijklmnopqrstuvwxyz";
static const char Long_dig[] = LONG_DIG_VALUE;
static char table[UCHAR_MAX + 1];

/**
 * @brief
 *	returns the unsigned Long value representing the string whose
 * 	first character is *nptr, when interpreted as an integer in base,
 *	base.
 *
 * @param[in]   nptr - pointer to string to convert to u_Long
 * @param[in/out]   endptr -  If endptr is not NULL,the function stores
 *              the address of the first invalid character in *endptr.
 * @param[in]   base - If base is zero, the base of the integer is determined by the way the
 * 	        string starts.  The string is interpreted as decimal if the first
 * 	        character after leading white space and an optional sign is a digit
 * 	        between 1 and 9, inclusive.  The string is interpreted as octal if the
 * 	        first character after leading white space and an optional sign is the
 * 	        digit "0" and the next character is not an "X" (either upper or lower
 * 	        case).  The string is interpreted as hexidecimal if the first character
 * 	        after leading white space and an optional sign is the digit "0",
 * 	        followed by an "X" (either upper or lower case).
 *              If base is greater than 1 and less than the number of characters in the
 *	        Long_dig array, it represents the base in which the number will be
 *	        interpreted.  Characters for digits beyond 9 are represented by the
 *	        letters of the alphabet, either upper case or lower case.
 *
 * @return u_Long Returns the result of the conversion
 * @retval >= 0	The result of the conversion
 * @retval 0	FAILURE
 */
u_Long
strTouL(const char *nptr, char **endptr, int base)
{
	unsigned	digit;
	u_Long		limit = 0, value;
	enum {
		unknown1,
		unknown2,
		hex1,
		hex2,
		hex3,
		known,
		working,
		overflow
	}		state;

	if (table[(unsigned char)'1'] != 1) {
		int	i;		/* Initialize conversion table */

		(void)memset(table, CHAR_MAX, sizeof(table));
		for (i = (int)strlen(Long_dig) - 1; i >= 0; i--)
			table[(unsigned char)Long_dig[i]] = i;
		for (i = (int)strlen(letters) - 1; i >= 0; i--)
			table[(unsigned char)letters[i]] = i + 10;
		x_val = table[(unsigned char)'x'];
	}
	if (nptr == NULL) {
		if (endptr != NULL)
			*endptr = (char *)nptr;
		return (0);
	}
	if (base < 0 || base == 1 || (size_t)base > strlen(Long_dig)) {
		errno = EDOM;
		if (endptr != NULL)
			*endptr = (char *)nptr;
		return (0);
	}
	switch (base) {
		case 0:
			state = unknown1;
			break;
		case 16:
			state = hex1;
			break;
		default:
			state = known;
	}
	while (isspace(*nptr++));
	Long_neg = FALSE;
	switch (*--nptr) {
		case '-':
			Long_neg = TRUE;
		case '+':
			nptr++;
	}
	value = 0;
	while ((digit = table[(unsigned char)*nptr++]) != CHAR_MAX) {
		switch (state) {
			case unknown1:
				if (digit >= 10)
					goto done;
				if (digit == 0) {
					state = unknown2;
					break;
				}
				base = 10;
				state = working;
				limit = UlONG_MAX / 10;
				value = digit;
				break;
			case unknown2:
				if (digit >= 8) {
					if (digit != x_val)
						goto done;
					base = 16;
					state = hex3;
					break;
				}
				base = 8;
				state = working;
				limit = UlONG_MAX / 8;
				value = digit;
				break;
			case hex1:
				if (digit >= base)
					goto done;
				if (digit == 0) {
					state = hex2;
					break;
				}
				state = working;
				limit = UlONG_MAX / 16;
				value = digit;
				break;
			case hex2:
				if (digit == x_val) {
					state = hex3;
					break;
				}
			case hex3:
			case known:
				if (digit >= base)
					goto done;
				state = working;
				limit = UlONG_MAX / base;
				value = digit;
				break;
			case working:
				if (digit >= base)
					goto done;
				if (value < limit) {
					value = value * base + digit;
					break;
				}
				if (value > limit ||
					UlONG_MAX - (value *= base) < digit) {
					state = overflow;
					value = UlONG_MAX;
					errno = ERANGE;
					break;
				}
				value += digit;
				break;
			case overflow:
				if (digit >= base)
					goto done;
		}
	}
done:
	if (endptr != NULL) {
		if (state == hex3)
			nptr--;
		*endptr = (char *)--nptr;
	}
	if (Long_neg)
		errno = ERANGE;
	return value;
}
