package vals

import (
	
	
	
	
	
)

// Design notes:
//
// The choice and relationship of number types in Elvish is closely modelled
// after R6RS's numerical tower (with the omission of complex types for now). In
// fact, there is a 1:1 correspondence between number types in Elvish and a
// typical R6RS implementation (the list below uses Chez Scheme's terminology;
// see https://www.scheme.com/csug8/numeric.html):
//
// int      : fixnum
// *big.Int : bignum
// *big.Rat : ratnum
// float64  : flonum
//
// Similar to Chez Scheme, *big.Int is only used for representing integers
// outside the range of int, and *big.Rat is only used for representing
// non-integer rationals. Furthermore, *big.Rat values are always in simplest
// form (this is guaranteed by the math/big library). As a consequence, each
// number in Elvish only has a single unique representation.
//
// Note that the only machine-native integer type included in the system is int.
// This is done primarily for the uniqueness of representation for each number,
// but also for simplicity - the vast majority of Go functions that take
// machine-native integers take int. When there is a genuine need to work with
// other machine-native integer types, you may have to manually convert from and
// to *big.Int and check for the relevant range of integers.

// Num is a stand-in type for int, *big.Int, *big.Rat or float64. This type
// doesn't offer type safety, but is useful as a marker; for example, it is
// respected when parsing function arguments.
type Num interface{}

// NumSlice is a stand-in type for []int, []*big.Int, []*big.Rat or []float64.
// This type doesn't offer type safety, but is useful as a marker.
type NumSlice interface{}

// ParseNum parses a string into a suitable number type. If the string does not
// represent a valid number, it returns nil.
func ( string) Num {
	if strings.ContainsRune(, '/') {
		// Parse as big.Rat
		if ,  := new(big.Rat).SetString();  {
			return NormalizeBigRat()
		}
		return nil
	}
	// Try parsing as big.Int
	if ,  := new(big.Int).SetString(, 0);  {
		return NormalizeBigInt()
	}
	// Try parsing as float64
	if ,  := strconv.ParseFloat(, 64);  == nil {
		return 
	}
	return nil
}

// NumType represents a number type.
type NumType uint8

// Possible values for NumType, sorted in the order of implicit conversion
// (lower types can be implicitly converted to higher types).
const (
	Int NumType = iota
	BigInt
	BigRat
	Float64
)

// UnifyNums unifies the given slice of numbers into the same type, converting
// those with lower NumType to the higest NumType present in the slice. The typ
// argument can be used to force the minimum NumType.
func ( []Num,  NumType) NumSlice {
	for ,  := range  {
		if  := getNumType();  >  {
			 = 
		}
	}
	switch  {
	case Int:
		 := make([]int, len())
		for ,  := range  {
			[] = .(int)
		}
		return 
	case BigInt:
		 := make([]*big.Int, len())
		for ,  := range  {
			switch num := .(type) {
			case int:
				[] = big.NewInt(int64())
			case *big.Int:
				[] = 
			default:
				panic("unreachable")
			}
		}
		return 
	case BigRat:
		 := make([]*big.Rat, len())
		for ,  := range  {
			switch num := .(type) {
			case int:
				[] = big.NewRat(int64(), 1)
			case *big.Int:
				var  big.Rat
				.SetInt()
				[] = &
			case *big.Rat:
				[] = 
			default:
				panic("unreachable")
			}
		}
		return 
	case Float64:
		 := make([]float64, len())
		for ,  := range  {
			switch num := .(type) {
			case int:
				[] = float64()
			case *big.Int:
				if .IsInt64() {
					// Might fit in float64
					[] = float64(.Int64())
				} else {
					// Definitely won't fit in float64
					[] = math.Inf(.Sign())
				}
			case *big.Rat:
				[], _ = .Float64()
			case float64:
				[] = 
			default:
				panic("unreachable")
			}
		}
		return 
	default:
		panic("unreachable")
	}
}

func ( Num) NumType {
	switch .(type) {
	case int:
		return Int
	case *big.Int:
		return BigInt
	case *big.Rat:
		return BigRat
	case float64:
		return Float64
	default:
		panic("invalid num type " + fmt.Sprintf("%T", ))
	}
}

// NormalizeBigInt converts a big.Int to an int if it is within the range of
// int. Otherwise it returns n as is.
func ( *big.Int) Num {
	if ,  := getInt();  {
		return 
	}
	return 
}

// NormalizeBigRat converts a big.Rat to a big.Int (or an int if within the
// range) if its denominator is 1.
func ( *big.Rat) Num {
	if .IsInt() {
		 := .Num()
		if ,  := getInt();  {
			return 
		}
		return 
	}
	return 
}

func ( *big.Int) (int, bool) {
	// TODO: Use a more efficient implementation by examining z.Bits
	if .IsInt64() {
		 := .Int64()
		 := int()
		if int64() ==  {
			return , true
		}
	}
	return -1, false
}