package moremath

import (
	
)

// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/values.html#floating-point
const (
	// F32CanonicalNaNBits is the 32-bit float where payload's MSB equals 1 and others are all zero.
	F32CanonicalNaNBits = uint32(0x7fc0_0000)
	// F32CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F32CanonicalNaNBitsMask == F32CanonicalNaNBits"
	F32CanonicalNaNBitsMask = uint32(0x7fff_ffff)
	// F64CanonicalNaNBits is the 64-bit float where payload's MSB equals 1 and others are all zero.
	F64CanonicalNaNBits = uint64(0x7ff8_0000_0000_0000)
	// F64CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F64CanonicalNaNBitsMask == F64CanonicalNaNBits"
	F64CanonicalNaNBitsMask = uint64(0x7fff_ffff_ffff_ffff)
	// F32ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 32-bit arithmetic NaN values
	F32ArithmeticNaNPayloadMSB = uint32(0x0040_0000)
	// F32ExponentMask is used to extract the exponent of 32-bit floating point.
	F32ExponentMask = uint32(0x7f80_0000)
	// F32ArithmeticNaNBits is an example 32-bit arithmetic NaN.
	F32ArithmeticNaNBits = F32CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN.
	// F64ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 64-bit arithmetic NaN values
	F64ArithmeticNaNPayloadMSB = uint64(0x0008_0000_0000_0000)
	// F64ExponentMask is used to extract the exponent of 64-bit floating point.
	F64ExponentMask = uint64(0x7ff0_0000_0000_0000)
	// F64ArithmeticNaNBits is an example 64-bit arithmetic NaN.
	F64ArithmeticNaNBits = F64CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN.
)

// WasmCompatMin64 is the Wasm spec compatible variant of math.Min for 64-bit floating points.
func (,  float64) float64 {
	switch {
	case math.IsNaN() || math.IsNaN():
		return returnF64NaNBinOp(, )
	case math.IsInf(, -1) || math.IsInf(, -1):
		return math.Inf(-1)
	case  == 0 &&  == :
		if math.Signbit() {
			return 
		}
		return 
	}
	if  <  {
		return 
	}
	return 
}

// WasmCompatMin32 is the Wasm spec compatible variant of math.Min for 32-bit floating points.
func (,  float32) float32 {
	,  := float64(), float64()
	switch {
	case math.IsNaN() || math.IsNaN():
		return returnF32NaNBinOp(, )
	case math.IsInf(, -1) || math.IsInf(, -1):
		return float32(math.Inf(-1))
	case  == 0 &&  == :
		if math.Signbit() {
			return 
		}
		return 
	}
	if  <  {
		return 
	}
	return 
}

// WasmCompatMax64 is the Wasm spec compatible variant of math.Max for 64-bit floating points.
func (,  float64) float64 {
	switch {
	case math.IsNaN() || math.IsNaN():
		return returnF64NaNBinOp(, )
	case math.IsInf(, 1) || math.IsInf(, 1):
		return math.Inf(1)
	case  == 0 &&  == :
		if math.Signbit() {
			return 
		}
		return 
	}
	if  >  {
		return 
	}
	return 
}

// WasmCompatMax32 is the Wasm spec compatible variant of math.Max for 32-bit floating points.
func (,  float32) float32 {
	,  := float64(), float64()
	switch {
	case math.IsNaN() || math.IsNaN():
		return returnF32NaNBinOp(, )
	case math.IsInf(, 1) || math.IsInf(, 1):
		return float32(math.Inf(1))
	case  == 0 &&  == :
		if math.Signbit() {
			return 
		}
		return 
	}
	if  >  {
		return 
	}
	return 
}

// WasmCompatNearestF32 is the Wasm spec compatible variant of math.Round, used for Nearest instruction.
// For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic.
//
// e.g. math.Round(-4.5) results in -5 while this results in -4.
//
// See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic.
func ( float32) float32 {
	var  float32
	// TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm
	if  != 0 {
		 := float32(math.Ceil(float64()))
		 := float32(math.Floor(float64()))
		 := math.Abs(float64( - ))
		 := math.Abs(float64( - ))
		 :=  / 2.0
		if  <  {
			 = 
		} else if  ==  && float32(math.Floor(float64())) ==  {
			 = 
		} else {
			 = 
		}
	} else {
		 = 
	}
	return returnF32UniOp(, )
}

