///|
/// Deterministic pseudo-random stream for reproducible artwork and benchmarks.
pub(all) struct Random {
  mut state : UInt
} derive(Debug)

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

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

///|
pub fn Random::next_float(self : Random) -> Double {
  self.next_u32().to_double() / 4294967295.0
}

///|
pub fn Random::range(self : Random, low : Double, high : Double) -> Double {
  low + (high - low) * self.next_float()
}

///|
pub fn Random::int_range(self : Random, low : Int, high : Int) -> Int {
  if high <= low {
    low
  } else {
    low + (self.next_float() * (high - low).to_double()).to_int()
  }
}

///|
pub fn[T] Random::choose(self : Random, values : Array[T]) -> T? {
  if values.length() == 0 {
    None
  } else {
    Some(values[self.int_range(0, values.length())])
  }
}

///|
pub fn hash2d(x : Int, y : Int, seed? : UInt = 0U) -> UInt {
  let mut h = seed ^ 0x811c9dc5U
  h = (h ^ x.reinterpret_as_uint()) * 0x01000193U
  h = (h ^ y.reinterpret_as_uint()) * 0x01000193U
  h ^ (h >> 16)
}

///|
pub fn hash2d_float(x : Int, y : Int, seed? : UInt = 0U) -> Double {
  hash2d(x, y, seed~).to_double() / 4294967295.0
}

///|
pub fn smoothstep(value : Double) -> Double {
  let t = if value < 0.0 { 0.0 } else if value > 1.0 { 1.0 } else { value }
  t * t * (3.0 - 2.0 * t)
}

///|
/// A deterministic value-noise sampler with bilinear interpolation.
pub(all) struct ValueNoise {
  frequency : Double
  seed : UInt
} derive(Eq, Debug, ToJson)

///|
pub fn ValueNoise::new(frequency : Double, seed : UInt) -> ValueNoise {
  { frequency, seed }
}

///|
pub impl NoiseSampler for ValueNoise with fn sample_2d(self, x, y) {
  let px = x * self.frequency
  let py = y * self.frequency
  let x0 = px.to_int()
  let y0 = py.to_int()
  let fx = smoothstep(px - x0.to_double())
  let fy = smoothstep(py - y0.to_double())
  let a = hash2d_float(x0, y0, seed=self.seed) * 2.0 - 1.0
  let b = hash2d_float(x0 + 1, y0, seed=self.seed) * 2.0 - 1.0
  let c = hash2d_float(x0, y0 + 1, seed=self.seed) * 2.0 - 1.0
  let d = hash2d_float(x0 + 1, y0 + 1, seed=self.seed) * 2.0 - 1.0
  let top = a + (b - a) * fx
  let bottom = c + (d - c) * fx
  top + (bottom - top) * fy
}

///|
/// Fractal Brownian motion combines several noise octaves.
pub fn[N : NoiseSampler] fractal_noise(
  sampler : N,
  x : Double,
  y : Double,
  octaves : Int,
  lacunarity? : Double = 2.0,
  gain? : Double = 0.5,
) -> Double {
  let mut amplitude = 1.0
  let mut frequency = 1.0
  let mut total = 0.0
  let mut normalizer = 0.0
  for _ in 0.. Double {
  let value = fractal_noise(sampler, x, y, octaves)
  1.0 - value.abs()
}