// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package strconv

import "os"

type NumError struct {
	Num	string;
	Error	os.Error;
}

func (e *NumError) String() string {
	return "parsing " + e.Num + ": " + e.Error.String();
}


func computeIntsize() uint {
	siz := uint(8);
	for 1<<siz != 0 {
		siz *= 2;
	}
	return siz;
}

var IntSize = computeIntsize()

// Return the first number n such that n*base >= 1<<64.
func cutoff64(base int) uint64 {
	if base < 2 {
		return 0;
	}
	return (1<<64 - 1)/uint64(base) + 1;
}

// Btoui64 interprets a string s in an arbitrary base b (2 to 36)
// and returns the corresponding value n.  If b == 0, the base
// is taken from the string prefix: base 16 for "0x", base 8 for "0",
// and base 10 otherwise.
//
// The errors that Btoui64 returns have concrete type *NumError
// and include err.Num = s.  If s is empty or contains invalid
// digits, err.Error = os.EINVAL; if the value corresponding
// to s cannot be represented by a uint64, err.Error = os.ERANGE.
func Btoui64(s string, b int) (n uint64, err os.Error) {
	s0 := s;
	switch {
	case len(s) < 1:
		err = os.EINVAL;
		goto Error;

	case 2 <= b && b <= 36:
		// valid base; nothing to do

	case b == 0:
		// Look for octal, hex prefix.
		switch {
		case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
			b = 16;
			s = s[2:len(s)];
			if len(s) < 1 {
				err = os.EINVAL;
				goto Error;
			}
		case s[0] == '0':
			b = 8;
		default:
			b = 10;
		}

	default:
		err = os.ErrorString("invalid base " + Itoa(b));
		goto Error;
	}

	n = 0;
	cutoff := cutoff64(b);

	for i := 0; i < len(s); i++ {
		var v byte;
		switch {
		case '0' <= s[i] && s[i] <= '9':
			v = s[i]-'0';
		case 'a' <= s[i] && s[i] <= 'z':
			v = s[i]-'a'+10;
		case 'A' <= s[i] && s[i] <= 'Z':
			v = s[i]-'A'+10;
		default:
			n = 0;
			err = os.EINVAL;
			goto Error;
		}
		if int(v) >= b {
			n = 0;
			err = os.EINVAL;
			goto Error;
		}

		if n >= cutoff {
			// n*b overflows
			n = 1<<64 - 1;
			err = os.ERANGE;
			goto Error;
		}
		n *= uint64(b);

		n1 := n+uint64(v);
		if n1 < n {
			// n+v overflows
			n = 1<<64 - 1;
			err = os.ERANGE;
			goto Error;
		}
		n = n1;
	}

	return n, nil;

Error:
	return n, &NumError{s0, err};
}

// Atoui64 interprets a string s as a decimal number and
// returns the corresponding value n.
//
// Atoui64 returns err == os.EINVAL if s is empty or contains invalid digits.
// It returns err == os.ERANGE if s cannot be represented by a uint64.
func Atoui64(s string) (n uint64, err os.Error) {
	return Btoui64(s, 10);
}

// Btoi64 is like Btoui64 but allows signed numbers and
// returns its result in an int64.
func Btoi64(s string, base int) (i int64, err os.Error) {
	// Empty string bad.
	if len(s) == 0 {
		return 0, &NumError{s, os.EINVAL};
	}

	// Pick off leading sign.
	s0 := s;
	neg := false;
	if s[0] == '+' {
		s = s[1:len(s)];
	} else if s[0] == '-' {
		neg = true;
		s = s[1:len(s)];
	}

	// Convert unsigned and check range.
	var un uint64;
	un, err = Btoui64(s, base);
	if err != nil && err.(*NumError).Error != os.ERANGE {
		err.(*NumError).Num = s0;
		return 0, err;
	}
	if !neg && un >= 1<<63 {
		return 1<<63 - 1, &NumError{s0, os.ERANGE};
	}
	if neg && un > 1<<63 {
		return -1 << 63, &NumError{s0, os.ERANGE};
	}
	n := int64(un);
	if neg {
		n = -n;
	}
	return n, nil;
}

// Atoi64 is like Atoui64 but allows signed numbers and
// returns its result in an int64.
func Atoi64(s string) (i int64, err os.Error) {
	return Btoi64(s, 10);
}


// Atoui is like Atoui64 but returns its result as a uint.
func Atoui(s string) (i uint, err os.Error) {
	i1, e1 := Atoui64(s);
	if e1 != nil && e1.(*NumError).Error != os.ERANGE {
		return 0, e1;
	}
	i = uint(i1);
	if uint64(i) != i1 {
		return ^uint(0), &NumError{s, os.ERANGE};
	}
	return i, nil;
}

// Atoi is like Atoi64 but returns its result as an int.
func Atoi(s string) (i int, err os.Error) {
	i1, e1 := Atoi64(s);
	if e1 != nil && e1.(*NumError).Error != os.ERANGE {
		return 0, e1;
	}
	i = int(i1);
	if int64(i) != i1 {
		if i1 < 0 {
			return -1 << (IntSize-1), &NumError{s, os.ERANGE};
		}
		return 1<<(IntSize-1) - 1, &NumError{s, os.ERANGE};
	}
	return i, nil;
}
