///|
fn no_modules(_importer : String, name : String) -> @moonwren.ModuleLoadResult {
  @moonwren.ModuleLoadFailed("module '\{name}' is not available")
}

///|
fn fail_soak(message : String) -> Unit {
  println("soak_failed: \{message}")
  @sys.exit(1)
}

///|
fn main {
  let arguments = @env.args()
  let iterations = if arguments.contains("--quick") { 500 } else { 10000 }
  let allocations = [0]
  let finalizations = [0]
  let callbacks = [0]
  let foreign_class = @moonwren.ForeignClassBinding::new(
    "main",
    "Token",
    fn(_) {
      allocations[0] = allocations[0] + 1
      @moonwren.ForeignAllocationSucceeded(allocations[0])
    },
    fn(_) { finalizations[0] = finalizations[0] + 1 },
  )
  let token_value = @moonwren.ForeignMethodBinding::new_for_foreign_class(
    "main",
    "Token",
    "value",
    fn(handle, _) {
      @moonwren.ForeignCallSucceeded(
        @moonwren.Value::number((handle - 1).to_double()),
      )
    },
  )
  let audit = @moonwren.ForeignMethodBinding::new(
    "main",
    "Host",
    true,
    "audit(_)",
    fn(_, arguments) {
      callbacks[0] = callbacks[0] + 1
      @moonwren.ForeignCallSucceeded(arguments[0])
    },
  )
  let vm = @moonwren.Vm::new(
    @moonwren.VmLimits::new(100000, 64, 10000, 8, 2048, 100000),
    no_modules,
    [token_value, audit],
    [foreign_class],
  )
  if !vm.configure_automatic_gc(@moonwren.VmGcPolicy::new(64, 25)) {
    fail_soak("automatic GC policy was rejected")
  }
  let source =
    #|foreign class Token {
    #|  construct new(value) {}
    #|  foreign value
    #|}
    #|class Host { foreign static audit(value) }
    #|var total = 0
    #|var process = Fn.new { |input|
    #|  var token = Token.new(input)
    #|  var temporary = [token.value, {"input": input}, Fn.new { input }]
    #|  total = total + Host.audit(temporary[0])
    #|  return total
    #|}
  match vm.interpret("main", source) {
    @moonwren.ProgramCompleted(_, _) => ()
    @moonwren.ProgramFailed(issues, _) => fail_soak(issues[0].message)
  }
  let process = match vm.capture("main", "process") {
    @moonwren.VmHandleCreated(value) => value
    @moonwren.VmHandleFailed(issue) => {
      fail_soak(issue.message)
      return
    }
  }
  let mut peak_reachable = 0
  for index = 0; index < iterations; index = index + 1 {
    let expected = (index * (index + 1) / 2).to_double()
    match vm.call(process, [@moonwren.Value::number(index.to_double())]) {
      @moonwren.ProgramCompleted(value, _) =>
        if value != @moonwren.Value::number(expected) {
          fail_soak("wrong result at iteration \{index}: \{value.to_text()}")
        }
      @moonwren.ProgramFailed(issues, _) => fail_soak(issues[0].message)
    }
    if index % 25 == 24 {
      let stats = vm.collect_garbage()
      if stats.reachable_objects > peak_reachable {
        peak_reachable = stats.reachable_objects
      }
      if stats.reachable_objects > 64 {
        fail_soak(
          "reachable object count grew beyond the bound: \{stats.reachable_objects}",
        )
      }
    }
  }
  if !vm.release(process) {
    fail_soak("process handle could not be released")
  }
  let final_stats = vm.collect_garbage()
  if allocations[0] != iterations {
    fail_soak("expected \{iterations} allocations, got \{allocations[0]}")
  }
  if callbacks[0] != iterations {
    fail_soak("expected \{iterations} callbacks, got \{callbacks[0]}")
  }
  if finalizations[0] != allocations[0] {
    fail_soak(
      "foreign finalizer mismatch: \{finalizations[0]}/\{allocations[0]}",
    )
  }
  vm.close()
  if finalizations[0] != allocations[0] {
    fail_soak("VM close finalized a foreign object more than once")
  }
  println(
    "soak_passed iterations=\{iterations} callbacks=\{callbacks[0]} finalized=\{finalizations[0]} peak_reachable=\{peak_reachable} remaining=\{final_stats.reachable_objects}",
  )
}