/**
 * Utilities for working with the Int32 type.
 *
 * @example from "int32" include Int32
 *
 * @example 1l
 * @example -1l
 *
 * @since v0.2.0
 */
module Int32

from "runtime/unsafe/offsets" include Offsets
use Offsets.{
  _UINT32_VALUE_OFFSET as _VALUE_OFFSET,
  _UINT32_VALUE_OFFSET,
  _FLOAT32_VALUE_OFFSET,
}
from "runtime/unsafe/wasmi32" include WasmI32
use WasmI32.{
  (+),
  (-),
  (*),
  (/),
  (&),
  (|),
  (^),
  (<<),
  (>>),
  (==),
  (!=),
  (<),
  (<=),
  (>),
  (>=),
}
from "runtime/unsafe/wasmi64" include WasmI64
from "runtime/exception" include Exception

from "runtime/dataStructures" include DataStructures
use DataStructures.{ newInt32 }

from "runtime/numbers" include Numbers
use Numbers.{
  coerceNumberToInt32 as fromNumber,
  coerceInt32ToNumber as toNumber,
}

provide { fromNumber, toNumber }

/**
 * Converts a Uint32 to an Int32.
 *
 * @param number: The value to convert
 * @returns The Uint32 represented as an Int32
 *
 * @example Int32.fromUint32(1ul) == 1l
 *
 * @since v0.6.0
 */
@unsafe
provide let fromUint32 = (number: Uint32) => {
  let x = WasmI32.load(WasmI32.fromGrain(number), _UINT32_VALUE_OFFSET)
  let result = newInt32(x)
  WasmI32.toGrain(result): Int32
}

/**
 * Interprets a Float32 as an Int32.
 *
 * @param value: The value to convert
 * @returns The Float32 interpreted as an Int32
 *
 * @example Int32.reinterpretFloat32(1.0f) == 1065353216l
 * @example Int32.reinterpretFloat32(-1.0f) == -1065353216l
 *
 * @since v0.7.0
 */
@unsafe
provide let reinterpretFloat32 = (value: Float32) => {
  let x = WasmI32.load(WasmI32.fromGrain(value), _FLOAT32_VALUE_OFFSET)
  let result = newInt32(x)
  WasmI32.toGrain(result): Int32
}

/**
 * Increments the value by one.
 *
 * @param value: The value to increment
 * @returns The incremented value
 *
 * @example Int32.incr(1l) == 2l
 * @example Int32.incr(-2l) == -1l
 *
 * @since v0.2.0
 */
@unsafe
provide let incr = (value: Int32) => {
  let value = WasmI32.fromGrain(value)
  let ptr = newInt32(WasmI32.load(value, _VALUE_OFFSET) + 1n)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Decrements the value by one.
 *
 * @param value: The value to decrement
 * @returns The decremented value
 *
 * @example Int32.decr(2l) == 1l
 * @example Int32.decr(0l) == -1l
 *
 * @since v0.2.0
 */
@unsafe
provide let decr = (value: Int32) => {
  let value = WasmI32.fromGrain(value)
  let ptr = newInt32(WasmI32.load(value, _VALUE_OFFSET) - 1n)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the sum of its operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The sum of the two operands
 *
 * @example
 * use Int32.{ (+) }
 * assert 1l + 1l == 2l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `add`
 */
@unsafe
provide let (+) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv + yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the difference of its operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The difference of the two operands
 *
 * @example
 * use Int32.{ (-) }
 * assert 2l - 1l == 1l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `sub`
 */
@unsafe
provide let (-) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv - yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the product of its operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The product of the two operands
 *
 * @example
 * use Int32.{ (*) }
 * assert 2l * 2l == 4l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `mul`
 */
@unsafe
provide let (*) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv * yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the quotient of its operands using signed division.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The quotient of its operands
 *
 * @example
 * use Int32.{ (/) }
 * assert 8l / 2l == 4l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `div`
 */
@unsafe
provide let (/) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv / yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the remainder of the division of its operands using signed division.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The remainder of its operands
 *
 * @example Int32.rem(8l, 3l) == 2l
 *
 * @since v0.2.0
 */
@unsafe
provide let rem = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.remS(xv, yv))
  WasmI32.toGrain(ptr): Int32
}

@unsafe
let abs = n => {
  use WasmI32.{ (-) }
  let mask = n >> 31n
  (n ^ mask) - mask
}

