///|
/// Parsed command line of one command: positional arguments, options that
/// take a value, and boolean flags.
priv struct Options {
  positional : Array[String]
  values : Map[String, String]
  flags : Map[String, Bool]
}

///|
/// Split `args` into positional arguments and options. `value_options` maps
/// every accepted spelling (`-o`, `--out`) to the canonical name; `flag_options`
/// does the same for options without a value. `--name=value` is accepted for
/// value options.
fn parse_options(
  args : Array[String],
  value_options : Map[String, String],
  flag_options : Map[String, String],
) -> Options raise CliError {
  let options : Options = { positional: [], values: Map([]), flags: Map([]), }
  let mut i = 0
  let mut only_positional = false
  while i < args.length() {
    let arg = args[i]
    i += 1
    if only_positional || arg == "-" || !arg.has_prefix("-") {
      options.positional.push(arg)
      continue
    }
    if arg == "--" {
      only_positional = true
      continue
    }
    let (name, inline_value) = match arg.find("=") {
      Some(at) if arg.has_prefix("--") =>
        (arg[0:at].to_owned(), Some(arg[at + 1:].to_owned()))
      _ => (arg, None)
    }
    match value_options.get(name) {
      Some(canonical) => {
        let value = match inline_value {
          Some(v) => v
          None => {
            if i >= args.length() {
              raise Usage("option \"\{name}\" needs a value")
            }
            i += 1
            args[i - 1]
          }
        }
        options.values[canonical] = value
      }
      None =>
        match flag_options.get(name) {
          Some(canonical) if inline_value is None =>
            options.flags[canonical] = true
          _ => raise Usage("unknown option \"\{arg}\"")
        }
    }
  }
  options
}

///|
fn Options::flag(self : Options, name : String) -> Bool {
  self.flags.get(name).unwrap_or(false)
}

///|
fn Options::value(self : Options, name : String) -> String? {
  self.values.get(name)
}