///|
/// SplitMix64, the seeding construction recommended for the xoshiro family.
///
/// A single `UInt64` seed is expanded into a well-mixed state so that seeds as
/// similar as `1` and `2` still produce unrelated streams.
pub struct SplitMix64 {
  mut state : UInt64
}

///|
pub fn SplitMix64::new(seed : UInt64) -> SplitMix64 {
  { state: seed, }
}

///|
pub fn SplitMix64::next_u64(self : SplitMix64) -> UInt64 {
  self.state = self.state + 0x9E3779B97F4A7C15UL
  let mut z = self.state
  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL
  z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL
  z ^ (z >> 31)
}

///|
/// The simulator's random source: xoshiro256**.
///
/// Why hand-roll instead of using the standard library: a reproducible
/// experiment must produce the same numbers on every backend and every future
/// version of this package. The algorithm is frozen here, its reference vectors
/// are pinned in the test suite, and nothing about it depends on the platform's
/// word size or on how a host language seeds its own generator.
pub struct Rng {
  mut s0 : UInt64
  mut s1 : UInt64
  mut s2 : UInt64
  mut s3 : UInt64
}

///|
pub fn Rng::new(seed : UInt64) -> Rng {
  let seeder = SplitMix64::new(seed)
  {
    s0: seeder.next_u64(),
    s1: seeder.next_u64(),
    s2: seeder.next_u64(),
    s3: seeder.next_u64(),
  }
}

///|
pub fn Rng::next_u64(self : Rng) -> UInt64 {
  let result = rotl64(self.s1 * 5UL, 7) * 9UL
  let t = self.s1 << 17
  self.s2 = self.s2 ^ self.s0
  self.s3 = self.s3 ^ self.s1
  self.s1 = self.s1 ^ self.s2
  self.s0 = self.s0 ^ self.s3
  self.s2 = self.s2 ^ t
  self.s3 = rotl64(self.s3, 45)
  result
}

///|
pub fn rotl64(value : UInt64, shift : Int) -> UInt64 {
  (value << shift) | (value >> (64 - shift))
}

///|
/// A uniform `Int` in `[0, bound)`, using rejection sampling so that the
/// distribution is exactly uniform rather than modulo-biased. Determinism is
/// preserved because the number of rejections depends only on the sequence.
pub fn Rng::next_int(self : Rng, bound : Int) -> Int {
  if bound <= 0 {
    abort("Rng::next_int requires a positive bound")
  }
  let limit = 4294967296UL / bound.to_uint64() * bound.to_uint64()
  let mut draw = self.next_u64() % 4294967296UL
  while draw >= limit {
    draw = self.next_u64() % 4294967296UL
  }
  (draw % bound.to_uint64()).to_int()
}

///|
/// A uniform `Int64` in `[0, bound)`.
pub fn Rng::next_int64(self : Rng, bound : Int64) -> Int64 {
  if bound <= 0L {
    abort("Rng::next_int64 requires a positive bound")
  }
  let base = bound.reinterpret_as_uint64()
  let limit = 18446744073709551615UL / base * base
  let mut draw = self.next_u64()
  while draw >= limit {
    draw = self.next_u64()
  }
  (draw % base).reinterpret_as_int64()
}

///|
/// A uniform `Double` in `[0, 1)`, built from the top 53 bits so every value is
/// exactly representable and the generator never returns 1.0.
pub fn Rng::next_double(self : Rng) -> Double {
  (self.next_u64() >> 11).to_double() / 9007199254740992.0
}

///|
/// A uniform `Double` in `[low, high)`.
pub fn Rng::next_double_between(
  self : Rng,
  low : Double,
  high : Double,
) -> Double {
  low + (high - low) * self.next_double()
}

///|
/// `true` with probability `p`. `p <= 0` never fires and `p >= 1` always does,
/// which keeps scenario files free of edge-case surprises.
pub fn Rng::next_bool(self : Rng, p : Double) -> Bool {
  if p <= 0.0 {
    false
  } else if p >= 1.0 {
    true
  } else {
    self.next_double() < p
  }
}

///|
/// An exponential sample with the given mean, from the inverse transform
/// `-mean * ln(U)`. Used for inter-arrival times and for burst gaps.
pub fn Rng::next_exponential(self : Rng, mean : Double) -> Double {
  let u = self.next_double()
  -mean * ln_approx(1.0 - u)
}

///|
/// Natural logarithm via `@math`-free series-free approximation is not good
/// enough for statistics, so this delegates to the standard library and only
/// guards the domain.
fn ln_approx(x : Double) -> Double {
  if x <= 0.0 {
    abort("ln domain error")
  }
  @math.ln(x)
}

///|
/// Derive an independent stream from this generator plus a label.
///
/// This is how the simulator gives every flow, every link and every loss
/// process its own randomness without any of them sharing draw order: renaming
/// a flow changes its stream, but adding an unrelated flow does not disturb it.
pub fn Rng::fork(self : Rng, label : String) -> Rng {
  Rng::new(self.next_u64() ^ fnv1a64(label))
}

///|
/// FNV-1a over the UTF-8 bytes of a label, used to turn names into stable
/// numbers. Any hash would do; what matters is that it never changes.
pub fn fnv1a64(text : String) -> UInt64 {
  let mut hash = 0xCBF29CE484222325UL
  for byte in @utf8.encode(text) {
    hash = (hash ^ byte.to_uint64()) * 0x100000001B3UL
  }
  hash
}

///|
/// Combine two 64-bit values into one, so that the pair determines the result
/// and the order matters.
///
/// This is what gives a packet a fate derived from its identity: the same
/// identity on the same link always produces the same number, whatever else
/// happened on that link in between.
pub fn mix64(a : UInt64, b : UInt64) -> UInt64 {
  let seeder = SplitMix64::new(a ^ (b * 0x9E3779B97F4A7C15UL))
  seeder.next_u64()
}