package eval

import (
	"math"
	"math/rand"

	"github.com/elves/elvish/pkg/eval/vals"
)

// Numerical operations.

func init() {
	addBuiltinFns(map[string]interface{}{
		// Constructor
		"float64": toFloat64,

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

		// Arithmetics
		"+": plus,
		"-": minus,
		"*": times,
		"/": slash,
		"^": math.Pow,
		"%": mod,

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

func toFloat64(f float64) float64 {
	return f
}

func lt(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] < nums[i+1]) {
			return false
		}
	}
	return true
}

func le(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] <= nums[i+1]) {
			return false
		}
	}
	return true
}

func eqNum(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] == nums[i+1]) {
			return false
		}
	}
	return true
}

func ne(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] != nums[i+1]) {
			return false
		}
	}
	return true
}

func gt(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] > nums[i+1]) {
			return false
		}
	}
	return true
}

func ge(nums ...float64) bool {
	for i := 0; i < len(nums)-1; i++ {
		if !(nums[i] >= nums[i+1]) {
			return false
		}
	}
	return true
}

func plus(nums ...float64) float64 {
	sum := 0.0
	for _, f := range nums {
		sum += f
	}
	return sum
}

func minus(sum float64, nums ...float64) float64 {
	if len(nums) == 0 {
		// Unary -
		return -sum
	}
	for _, f := range nums {
		sum -= f
	}
	return sum
}

func times(nums ...float64) float64 {
	prod := 1.0
	for _, f := range nums {
		prod *= f
	}
	return prod
}

func slash(fm *Frame, args ...float64) error {
	if len(args) == 0 {
		// cd /
		return fm.Chdir("/")
	}
	// Division
	divide(fm, args[0], args[1:]...)
	return nil
}

func divide(fm *Frame, prod float64, nums ...float64) {
	out := fm.ports[1].Chan
	for _, f := range nums {
		prod /= f
	}
	out <- vals.FromGo(prod)
}

func mod(a, b int) (int, error) {
	if b == 0 {
		return 0, ErrArgs
	}
	return a % b, nil
}

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