// WasmCompatNearestF64 is the Wasm spec compatible variant of math.Round, used for Nearest instruction.
// For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic.
//
// e.g. math.Round(-4.5) results in -5 while this results in -4.
//
// See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic.
func ( float64) float64 {
	// TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm
	var  float64
	if  != 0 {
		 := math.Ceil()
		 := math.Floor()
		 := math.Abs( - )
		 := math.Abs( - )
		 :=  / 2.0
		if  <  {
			 = 
		} else if  ==  && math.Floor() ==  {
			 = 
		} else {
			 = 
		}
	} else {
		 = 
	}
	return returnF64UniOp(, )
}

// WasmCompatCeilF32 is the same as math.Ceil on 32-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float32) float32 {
	return returnF32UniOp(, float32(math.Ceil(float64())))
}

// WasmCompatCeilF64 is the same as math.Ceil on 64-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float64) float64 {
	return returnF64UniOp(, math.Ceil())
}

// WasmCompatFloorF32 is the same as math.Floor on 32-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float32) float32 {
	return returnF32UniOp(, float32(math.Floor(float64())))
}

// WasmCompatFloorF64 is the same as math.Floor on 64-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float64) float64 {
	return returnF64UniOp(, math.Floor())
}

// WasmCompatTruncF32 is the same as math.Trunc on 32-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float32) float32 {
	return returnF32UniOp(, float32(math.Trunc(float64())))
}

// WasmCompatTruncF64 is the same as math.Trunc on 64-bit except that
// the returned NaN value follows the Wasm specification on NaN
// propagation.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func ( float64) float64 {
	return returnF64UniOp(, math.Trunc())
}

func f32IsNaN( float32) bool {
	return  !=  // this is how NaN is defined.
}

func f64IsNaN( float64) bool {
	return  !=  // this is how NaN is defined.
}

// returnF32UniOp returns the result of 32-bit unary operation. This accepts `original` which is the operand,
// and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows
// the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling.
func returnF32UniOp(,  float32) float32 {
	// Following the same logic as in the reference interpreter:
	// https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122
	if !f32IsNaN() {
		return 
	}
	if !f32IsNaN() {
		return math.Float32frombits(F32CanonicalNaNBits)
	}
	return math.Float32frombits(math.Float32bits() | F32CanonicalNaNBits)
}

// returnF32UniOp returns the result of 64-bit unary operation. This accepts `original` which is the operand,
// and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows
// the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling.
func returnF64UniOp(,  float64) float64 {
	// Following the same logic as in the reference interpreter (== amd64 and arm64's behavior):
	// https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122
	if !f64IsNaN() {
		return 
	}
	if !f64IsNaN() {
		return math.Float64frombits(F64CanonicalNaNBits)
	}
	return math.Float64frombits(math.Float64bits() | F64CanonicalNaNBits)
}

// returnF64NaNBinOp returns a NaN for 64-bit binary operations. `x` and `y` are original floats
// and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation
// procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func returnF64NaNBinOp(,  float64) float64 {
	if f64IsNaN() {
		return math.Float64frombits(math.Float64bits() | F64CanonicalNaNBits)
	} else {
		return math.Float64frombits(math.Float64bits() | F64CanonicalNaNBits)
	}
}

// returnF64NaNBinOp returns a NaN for 32-bit binary operations. `x` and `y` are original floats
// and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation
// procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation
func returnF32NaNBinOp(,  float32) float32 {
	if f32IsNaN() {
		return math.Float32frombits(math.Float32bits() | F32CanonicalNaNBits)
	} else {
		return math.Float32frombits(math.Float32bits() | F32CanonicalNaNBits)
	}
}