///|
fn help_message() -> String {
  let message =
    #|Usage: od [OPTION]... [FILE]...
    #|Dump concatenated files, or stdin when no FILE or FILE - is given.
    #|  -A, --address-radix=d|o|x|n  address base, or no addresses
    #|  -j, --skip-bytes=N          skip N input bytes
    #|  -N, --read-bytes=N          print at most N bytes
    #|  -t, --format=TYPE           a, c, d/u/o/x with size 1,2,4,8; optional z
    #|  -a -b -c -d -i -l -o -s -x  traditional named/integer formats
    #|      --endian=little|big     byte order (default little)
    #|  -w[N], --width[=N]         bytes per line (default 16, bare option 32)
    #|  -v, --output-duplicates    do not replace repeated blocks with *
    #|      --help                 display this help and exit
    #|      --version              display version and exit
  message
}

///|
fn settings(parsed : @cli.ParsedArgs) -> DumpState raise OdError {
  let base = match parsed.last_value("address-radix").unwrap_or("o") {
    "d" => 10
    "o" => 8
    "x" => 16
    "n" => 0
    _ => raise OdError("invalid address radix")
  }
  let big = match parsed.last_value("endian").unwrap_or("little") {
    "little" => false
    "big" => true
    _ => raise OdError("invalid byte order")
  }
  let formats : Array[Format] = []
  let mut type_index = 0
  let types = parsed.values("format")
  for option in parsed.order {
    let spec = match option {
      "format" => {
        let t = types[type_index]
        type_index += 1
        t
      }
      "named" => "a"
      "octal-byte" => "o1"
      "character" => "c"
      "decimal" => "u2"
      "int" => "d4"
      "long" => "d8"
      "octal" => "o2"
      "signed" => "d2"
      "hex" => "x2"
      _ => continue
    }
    formats.append(parse_formats(spec))
  }
  if formats.is_empty() {
    formats.push({ kind: 'o', size: 2, trailer: false, })
  }
  let width = @string.parse_int(parsed.last_value("width").unwrap_or("16")) catch {
    _ => raise OdError("invalid line width")
  }
  if width <= 0 || width > 1048576 {
    raise OdError("line width must be between 1 and 1048576")
  }
  for format in formats {
    if width % format.size != 0 {
      raise OdError("line width must be a multiple of every format size")
    }
  }
  let skip = parse_count(parsed.last_value("skip-bytes").unwrap_or("0"))
  let remaining = match parsed.last_value("read-bytes") {
    Some(n) => Some(parse_count(n))
    None => None
  }
  {
    formats,
    base,
    width,
    big,
    verbose: parsed.contains("output-duplicates"),
    pending: [],
    skip,
    remaining,
    offset: 0L,
    previous: None,
    squeezed: false,
  }
}

///|
async fn main {
  let metadata_args = @env.args()[1:]
  if metadata_args.length() == 1 && metadata_args[0] == "--version" {
    @stdio.stdout.write("od 0.1.1\n")
    return
  }
  let args : Array[String] = []
  let mut options = true
  for arg in @env.args()[1:] {
    if arg == "--" {
      options = false
    }
    args.push(
      if options && (arg == "-w" || arg == "--width") {
        "--width=32"
      } else {
        arg
      },
    )
  }
  let parsed = @cli.parse(args, [
    @cli.option("address-radix", short='A', repeatable=true),
    @cli.option("skip-bytes", short='j', repeatable=true),
    @cli.option("read-bytes", short='N', repeatable=true),
    @cli.option("format", short='t', repeatable=true),
    @cli.option("endian", repeatable=true),
    @cli.option("width", short='w', repeatable=true),
    @cli.flag("output-duplicates", short='v', repeatable=true),
    @cli.flag("named", short='a', repeatable=true),
    @cli.flag("octal-byte", short='b', repeatable=true),
    @cli.flag("character", short='c', repeatable=true),
    @cli.flag("decimal", short='d', repeatable=true),
    @cli.flag("int", short='i', repeatable=true),
    @cli.flag("long", short='l', repeatable=true),
    @cli.flag("octal", short='o', repeatable=true),
    @cli.flag("signed", short='s', repeatable=true),
    @cli.flag("hex", short='x', repeatable=true),
    @cli.flag("help"),
    @cli.flag("version"),
  ]) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("od: \{message}: '\{option}'\n")
      @sys.exit(1)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(help_message() + "\n")
    return
  }
  if parsed.contains("version") {
    @stdio.stdout.write("od \{command_version}\n")
    return
  }
  let state = settings(parsed) catch {
    OdError(message) => {
      @stdio.stderr.write("od: \{message}\n")
      @sys.exit(1)
      return
    }
  }
  let sources = if parsed.operands.is_empty() { ["-"] } else { parsed.operands }
  let mut failed = false
  for path in sources {
    if state.skip == 0L && state.remaining == Some(0L) {
      break
    }
    try {
      if path == "-" {
        state.read(@stdio.stdin)
      } else {
        let file = @fs.open(path, mode=ReadOnly)
        defer file.close()
        state.read(file)
      }
    } catch {
      err => {
        @stdio.stderr.write("od: \{path}: \{err}\n")
        failed = true
      }
    }
  }
  if state.skip > 0L {
    @stdio.stderr.write("od: cannot skip past end of combined input\n")
    @sys.exit(1)
    return
  }
  if !state.pending.is_empty() {
    @stdio.stdout.write(state.row())
  }
  if state.base != 0 {
    @stdio.stdout.write(address(state.offset, state.base) + "\n")
  }
  if failed {
    @sys.exit(1)
  }
}