///|
fn main {
  let args = @env.args()
  let repeat = find_repeat_arg(args)
  let sample_count = find_samples_arg(args)
  let warmup_count = find_warmup_arg(args)
  guard repeat > 0 else { abort("repeat must be positive") }
  guard sample_count >= 0 else { abort("samples must be non-negative") }
  guard warmup_count >= 0 else { abort("warmup must be non-negative") }
  match find_case_arg(args) {
    Some(case_id) =>
      match @support.find_case(case_id) {
        Some(case_item) => {
          let case_path = find_case_path_arg(args).unwrap_or(
            "bench/datasets/cases/" + case_item.id + ".json",
          )
          let prepared = @support.prepare_case_from_fixture(
            case_item, case_path,
          )
          let scratch = case_item.mutation_policy == "scratch_per_sample"
          if sample_count == 0 {
            let checksum = run_repeat(prepared, repeat, scratch)
            println(@support.case_diagnostic_payload(case_item, checksum))
          } else {
            run_warmup(prepared, repeat, warmup_count, scratch)
            let samples = collect_samples(
              prepared, repeat, sample_count, scratch,
            )
            println(benchmark_payload(case_item, samples, repeat))
          }
        }
        None => abort("unknown benchmark case: " + case_id)
      }
    None =>
      abort(
        "usage: moon run src/perf_runner  [--case-file ] [--repeat ] [--samples ] [--warmup ]",
      )
  }
}

///|
fn find_case_arg(args : Array[String]) -> String? {
  let mut skip_next = false
  for candidate in args {
    if skip_next {
      skip_next = false
      continue
    }
    if candidate == "--repeat" ||
      candidate == "--samples" ||
      candidate == "--warmup" ||
      candidate == "--case-file" {
      skip_next = true
      continue
    }
    if @support.find_case(candidate) is Some(_) {
      return Some(candidate)
    }
  }
  None
}

///|
fn find_case_path_arg(args : Array[String]) -> String? {
  for i = 0; i + 1 < args.length(); i = i + 1 {
    if args[i] == "--case-file" {
      return Some(args[i + 1])
    }
  }
  None
}

///|
fn find_repeat_arg(args : Array[String]) -> Int32 {
  find_flag_arg(args, "--repeat").unwrap_or(1)
}

///|
fn find_samples_arg(args : Array[String]) -> Int32 {
  find_flag_arg(args, "--samples").unwrap_or(0)
}

///|
fn find_warmup_arg(args : Array[String]) -> Int32 {
  find_flag_arg(args, "--warmup").unwrap_or(0)
}

///|
fn find_flag_arg(args : Array[String], flag : String) -> Int32? {
  for i = 0; i + 1 < args.length(); i = i + 1 {
    if args[i] == flag {
      return Some(parse_int_arg(flag, args[i + 1]))
    }
  }
  None
}

///|
fn parse_int_arg(flag : String, value : String) -> Int32 {
  try @string.parse_int(value) catch {
    _ => abort("invalid value for " + flag + ": " + value)
  } noraise {
    parsed => parsed
  }
}

///|
fn run_repeat(
  case_item : @support.PreparedCase,
  repeat : Int32,
  scratch : Bool,
) -> UInt64 {
  let scratch_cases = prepare_repeat_cases(case_item, repeat, scratch)
  run_prepared_cases(scratch_cases)
}

///|
fn run_warmup(
  case_item : @support.PreparedCase,
  repeat : Int32,
  warmup_count : Int32,
  scratch : Bool,
) -> Unit {
  for _ in 0.. Array[Int32] {
  let samples = []
  for _ in 0.. Array[@support.PreparedCase] {
  let out = []
  for _ in 0.. UInt64 {
  let mut checksum = 1469598103934665603UL
  for case_item in cases {
    let value = @support.run_prepared_case_inplace(case_item, false)
    checksum = (checksum ^ value) * 1099511628211UL
  }
  checksum
}

///|
fn benchmark_payload(
  case_item : @support.Case,
  samples : Array[Int32],
  repeat : Int32,
) -> String {
  "{\"kind\":\"benchmark\",\"dataset_version\":\"" +
  @support.dataset_version +
  "\",\"case_id\":\"" +
  case_item.id +
  "\",\"operation\":\"" +
  case_item.operation +
  "\",\"family\":\"" +
  case_item.family +
  "\",\"workload_tier\":\"" +
  case_item.workload_tier +
  "\",\"structure\":\"" +
  case_item.structure +
  "\",\"timing_scope\":\"" +
  case_item.timing_scope +
  "\",\"input_layout\":\"" +
  case_item.input_layout +
  "\",\"mutation_policy\":\"" +
  case_item.mutation_policy +
  "\",\"size_tier\":\"" +
  case_item.size_tier +
  "\",\"cost_model\":\"" +
  case_item.cost_model +
  "\",\"rows\":" +
  case_item.rows.to_string() +
  ",\"cols\":" +
  case_item.cols.to_string() +
  ",\"rhs_cols\":" +
  case_item.rhs_cols.to_string() +
  ",\"repeat\":" +
  repeat.to_string() +
  ",\"samples\":" +
  int_array_json(samples) +
  "}"
}

///|
fn int_array_json(values : Array[Int32]) -> String {
  let mut out = "["
  for i = 0; i < values.length(); i = i + 1 {
    if i > 0 {
      out = out + ","
    }
    out = out + values[i].to_string()
  }
  out + "]"
}