/**
 * Raw trigonometric functions.
 */
module Trig

from "runtime/unsafe/wasmi32" include WasmI32
from "runtime/unsafe/wasmi64" include WasmI64
from "runtime/unsafe/wasmf64" include WasmF64
from "runtime/unsafe/conv" include Conv
use Conv.{ fromInt32, fromFloat64 }
from "runtime/math/kernel/sin" include Sine
use Sine.{ sin as kernSin }
from "runtime/math/kernel/cos" include Cosine
use Cosine.{ cos as kernCos }
from "runtime/math/kernel/tan" include Tangent
use Tangent.{ tan as kernTan }
from "runtime/math/rempio2" include Rempio2
use Rempio2.{ rempio2 }

@unsafe
provide let sin = (x: WasmF64) => { // see: musl/src/math/sin.c
  use WasmI32.{ (&), (<), (<=), (>=), (==) }
  use WasmI64.{ (>>>) }
  use WasmF64.{ (-), (*) }

  let i = WasmI64.reinterpretF64(x)
  // Get high word of x
  let ix = WasmI32.wrapI64(i >>> 32N)
  use WasmI32.{ (>>>) }
  let sign = ix >>> 31n == 1n
  let ix = ix & 0x7FFFFFFFn

  // |x| ~< pi/4
  if (ix <= 0x3FE921FBn) {
    if (ix < 0x3E500000n) { // |x| < 2**-26
      return x
    }
    return kernSin(x, 0.0W, false)
  }

  // sin(Inf or NaN) is NaN
  if (ix >= 0x7FF00000n) return x - x

  // argument reduction needed
  let (n, y0, y1) = rempio2(x, i, sign)
  let n = fromInt32(n)
  let y0 = fromFloat64(y0)
  let y1 = fromFloat64(y1)

  return match (n & 3n) {
    0n => kernSin(y0, y1, true),
    1n => kernCos(y0, y1),
    2n => kernSin(y0, y1, true) * -1.0W,
    _ => kernCos(y0, y1) * -1.0W,
  }
}

@unsafe
provide let cos = (x: WasmF64) => { // see: musl/src/math/cos.c
  use WasmI32.{ (&), (<), (<=), (>=), (==) }
  use WasmI64.{ (>>>) }
  use WasmF64.{ (-), (*) }
  let i = WasmI64.reinterpretF64(x)

  // Get high word of x
  let ix = WasmI32.wrapI64(i >>> 32N)
  use WasmI32.{ (>>>) }
  let sign = ix >>> 31n == 1n
  let ix = ix & 0x7FFFFFFFn

  // |x| ~< pi/4
  if (ix <= 0x3FE921FBn) {
    if (ix < 0x3E46A09En) { // |x| < 2**-27 * sqrt(2)
      return 1.0W
    }
    return kernCos(x, 0.0W)
  }

  // cos(Inf or NaN) is NaN
  if (ix >= 0x7FF00000n) return x - x

  // argument reduction needed
  let (n, y0, y1) = rempio2(x, i, sign)
  let n = fromInt32(n)
  let y0 = fromFloat64(y0)
  let y1 = fromFloat64(y1)

  return match (n & 3n) {
    0n => kernCos(y0, y1),
    1n => kernSin(y0, y1, true) * -1.0W,
    2n => kernCos(y0, y1) * -1.0W,
    _ => kernSin(y0, y1, true),
  }
}

@unsafe
provide let tan = (x: WasmF64) => { // see: musl/src/math/tan.c
  use WasmI32.{ (&), (<=), (<), (>=), (==) }
  use WasmI64.{ (>>>) }
  use WasmF64.{ (-) }
  let i = WasmI64.reinterpretF64(x)
  // Get high word of x
  let ix = WasmI32.wrapI64(i >>> 32N)
  use WasmI32.{ (>>>) }
  let sign = ix >>> 31n == 1n
  let ix = ix & 0x7FFFFFFFn

  // |x| ~< pi/4
  if (ix <= 0x3FE921FBn) {
    if (ix < 0x3E400000n) { // |x| < 2**-27
      return x
    }
    return kernTan(x, 0.0W, true)
  }

  // tan(Inf or NaN) is NaN
  if (ix >= 0x7FF00000n) return x - x

  let (n, y0, y1) = rempio2(x, i, sign)
  let n = fromInt32(n)
  let y0 = fromFloat64(y0)
  let y1 = fromFloat64(y1)
  use WasmI32.{ (-), (&), (<<), (!=) }
  return match (n & 3n) {
    0n => kernTan(y0, y1, true),
    1n => kernTan(y0, y1, false),
    2n => kernTan(y0, y1, true),
    _ => kernTan(y0, y1, false),
  }
}