/**
 * Computes the remainder of the division of the first operand by the second.
 * The result will have the sign of the second operand.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns The modulus of its operands
 *
 * @throws ModuloByZero: When `y` is zero
 *
 * @example
 * use Int32.{ (%) }
 * assert -5l % 3l == 1l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `mod`
 */
@unsafe
provide let (%) = (x: Int32, y: Int32) => {
  use WasmI32.{ (-) }
  let xval = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yval = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)

  if (WasmI32.eqz(yval)) {
    throw Exception.ModuloByZero
  }

  let ptr = if ((xval ^ yval) < 0n) {
    let xabs = abs(xval)
    let yabs = abs(yval)
    let mval = WasmI32.remS(xabs, yabs)
    let mres = yabs - mval
    newInt32(if (mval != 0n) (if (yval < 0n) 0n - mres else mres) else 0n)
  } else {
    newInt32(WasmI32.remS(xval, yval))
  }
  WasmI32.toGrain(ptr): Int32
}

/**
 * Rotates the bits of the value left by the given number of bits.
 *
 * @param value: The value to rotate
 * @param amount: The number of bits to rotate by
 * @returns The rotated value
 *
 * @example Int32.rotl(1l, 1l) == 2l
 * @example Int32.rotl(1l, 2l) == 4l
 *
 * @since v0.4.0
 */
@unsafe
provide let rotl = (value: Int32, amount: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(amount), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.rotl(xv, yv))
  WasmI32.toGrain(ptr): Int32
}

/**
 * Rotates the bits of the value right by the given number of bits.
 *
 * @param value: The value to rotate
 * @param amount: The number of bits to rotate by
 * @returns The rotated value
 *
 * @example Int32.rotr(2l, 1l) == 1l
 * @example Int32.rotr(4l, 2l) == 1l
 *
 * @since v0.4.0
 */
@unsafe
provide let rotr = (value: Int32, amount: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(amount), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.rotr(xv, yv))
  WasmI32.toGrain(ptr): Int32
}

/**
 * Shifts the bits of the value left by the given number of bits.
 *
 * @param value: The value to shift
 * @param amount: The number of bits to shift by
 * @returns The shifted value
 *
 * @example
 * use Int32.{ (<<) }
 * assert (5l << 1l) == 10l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `shl`
 */
@unsafe
provide let (<<) = (value: Int32, amount: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(amount), _VALUE_OFFSET)
  let ptr = newInt32(xv << yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Shifts the bits of the value right by the given number of bits, preserving the sign bit.
 *
 * @param value: The value to shift
 * @param amount: The amount to shift by
 * @returns The shifted value
 *
 * @example
 * use Int32.{ (>>) }
 * assert (5l >> 1l) == 2l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `shr`
 */
@unsafe
provide let (>>) = (value: Int32, amount: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(amount), _VALUE_OFFSET)
  let ptr = newInt32(xv >> yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Checks if the first value is equal to the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is equal to the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (==) }
 * assert 1l == 1l
 *
 * @since v0.6.0
 * @history v0.4.0: Originally named `eq`
 */
@unsafe
provide let (==) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv == yv
}

/**
 * Checks if the first value is not equal to the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is not equal to the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (!=) }
 * assert 1l != 2l
 *
 * @since v0.6.0
 * @history v0.4.0: Originally named `ne`
 */
@unsafe
provide let (!=) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv != yv
}

/**
 * Checks if the given value is equal to zero.
 *
 * @param value: The value to inspect
 * @returns `true` if the first value is equal to zero or `false` otherwise
 *
 * @example Int32.eqz(0l) == true
 * @example Int32.eqz(1l) == false
 *
 * @since v0.4.0
 */
@unsafe
provide let eqz = (value: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  WasmI32.eqz(xv)
}

/**
 * Checks if the first value is less than the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is less than the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (<) }
 * assert 1l < 2l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `lt`
 */
@unsafe
provide let (<) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv < yv
}

/**
 * Checks if the first value is greater than the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is greater than the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (>) }
 * assert 2l > 1l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `gt`
 */
@unsafe
provide let (>) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv > yv
}

/**
 * Checks if the first value is less than or equal to the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is less than or equal to the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (<=) }
 * assert 1l <= 2l
 * @example
 * use Int32.{ (<=) }
 * assert 1l <= 1l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `lte`
 */
@unsafe
provide let (<=) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv <= yv
}

