#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>

#define DIVLIMIT		(UINTMAX_MAX / 10)
#define MODLIMIT		(UINTMAX_MAX % 10)

static int
digit_value(int c, int base)
{

	assert('Z' - 'A' == 25);
	assert('z' - 'a' == 25);

	if ('0' <= c && c <= '9' && c - '0' < base)
		return (c - '0');

	if ('A' <= c && c <= 'Z' && 10 + (c - 'A') < base)
		return 10 + (c - 'A');

	if ('a' <= c && c <= 'z' && 10 + (c - 'a') < base)
		return 10 + (c - 'a');

	return -1;
}

int
strtounum(const char *s, int base, uintmax_t *result, size_t *consumed)
{
	uintmax_t n;
	size_t con;
	int digit;

	assert(s != NULL);
	assert(2 <= base && base <= 36);

	con = 0;
	while (*s != '\0' && isspace((unsigned char)(*s))) {
		s++;
		con++;
	}

	if (*s == '\0') {
		errno = EINVAL;
		return -1;
	}

	n = 0;
	for (n = 0; (digit = digit_value(*s, base)) != -1; s++, con++) {
		if (n > DIVLIMIT || (n == DIVLIMIT && (unsigned int)digit > MODLIMIT)) {
			errno = ERANGE;
			return -1;
		}
		n = base * n + digit;
	}

	if (*s != '\0') {
		errno = EINVAL;
		return -1;
	}

	if (result != NULL)
		*result = n;
	if (consumed != NULL)
		*consumed = con;
	return 0;
}

#if defined(TEST_STRTOUNUM)
int
main(void)
{
	int result;
	uintmax_t n;
	size_t con;

	assert(CHAR_BIT == 8);

	result = strtounum("1234567", 10, &n, &con);
	assert(result == 0);
	assert(n == 1234567UL);
	assert(con == 7);

	result = strtounum("12345a", 10, &n, &con);
	assert(result == -1);
	assert(errno == EINVAL);

	result = strtounum("abcd", 36, &n, &con);
	assert(result == 0);
	assert(n == ((((UINTMAX_C(0) + 10) * 36 + 11) * 36 + 12) * 36 + 13));
	assert(con == 4);

	result = strtounum("1100101", 2, &n, &con);
	assert(result == 0);
	assert(n == 0x65);
	assert(con == 7);

	result = strtounum("012", 2, &n, &con);
	assert(result == -1);
	assert(errno == EINVAL);

	if (sizeof(uintmax_t) == 8) {
		/* 2^64 - 1 */
		result = strtounum("18446744073709551615", 10, &n, &con);
		assert(result == 0);
		assert(n == UINTMAX_MAX);
		assert(con == 20);

		/* 2^64 */
		result = strtounum("18446744073709551616", 10, &n, &con);
		assert(result == -1);
		assert(errno == ERANGE);

		/* A much too big number */
		result = strtounum("1111111111111111111111111", 10, &n, &con);
		assert(result == -1);
		assert(errno == ERANGE);

	} else {
		assert(!"Unsupported bit width of type uintmax_t.");
	}

	return 0;
}
#endif
