///|
pub fn random_time_based() -> @random.Rand {
  @random.Rand::chacha8(seed=generate_time_based_seed())
}

///|
pub fn generate_time_based_seed() -> Bytes {
  let timestamp = @env.now()
  let part1 = timestamp - 25
  let part2 = timestamp + 25
  let part3 = timestamp - 41
  let part4 = timestamp + 41
  uint64_array_to_bytes([part1, part2, part3, part4])
}

///|
pub fn uint64_array_to_bytes(values : Array[UInt64]) -> Bytes {
  Bytes::makei(values.length() * 8, fn(i) {
    let value = values[i / 8]
    let shift = i % 8 * 8
    (value >> shift).to_byte()
  })
}

///|
test "random handles degenerate limits without panicking" {
  let rand = @random.Rand::chacha8(seed=uint64_array_to_bytes([1, 2, 3, 4]))
  assert_eq(random(rand, 0N), 0N)
  assert_eq(random(rand, 1N), 0N)
  assert_eq(random_range(rand, 5N, 5N), 5N)
  assert_eq(random_range(rand, 7N, 3N), 7N)
}

///|
test "uint64_array_to_bytes handles non-four element arrays" {
  assert_eq(uint64_array_to_bytes([1, 2]).length(), 16)
  assert_eq(uint64_array_to_bytes([0x0102030405060708]).length(), 8)
}

///|
pub fn random(rand : @random.Rand, limit : BigInt) -> BigInt {
  // limit <= 1 means the range [0, limit) is empty or degenerate
  guard limit > 1 else { return 0 }
  let k = limit.bit_length()
  let two_pow_k = (1 : BigInt) << k
  let threshold = two_pow_k - two_pow_k % limit
  let mut x = rand.bigint(k)
  while x >= threshold {
    x = rand.bigint(k)
  }
  x % limit
}

///|
pub fn random_range(
  rand : @random.Rand,
  inclusive : BigInt,
  exclusive : BigInt,
) -> BigInt {
  inclusive + random(rand, exclusive - inclusive)
}