/**
 * Checks if the first value is greater than or equal to the second value.
 *
 * @param x: The first value
 * @param y: The second value
 * @returns `true` if the first value is greater than or equal to the second value or `false` otherwise
 *
 * @example
 * use Int32.{ (>=) }
 * assert 2l >= 1l
 * @example
 * use Int32.{ (>=) }
 * assert 1l >= 1l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `gte`
 */
@unsafe
provide let (>=) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  xv >= yv
}

/**
 * Computes the bitwise NOT of the given value.
 *
 * @param value: The given value
 * @returns Containing the inverted bits of the given value
 *
 * @example Int32.lnot(-5l) == 4l
 *
 * @since v0.2.0
 */
@unsafe
provide let lnot = (value: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let ptr = newInt32(xv ^ 0xffffffffn)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the bitwise AND (`&`) on the given operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns Containing a `1` in each bit position for which the corresponding bits of both operands are `1`
 *
 * @example
 * use Int32.{ (&) }
 * assert (3l & 4l) == 0l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `land`
 */
@unsafe
provide let (&) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv & yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the bitwise OR (`|`) on the given operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns Containing a `1` in each bit position for which the corresponding bits of either or both operands are `1`
 *
 * @example
 * use Int32.{ (|) }
 * assert (3l | 4l) == 7l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `lor`
 */
@unsafe
provide let (|) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv | yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Computes the bitwise XOR (`^`) on the given operands.
 *
 * @param x: The first operand
 * @param y: The second operand
 * @returns Containing a `1` in each bit position for which the corresponding bits of either but not both operands are `1`
 *
 * @example
 * use Int32.{ (^) }
 * assert (3l ^ 5l) == 6l
 *
 * @since v0.6.0
 * @history v0.2.0: Originally named `lxor`
 */
@unsafe
provide let (^) = (x: Int32, y: Int32) => {
  let xv = WasmI32.load(WasmI32.fromGrain(x), _VALUE_OFFSET)
  let yv = WasmI32.load(WasmI32.fromGrain(y), _VALUE_OFFSET)
  let ptr = newInt32(xv ^ yv)
  WasmI32.toGrain(ptr): Int32
}

/**
 * Counts the number of leading zero bits in the value.
 *
 * @param value: The value to inspect
 * @returns The amount of leading zeros
 *
 * @example Int32.clz(1l) == 31l
 * @example Int32.clz(4l) == 29l
 *
 * @since v0.4.0
 */
@unsafe
provide let clz = (value: Int32) => {
  let nv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.clz(nv))
  WasmI32.toGrain(ptr): Int32
}

/**
 * Counts the number of trailing zero bits in the value.
 *
 * @param value: The value to inspect
 * @returns The amount of trailing zeros
 *
 * @example Int32.ctz(1l) == 0l
 * @example Int32.ctz(4l) == 2l
 *
 * @since v0.4.0
 */
@unsafe
provide let ctz = (value: Int32) => {
  let nv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.ctz(nv))
  WasmI32.toGrain(ptr): Int32
}

/**
 * Counts the number of bits set to `1` in the value, also known as a population count.
 *
 * @param value: The value to inspect
 * @returns The amount of 1-bits in its operand
 *
 * @example Int32.popcnt(1l) == 1l
 * @example Int32.popcnt(3l) == 2l
 *
 * @since v0.4.0
 */
@unsafe
provide let popcnt = (value: Int32) => {
  let nv = WasmI32.load(WasmI32.fromGrain(value), _VALUE_OFFSET)
  let ptr = newInt32(WasmI32.popcnt(nv))
  WasmI32.toGrain(ptr): Int32
}

// Exponentiation by squaring https://en.wikipedia.org/wiki/Exponentiation_by_squaring special path for int^int
let rec expBySquaring = (y, x, n) => {
  if (n == 0l) {
    1l
  } else if (n == 1l) {
    x * y
  } else if (n % 2l == 0l) {
    expBySquaring(y, x * x, n / 2l)
  } else {
    expBySquaring(x * y, x * x, (n - 1l) / 2l)
  }
}

/**
 * Computes the exponentiation of the given base and power.
 *
 * @param base: The base number
 * @param power: The exponent number
 * @returns The base raised to the given power
 *
 * @example
 * use Int32.{ (**) }
 * assert 2l ** 3l == 8l
 *
 * @since v0.6.0
 */
provide let (**) = (base, power) => {
  if (power < 0l)
    return expBySquaring(1l, 1l / base, power * -1l)
  else
    return expBySquaring(1l, base, power)
}
