package eval

import (
	
	
	

	
	
)

// Numerical operations.

//elvdoc:fn rand
//
// ```elvish
// rand
// ```
//
// Output a pseudo-random number in the interval [0, 1). Example:
//
// ```elvish-transcript
// ~> rand
// ▶ 0.17843564133528436
// ```

func () {
	addBuiltinFns(map[string]interface{}{
		// Constructor
		"float64":   toFloat64,
		"num":       num,
		"exact-num": exactNum,

		// Comparison
		"<":  lt,
		"<=": le,
		"==": eqNum,
		"!=": ne,
		">":  gt,
		">=": ge,

		// Arithmetic
		"+": add,
		"-": sub,
		"*": mul,
		// Also handles cd /
		"/": slash,
		"%": rem,

		// Random
		"rand":    rand.Float64,
		"randint": randint,
	})
}

//elvdoc:fn num
//
// ```elvish
// num $string-or-number
// ```
//
// Constructs a [typed number](./language.html#number).
//
// If the argument is a string, this command outputs the typed number the
// argument represents, or raises an exception if the argument is not a valid
// representation of a number. If the argument is already a typed number, this
// command outputs it as is.
//
// This command is usually not needed for working with numbers; see the
// discussion of [numerical commands](#numerical-commands).
//
// Examples:
//
// ```elvish-transcript
// ~> num 10
// ▶ (num 10)
// ~> num 0x10
// ▶ (num 16)
// ~> num 1/12
// ▶ (num 1/12)
// ~> num 3.14
// ▶ (num 3.14)
// ~> num (num 10)
// ▶ (num 10)
// ```

func ( vals.Num) vals.Num {
	// Conversion is actually handled in vals/conversion.go.
	return 
}

//elvdoc:fn exact-num
//
// ```elvish
// exact-num $string-or-number
// ```
//
// Coerces the argument to an exact number. If the argument is infinity or NaN,
// an exception is thrown.
//
// If the argument is a string, it is converted to a typed number first. If the
// argument is already an exact number, it is returned as is.
//
// Examples:
//
// ```elvish-transcript
// ~> exact-num (num 0.125)
// ▶ (num 1/8)
// ~> exact-num 0.125
// ▶ (num 1/8)
// ~> exact-num (num 1)
// ▶ (num 1)
// ```
//
// Beware that seemingly simple fractions that can't be represented precisely in
// binary can result in the denominator being a very large power of 2:
//
// ```elvish-transcript
// ~> exact-num 0.1
// ▶ (num 3602879701896397/36028797018963968)
// ```

func ( vals.Num) (vals.Num, error) {
	if ,  := .(float64);  {
		 := new(big.Rat).SetFloat64()
		if  == nil {
			return nil, errs.BadValue{What: "argument here",
				Valid: "finite float", Actual: vals.ToString()}
		}
		return , nil
	}
	return , nil
}

//elvdoc:fn float64
//
// ```elvish
// float64 $string-or-number
// ```
//
// Constructs a floating-point number.
//
// This command is deprecated; use [`num`](#num) instead.

func ( float64) float64 {
	return 
}

//elvdoc:fn &lt; &lt;= == != &gt; &gt;= {#num-cmp}
//
// ```elvish
// <  $number... # less
// <= $number... # less or equal
// == $number... # equal
// != $number... # not equal
// >  $number... # greater
// >= $number... # greater or equal
// ```
//
// Number comparisons. All of them accept an arbitrary number of arguments:
//
// 1.  When given fewer than two arguments, all output `$true`.
//
// 2.  When given two arguments, output whether the two arguments satisfy the named
// relationship.
//
// 3.  When given more than two arguments, output whether every adjacent pair of
// numbers satisfy the named relationship.
//
// Examples:
//
// ```elvish-transcript
// ~> == 3 3.0
// ▶ $true
// ~> < 3 4
// ▶ $true
// ~> < 3 4 10
// ▶ $true
// ~> < 6 9 1
// ▶ $false
// ```
//
// As a consequence of rule 3, the `!=` command outputs `$true` as long as any
// _adjacent_ pair of numbers are not equal, even if some numbers that are not
// adjacent are equal:
//
// ```elvish-transcript
// ~> != 5 5 4
// ▶ $false
// ~> != 5 6 5
// ▶ $true
// ```

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  <  },
		func(,  *big.Int) bool { return .Cmp() < 0 },
		func(,  *big.Rat) bool { return .Cmp() < 0 },
		func(,  float64) bool { return  <  })

}

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  <=  },
		func(,  *big.Int) bool { return .Cmp() <= 0 },
		func(,  *big.Rat) bool { return .Cmp() <= 0 },
		func(,  float64) bool { return  <=  })
}

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  ==  },
		func(,  *big.Int) bool { return .Cmp() == 0 },
		func(,  *big.Rat) bool { return .Cmp() == 0 },
		func(,  float64) bool { return  ==  })
}

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  !=  },
		func(,  *big.Int) bool { return .Cmp() != 0 },
		func(,  *big.Rat) bool { return .Cmp() != 0 },
		func(,  float64) bool { return  !=  })
}

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  >  },
		func(,  *big.Int) bool { return .Cmp() > 0 },
		func(,  *big.Rat) bool { return .Cmp() > 0 },
		func(,  float64) bool { return  >  })
}

