///|
fn gen_int(rng : @random.Rand, depth : Int32, scope : Int32) -> IntExpr {
  if depth == 0 {
    if scope > 0 && rng.boolean() {
      Variable(rng.int(limit=scope))
    } else {
      Literal(rng.int(limit=17) - 8)
    }
  } else {
    match rng.int(limit=4) {
      0 => gen_int(rng, 0, scope)
      1 => Add(gen_int(rng, depth - 1, scope), gen_int(rng, depth - 1, scope))
      2 =>
        Subtract(gen_int(rng, depth - 1, scope), gen_int(rng, depth - 1, scope))
      _ =>
        Choose(
          gen_bool(rng, depth - 1, scope),
          gen_int(rng, depth - 1, scope),
          gen_int(rng, depth - 1, scope),
        )
    }
  }
}

///|
fn gen_bool(rng : @random.Rand, depth : Int32, scope : Int32) -> BoolExpr {
  if depth == 0 {
    Boolean(rng.boolean())
  } else if rng.boolean() {
    Less(gen_int(rng, depth - 1, scope), gen_int(rng, depth - 1, scope))
  } else {
    Not(gen_bool(rng, depth - 1, scope))
  }
}

///|
/// int-bool-v1: finite trees, three sequential bindings, no overflow or recursion.
pub fn generate(seed : Int32, depth~ : Int32 = 3) -> Program? {
  guard depth >= 0 && depth <= 4 else { return None }
  let bytes = Bytes::makei(32, i => {
    if i < 4 {
      (seed >> (i * 8)).to_byte()
    } else {
      0
    }
  })
  let rng = @random.Rand::chacha8(seed=bytes)
  let bindings = Array::makei(3, i => gen_int(rng, depth, i))
  let result = Choose(
    Less(Variable(0), Variable(1)),
    Add(Variable(2), gen_int(rng, depth, 3)),
    Subtract(Variable(2), gen_int(rng, depth, 3)),
  )
  Some({ bindings, result, })
}