///|
fn cmd_parse(args : Array[String]) -> Unit {
  match range_input(args) {
    Err(error) => print_error("MissingOption", error)
    Ok(input) =>
      match @range.parse_range(input) {
        Err(error) => print_range_error(error)
        Ok(request) =>
          println(
            "{\"ok\":true,\"unit\":\{q(request.unit().name())},\"specs\":\{request.specs().length()},\"canonical\":\{q(@range.serialize_range(request))}}",
          )
      }
  }
}

///|
fn cmd_resolve(args : Array[String]) -> Unit {
  match (range_input(args), length_input(args)) {
    (Ok(input), Ok(length)) =>
      match @range.parse_range(input) {
        Err(error) => print_range_error(error)
        Ok(request) =>
          match @range.resolve_byte_ranges(request, length) {
            Err(error) => print_range_error(error)
            Ok(result) =>
              println(
                "{\"ok\":true,\"original\":\{result.original_count()},\"satisfiable\":\{result.satisfiable_count()},\"unsatisfiable\":\{result.unsatisfiable_count()},\"ranges\":\{ranges_json(result.ranges())}}",
              )
          }
      }
    (Err(error), _) | (_, Err(error)) => print_error("InvalidOption", error)
  }
}

///|
fn cmd_content_range(args : Array[String]) -> Unit {
  match required(args, "--value") {
    Err(error) => print_error("MissingOption", error)
    Ok(value) =>
      match @range.parse_content_range(value) {
        Ok(parsed) =>
          println(
            "{\"ok\":true,\"canonical\":\{q(@range.serialize_content_range(parsed))}}",
          )
        Err(error) => print_range_error(error)
      }
  }
}

///|
fn cmd_accept_ranges(args : Array[String]) -> Unit {
  match required(args, "--value") {
    Err(error) => print_error("MissingOption", error)
    Ok(value) =>
      match @range.parse_accept_ranges(value) {
        Ok(parsed) =>
          println(
            "{\"ok\":true,\"bytes\":\{parsed.supports_bytes()},\"canonical\":\{q(@range.serialize_accept_ranges(parsed))}}",
          )
        Err(error) => print_range_error(error)
      }
  }
}

///|
fn cmd_plan(args : Array[String]) -> Unit {
  match (range_input(args), length_input(args)) {
    (Ok(input), Ok(length)) =>
      match @range.parse_range(input) {
        Err(error) => print_range_error(error)
        Ok(request) =>
          match
            @range.plan_byte_range_response(request, length, true, true, true) {
            Err(error) => print_range_error(error)
            Ok(plan) =>
              println(
                "{\"ok\":true,\"status\":\{plan.recommended_status()},\"kind\":\{q(plan_name(plan))},\"ranges\":\{ranges_json(plan.ranges())}}",
              )
          }
      }
    (Err(error), _) | (_, Err(error)) => print_error("InvalidOption", error)
  }
}

///|
fn cmd_audit(args : Array[String]) -> Unit {
  match range_input(args) {
    Err(error) => print_error("MissingOption", error)
    Ok(input) => {
      let length = match option_value(args, "--length") {
        Some(value) =>
          match parse_i64(value) {
            Ok(parsed) => parsed
            Err(error) => {
              print_error("InvalidOption", error)
              return
            }
          }
        None => 0L
      }
      match @range.parse_range(input) {
        Err(error) => print_range_error(error)
        Ok(request) => {
          let report = @range.audit_range_request(
            request,
            raw_input=input,
            representation_length=length,
          )
          if has_flag(args, "--details") {
            println(
              "{\"ok\":true,\"findings\":\{findings_json(report.findings())}}",
            )
          } else {
            println("{\"ok\":true,\"findings\":\{report.findings().length()}}")
          }
        }
      }
    }
  }
}

///|
fn cmd_if_range(args : Array[String]) -> Unit {
  match
    (required(args, "--value"), required(args, "--etag"), length_input(args)) {
    (Ok(value), Ok(etag), Ok(length)) =>
      match @range.parse_if_range(value) {
        Err(error) => print_range_error(error)
        Ok(validator) => {
          let matches = @range.if_range_matches(validator, etag, length)
          println(
            "{\"ok\":true,\"kind\":\{q(if_range_kind_name(validator))},\"matches\":\{matches}}",
          )
        }
      }
    (Err(error), _, _) | (_, Err(error), _) | (_, _, Err(error)) =>
      print_error("InvalidOption", error)
  }
}

///|
fn if_range_kind_name(validator : @range.IfRangeValue) -> String {
  match validator {
    @range.EntityTag(_) => "EntityTag"
    @range.HttpDate(_) => "HttpDate"
  }
}

///|
fn has_flag(args : Array[String], name : String) -> Bool {
  for value in args {
    if value == name {
      return true
    }
  }
  false
}

///|
fn findings_json(findings : Array[@range.AuditFinding]) -> String {
  let parts : Array[String] = []
  for finding in findings {
    parts.push(
      "{\"kind\":\{q(finding.kind().name())},\"context\":\{q(finding.context())}}",
    )
  }
  "[\{join(parts)}]"
}

///|
fn cmd_stats(args : Array[String]) -> Unit {
  match range_input(args) {
    Err(error) => print_error("MissingOption", error)
    Ok(input) =>
      match @range.parse_range(input) {
        Err(error) => print_range_error(error)
        Ok(request) =>
          println(
            "{\"unit\":\{q(request.unit().name())},\"specs\":\{request.specs().length()},\"input_bytes\":\{input.length()}}",
          )
      }
  }
}

///|
fn ranges_json(ranges : Array[@range.ConcreteRange]) -> String {
  let parts : Array[String] = []
  for range in ranges {
    parts.push(
      "{\"first\":\{range.first()},\"last\":\{range.last()},\"length\":\{range.length()}}",
    )
  }
  "[\{join(parts)}]"
}

///|
fn join(values : Array[String]) -> String {
  let builder = StringBuilder()
  for i = 0; i < values.length(); i = i + 1 {
    if i > 0 {
      builder.write_char(',')
    }
    builder.write_string(values[i])
  }
  builder.to_string()
}

///|
fn plan_name(plan : @range.ResponsePlan) -> String {
  match plan {
    @range.Ignore => "Ignore"
    @range.Unsatisfiable(_) => "Unsatisfiable"
    @range.Single(_) => "Single"
    @range.Multiple(_) => "Multiple"
  }
}