func ( ...vals.Num) bool {
	return chainCompare(,
		func(,  int) bool { return  >=  },
		func(,  *big.Int) bool { return .Cmp() >= 0 },
		func(,  *big.Rat) bool { return .Cmp() >= 0 },
		func(,  float64) bool { return  >=  })
}

func ( []vals.Num,
	 func(,  int) bool,  func(,  *big.Int) bool,
	 func(,  *big.Rat) bool,  func(,  float64) bool) bool {

	for  := 0;  < len()-1; ++ {
		var  bool
		switch pair := vals.UnifyNums([:+2], 0).(type) {
		case []int:
			 = ([0], [1])
		case []*big.Int:
			 = ([0], [1])
		case []*big.Rat:
			 = ([0], [1])
		case []float64:
			 = ([0], [1])
		}
		if ! {
			return false
		}
	}
	return true
}

//elvdoc:fn + {#add}
//
// ```elvish
// + $num...
// ```
//
// Outputs the sum of all arguments, or 0 when there are no arguments.
//
// This command is [exactness-preserving](#exactness-preserving).
//
// Examples:
//
// ```elvish-transcript
// ~> + 5 2 7
// ▶ (num 14)
// ~> + 1/2 1/3 1/4
// ▶ (num 13/12)
// ~> + 1/2 0.5
// ▶ (num 1.0)
// ```

func ( ...vals.Num) vals.Num {
	 := vals.UnifyNums(, vals.BigInt)
	switch nums := .(type) {
	case []*big.Int:
		 := big.NewInt(0)
		for ,  := range  {
			.Add(, )
		}
		return vals.NormalizeBigInt()
	case []*big.Rat:
		 := big.NewRat(0, 1)
		for ,  := range  {
			.Add(, )
		}
		return vals.NormalizeBigRat()
	case []float64:
		 := float64(0)
		for ,  := range  {
			 += 
		}
		return 
	default:
		panic("unreachable")
	}
}

//elvdoc:fn - {#sub}
//
// ```elvish
// - $x-num $y-num...
// ```
//
// Outputs the result of substracting from `$x-num` all the `$y-num`s, working
// from left to right. When no `$y-num` is given, outputs the negation of
// `$x-num` instead (in other words, `- $x-num` is equivalent to `- 0 $x-num`).
//
// This command is [exactness-preserving](#exactness-preserving).
//
// Examples:
//
// ```elvish-transcript
// ~> - 5
// ▶ (num -5)
// ~> - 5 2
// ▶ (num 3)
// ~> - 5 2 7
// ▶ (num -4)
// ~> - 1/2 1/3
// ▶ (num 1/6)
// ~> - 1/2 0.3
// ▶ (num 0.2)
// ~> - 10
// ▶ (num -10)
// ```

func ( ...vals.Num) (vals.Num, error) {
	if len() == 0 {
		return nil, errs.ArityMismatch{
			What:     "arguments here",
			ValidLow: 1, ValidHigh: -1, Actual: 0,
		}
	}

	 := vals.UnifyNums(, vals.BigInt)
	switch nums := .(type) {
	case []*big.Int:
		 := &big.Int{}
		if len() == 1 {
			.Neg([0])
			return , nil
		}
		.Set([0])
		for ,  := range [1:] {
			.Sub(, )
		}
		return , nil
	case []*big.Rat:
		 := &big.Rat{}
		if len() == 1 {
			.Neg([0])
			return , nil
		}
		.Set([0])
		for ,  := range [1:] {
			.Sub(, )
		}
		return , nil
	case []float64:
		if len() == 1 {
			return -[0], nil
		}
		 := [0]
		for ,  := range [1:] {
			 -= 
		}
		return , nil
	default:
		panic("unreachable")
	}
}

//elvdoc:fn * {#mul}
//
// ```elvish
// * $num...
// ```
//
// Outputs the product of all arguments, or 1 when there are no arguments.
//
// This command is [exactness-preserving](#exactness-preserving). Additionally,
// when any argument is exact 0 and no other argument is a floating-point
// infinity, the result is exact 0.
//
// Examples:
//
// ```elvish-transcript
// ~> * 2 5 7
// ▶ (num 70)
// ~> * 1/2 0.5
// ▶ (num 0.25)
// ~> * 0 0.5
// ▶ (num 0)
// ```

