///|
fn help_message() -> String {
  let message =
    #|Usage: xxd [OPTION]... [INFILE [OUTFILE]]
    #|Display bytes or reverse an addressed, plain, or binary dump.
    #|  -a, --autoskip       toggle suppression of zero lines
    #|  -b, --bits           binary digit display
    #|  -e, --little         little-endian groups
    #|  -E, --ebcdic         EBCDIC character display
    #|  -g, --group=N        bytes per group
    #|  -u, --upper          uppercase hexadecimal digits
    #|  -o, --offset=N       add N to displayed offsets
    #|  -p, -ps, --plain     plain hexadecimal display
    #|  -i, --include        C include display
    #|  -r, --reverse        reverse a dump
    #|  -c, --cols=N         bytes per line
    #|  -l, --len=N          maximum input bytes
    #|  -s, --seek=[+][-]N   input seek or reverse address adjustment
    #|  -n, --name=NAME      C include symbol
    #|      --help           display this help and exit
    #|      --version        display version and exit
  message
}

///|
async fn execute(parsed : @cli.ParsedArgs, opts : Options) -> Unit {
  let path = parsed.operands.get(0).unwrap_or("-")
  let destination = parsed.operands.get(1).unwrap_or("-")
  let input = if path == "-" {
    None
  } else {
    Some(@fs.open(path, mode=ReadOnly))
  }
  defer (if input is Some(file) { file.close() })
  let target = if destination == "-" {
    None
  } else {
    if @fs.exists(destination) {
      raise CliError(
        3, "xxd: existing output requires a public file-identity API",
      )
    }
    Some(@fs.create(destination, allow_existing=false, permission=438)) catch {
      error => raise CliError(3, "xxd: \{destination}: \{error}")
    }
  }
  defer (if target is Some(file) { file.close() })
  let output : Output = { file: target, position: 0L, }
  if opts.reverse {
    match input {
      Some(file) => reverse_stream(file, output, opts)
      None => reverse_stream(@stdio.stdin, output, opts)
    }
  } else {
    let start = input_offset(input, opts.seek)
    match input {
      Some(file) =>
        if file.kind() == @fs.Regular || file.kind() == @fs.BlockDevice {
          dump_stream(
            file,
            output,
            opts,
            start,
            path,
            positioned_file=Some(file),
          )
        } else {
          if opts.seek is Some(seek) && seek.negative {
            raise CliError(4, "xxd: Sorry, cannot seek.")
          }
          skip_bytes(file, start)
          dump_stream(file, output, opts, start, path)
        }
      None => {
        skip_bytes(@stdio.stdin, start)
        dump_stream(@stdio.stdin, output, opts, start, path)
      }
    }
  }
}

///|
fn parse_args(args : ArrayView[String]) -> @cli.ParsedArgs raise @cli.CliError {
  let args = normalized_args(args)
  @cli.parse(args[:], [
    @cli.flag("autoskip", short='a', repeatable=true),
    @cli.flag("bits", short='b', repeatable=true),
    @cli.flag("little", short='e', repeatable=true),
    @cli.flag("ebcdic", short='E', repeatable=true),
    @cli.flag("upper", short='u', repeatable=true),
    @cli.flag("plain", short='p', repeatable=true),
    @cli.flag("include", short='i', repeatable=true),
    @cli.flag("reverse", short='r', repeatable=true),
    @cli.option("group", short='g', repeatable=true),
    @cli.option("offset", short='o', repeatable=true),
    @cli.option("cols", short='c', repeatable=true),
    @cli.option("len", short='l', repeatable=true),
    @cli.option("seek", short='s', repeatable=true),
    @cli.option("name", short='n', repeatable=true),
    @cli.flag("help", short='h'),
  ])
}

///|
async fn main {
  let args = @env.args()[1:]
  if args is ["--version"] {
    @stdio.stdout.write("xxd 0.2.2\n")
    return
  }
  let parsed = parse_args(args) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("xxd: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(help_message() + "\n")
    return
  }
  if parsed.operands.length() > 2 {
    @stdio.stderr.write("xxd: extra operand: '\{parsed.operands[2]}'\n")
    @sys.exit(2)
    return
  }
  execute(parsed, options(parsed)) catch {
    CliError(status, message) => {
      @stdio.stderr.write(message + "\n")
      @sys.exit(status)
    }
    error => {
      @stdio.stderr.write("xxd: \{error}\n")
      @sys.exit(2)
    }
  }
}