// strtox.c
//
// String to number conversion
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//

#include "string.h"
#include "ctype.h"
#include "types.h"
#include <limits.h>

#define FL_UNSIGNED   1
#define FL_NEG        2
#define FL_OVERFLOW   4
#define FL_READDIGIT  8

static unsigned long strtoxl(const char *nptr, char **endptr, int ibase, int flags)
{
	const unsigned char *p;
	char c;
	unsigned long number;
	unsigned digval;
	unsigned long maxval;

	p = (const unsigned char *)nptr;
	number = 0;

	c = *p++;
	while(isspace(c))
		c = *p++;

	if(c == '-') {
		flags |= FL_NEG;
		c = *p++;
	} else if(c == '+') {
		c = *p++;
	}

	if(ibase < 0 || ibase == 1 || ibase > 36) {
		if(endptr)
			*endptr = (char *)nptr;
		return 0L;
	} else if(ibase == 0) {
		if(c != '0') {
			ibase = 10;
		} else if(*p == 'x' || *p == 'X') {
			ibase = 16;
		} else {
			ibase = 8;
		}
	}

	if(ibase == 16) {
		if(c == '0' && (*p == 'x' || *p == 'X')) {
			++p;
			c = *p++;
		}
	}

	maxval = ULONG_MAX / ibase;

	for(;;) {
		if(isdigit(c)) {
			digval = c - '0';
		} else if(isalpha(c)) {
			digval = toupper(c) - 'A' + 10;
		} else {
			break;
		}

		if(digval >= (unsigned)ibase)
			break;

		flags |= FL_READDIGIT;

		if(number < maxval || (number == maxval && (unsigned long)digval <= ULONG_MAX % ibase)) {
			number = number * ibase + digval;
		} else {
			flags |= FL_OVERFLOW;
		}

		c = *p++;
	}

	--p;

	if(!(flags & FL_READDIGIT)) {
		if(endptr)
			p = (const unsigned char*)nptr;
		number = 0;
	} else if((flags & FL_OVERFLOW) || (!(flags & FL_UNSIGNED) && (((flags & FL_NEG) && (number < LONG_MIN)) || (!(flags & FL_NEG) && (number > LONG_MAX))))) {
		if(flags & FL_UNSIGNED) {
			number = ULONG_MAX;
		} else if(flags & FL_NEG) {
			number = LONG_MIN;
		} else {
			number = LONG_MAX;
		}
	}

	if(endptr)
		*endptr = (char *)p;

	if(flags & FL_NEG)
		number = (unsigned long)(-(long)number);

	return number;
}

long strtol(const char *nptr, char **endptr, int ibase)
{
	return (long)strtoxl(nptr, endptr, ibase, 0);
}

unsigned long strtoul(const char *nptr, char **endptr, int ibase)
{
	return strtoxl(nptr, endptr, ibase, FL_UNSIGNED);
}

long atol(const char *nptr)
{
	int c;
	int sign;
	long total;
	const unsigned char *p = (const unsigned char *)nptr;

	while(isspace(*p))
		++p;

	c = *p++;
	sign = c;
	if(c == '-' || c == '+')
		c = *p++;

	total = 0;
	while(isdigit(c)) {
		total = 10 * total + (c - '0');
		c = *p++;
	}

	if(sign == '-') {
		return -total;
	} else {
		return total;
	}
}

int atoi(const char *nptr)
{
	return (int)atol(nptr);
}

s64 strtoll(const char *nptr, char **endptr, int ibase)
{
	const unsigned char *p = (const unsigned char *)nptr;
	int flags = 0;
	s64 number = 0;
	s64 digval;
	s64 maxval;
	int c;

	c = *p++;
	while(isspace(c))
		c = *p++;

	if(c == '-') {
		flags |= FL_NEG;
		c = *p++;
	} else if(c == '+') {
		c = *p++;
	}

	if(ibase < 0 || ibase == 1 || ibase > 36) {
		if(endptr)
			*endptr = (char *)nptr;
		return 0;
	} else if(ibase == 0) {
		if(c != '0') {
			ibase = 10;
		} else if(*p == 'x' || *p == 'X') {
			ibase = 16;
		} else {
			ibase = 8;
		}
	}

	if(ibase == 16) {
		if(c == '0' && (*p == 'x' || *p == 'X')) {
			++p;
			c = *p++;
		}
	}

	maxval = 9223372036854775807 / ibase;

	for(;;) {
		if(isdigit(c)) {
			digval = c - '0';
		} else if(isalpha(c)) {
			digval = toupper(c) - 'A' + 10;
		} else {
			break;
		}

		if(digval >= (s64) ibase)
			break;

		flags |= FL_READDIGIT;

		if(number < maxval || (number == maxval && digval <= 9223372036854775807 % ibase)) {
			number = number * ibase + digval;
		} else {
			flags |= FL_OVERFLOW;
		}

		c = *p++;
	}

	--p;

	if(!(flags & FL_READDIGIT)) {
		if(endptr)
			p = (const unsigned char*)nptr;
		number = 0;
	} else if(flags & FL_OVERFLOW) {
		if(flags & FL_NEG) {
			number = -9223372036854775807-1;
		} else {
			number = 9223372036854775807;
		}
	}

	if(endptr)
		*endptr = (char *)p;
	if(flags & FL_NEG)
		number = -number;

	return number;
}