func ( ...vals.Num) vals.Num {
	 := false
	 := false
	for ,  := range  {
		if  == 0 {
			 = true
		}
		if ,  := .(float64);  && math.IsInf(, 0) {
			 = true
			break
		}
	}
	if  && ! {
		return 0
	}

	 := vals.UnifyNums(, vals.BigInt)
	switch nums := .(type) {
	case []*big.Int:
		 := big.NewInt(1)
		for ,  := range  {
			.Mul(, )
		}
		return vals.NormalizeBigInt()
	case []*big.Rat:
		 := big.NewRat(1, 1)
		for ,  := range  {
			.Mul(, )
		}
		return vals.NormalizeBigRat()
	case []float64:
		 := float64(1)
		for ,  := range  {
			 *= 
		}
		return 
	default:
		panic("unreachable")
	}
}

//elvdoc:fn / {#div}
//
// ```elvish
// / $x-num $y-num...
// ```
//
// Outputs the result of dividing `$x-num` with all the `$y-num`s, working from
// left to right. When no `$y-num` is given, outputs the reciprocal of `$x-num`
// instead (in other words, `/ $y-num` is equivalent to `/ 1 $y-num`).
//
// Dividing by exact 0 raises an exception. Dividing by inexact 0 results with
// either infinity or NaN according to floating-point semantics.
//
// This command is [exactness-preserving](#exactness-preserving). Additionally,
// when `$x-num` is exact 0 and no `$y-num` is exact 0, the result is exact 0.
//
// Examples:
//
// ```elvish-transcript
// ~> / 2
// ▶ (num 1/2)
// ~> / 2.0
// ▶ (num 0.5)
// ~> / 10 5
// ▶ (num 2)
// ~> / 2 5
// ▶ (num 2/5)
// ~> / 2 5 7
// ▶ (num 2/35)
// ~> / 0 1.0
// ▶ (num 0)
// ~> / 2 0
// Exception: bad value: divisor must be number other than exact 0, but is exact 0
// [tty 6], line 1: / 2 0
// ~> / 2 0.0
// ▶ (num +Inf)
// ```
//
// When given no argument, this command is equivalent to `cd /`, due to the
// implicit cd feature. (The implicit cd feature will probably change to avoid
// this oddity).

func ( *Frame,  ...vals.Num) error {
	if len() == 0 {
		// cd /
		return .Evaler.Chdir("/")
	}
	// Division
	,  := div(...)
	if  == nil {
		.OutputChan() <- vals.FromGo()
	}
	return 
}

// ErrDivideByZero is thrown when attempting to divide by zero.
var ErrDivideByZero = errs.BadValue{
	What: "divisor", Valid: "number other than exact 0", Actual: "exact 0"}

func ( ...vals.Num) (vals.Num, error) {
	for ,  := range [1:] {
		if  == 0 {
			return nil, ErrDivideByZero
		}
	}
	if [0] == 0 {
		return 0, nil
	}
	 := vals.UnifyNums(, vals.BigRat)
	switch nums := .(type) {
	case []*big.Rat:
		 := &big.Rat{}
		.Set([0])
		if len() == 1 {
			.Inv()
			return , nil
		}
		for ,  := range [1:] {
			.Quo(, )
		}
		return , nil
	case []float64:
		 := [0]
		if len() == 1 {
			return 1 / , nil
		}
		for ,  := range [1:] {
			 /= 
		}
		return , nil
	default:
		panic("unreachable")
	}
}

//elvdoc:fn % {#rem}
//
// ```elvish
// % $x $y
// ```
//
// Output the remainder after dividing `$x` by `$y`. The result has the same
// sign as `$x`. Both must be integers that can represented in a machine word
// (this limit may be lifted in future).
//
// Examples:
//
// ```elvish-transcript
// ~> % 10 3
// ▶ 1
// ~> % -10 3
// ▶ -1
// ~> % 10 -3
// ▶ 1
// ```

func (,  int) (int, error) {
	// TODO: Support other number types
	if  == 0 {
		return 0, ErrDivideByZero
	}
	return  % , nil
}

//elvdoc:fn randint
//
// ```elvish
// randint $low $high
// ```
//
// Output a pseudo-random integer in the interval [$low, $high). Example:
//
// ```elvish-transcript
// ~> # Emulate dice
// randint 1 7
// ▶ 6
// ```

func (,  int) (int, error) {
	if  >=  {
		return 0, ErrArgs
	}
	return  + rand.Intn(-), nil
}