///|
/// Versioned xorshift32 stream; not suitable for secrets or cryptography.
pub struct Random {
  mut state : UInt
} derive(Debug)

///|
pub fn Random::new(seed : UInt) -> Random {
  { state: if seed == 0U { 0x6D2B79F5U } else { seed }, }
}

///|
pub fn Random::next(self : Random) -> UInt {
  let mut x = self.state
  x = x ^ (x << 13)
  x = x ^ (x >> 17)
  x = x ^ (x << 5)
  self.state = x
  x
}

///|
/// Uniform bounded selection using rejection rather than biased modulo alone.
pub fn Random::below(self : Random, bound : Int) -> Int? {
  if bound <= 0 {
    return None
  }
  let b = bound.reinterpret_as_uint()
  let threshold = (0U - b) % b
  for ;; {
    let value = self.next()
    if value >= threshold {
      return Some((value % b).reinterpret_as_int())
    }
  }
}

///|
fn mix_name(state : UInt, name : String) -> UInt {
  let mut hash = state
  // Scalar values are encoded into four fixed little-endian bytes.
  for char in name.iter() {
    let scalar = char.to_int().reinterpret_as_uint()
    for shift in [0, 8, 16, 24] {
      hash = (hash ^ ((scalar >> shift) & 255U)) * 16777619U
    }
  }
  // Include a separator that is not an encoded Unicode scalar.
  (hash ^ 0xFFFFFFFFU) * 16777619U
}

///|
/// Stable stream identity is (seed, entity, field, row, attempt).
pub fn stream(
  seed : UInt,
  entity : String,
  field : String,
  row : Int,
  attempt? : Int = 0,
) -> Random {
  let hash = mix_name(mix_name(2166136261U ^ seed, entity), field)
  let hash = (hash ^ row.reinterpret_as_uint()) * 16777619U
  Random::new((hash ^ attempt.reinterpret_as_uint()) * 16777619U)
}

///|
pub fn algorithm_version() -> String {
  "moonfixture-xorshift32-scalar-fnv1a-v1"
}

///|
pub extend Random with @moonbitlang/core/debug.Debug::{to_repr}