///|
fn is_space(value : Char) -> Bool {
  value == ' ' || value == '\t' || value == '\n' || value == '\r'
}

///|
priv suberror XargsError {
  XargsError(String)
}

///|
fn push_word(result : Array[String], word : String) -> Unit {
  result.push(word)
}

///|
fn parse_words(
  text : String,
  null_mode : Bool,
) -> Array[String] raise XargsError {
  let result : Array[String] = []
  let current = StringBuilder()
  if null_mode {
    for char in text {
      if char == '\u{0}' {
        push_word(result, current.to_string())
        current.reset()
      } else {
        current.write_char(char)
      }
    }
    if !current.is_empty() {
      push_word(result, current.to_string())
    }
    return result
  }
  let mut quote : Char? = None
  let mut escaped = false
  let mut word_started = false
  for char in text {
    if escaped {
      current.write_char(char)
      escaped = false
      word_started = true
    } else if char == '\\' && quote != Some('\'') {
      escaped = true
      word_started = true
    } else {
      match quote {
        Some(delimiter) =>
          if char == delimiter {
            quote = None
          } else {
            current.write_char(char)
          }
        None =>
          if char == '\'' || char == '"' {
            quote = Some(char)
            word_started = true
          } else if is_space(char) {
            if word_started {
              push_word(result, current.to_string())
              current.reset()
              word_started = false
            }
          } else {
            current.write_char(char)
            word_started = true
          }
      }
    }
  }
  if escaped {
    raise XargsError("input ends with an unmatched backslash")
  }
  if quote is Some(delimiter) {
    raise XargsError("input contains an unmatched \'\{delimiter}\' quote")
  }
  if word_started {
    push_word(result, current.to_string())
  }
  result
}

///|
async fn read_input() -> String {
  let bytes : Array[Byte] = []
  while @stdio.stdin.read_some(max_len=65536) is Some(chunk) {
    bytes.append(chunk.to_array())
  }
  let encoded = Bytes::from_array(bytes)
  @utf8.decode(encoded[:]) catch {
    _ => raise XargsError("input is not valid UTF-8")
  }
}

///|
async fn main {
  let args = @env.args()[1:]
  let mut null_mode = false
  let mut run_empty = false
  let mut verbose = false
  let mut max_args = 0
  let mut max_args_set = false
  let command : Array[String] = []
  let mut options = true
  let mut i = 0
  while i < args.length() {
    let arg = args[i]
    if options && arg == "--" {
      options = false
    } else if options && (arg == "-0" || arg == "--null") {
      null_mode = true
    } else if options && (arg == "-r" || arg == "--no-run-if-empty") {
      run_empty = true
    } else if options && (arg == "-t" || arg == "--verbose") {
      verbose = true
    } else if options && arg == "-n" {
      if i + 1 >= args.length() {
        @stdio.stderr.write("xargs: option requires an argument: -n\n")
        @sys.exit(1)
        return
      }
      i += 1
      max_args_set = true
      max_args = @strconv.from_str(args[i]) catch {
        _ => {
          @stdio.stderr.write("xargs: invalid max-args value\n")
          @sys.exit(1)
          return
        }
      }
    } else if options && arg.has_prefix("--max-args=") {
      max_args_set = true
      max_args = @strconv.from_str(arg[11:]) catch {
        _ => {
          @stdio.stderr.write("xargs: invalid max-args value\n")
          @sys.exit(1)
          return
        }
      }
    } else if options && arg.has_prefix("-") && arg != "-" {
      let mut valid = true
      for flag in arg[1:] {
        match flag {
          '0' => null_mode = true
          'r' => run_empty = true
          't' => verbose = true
          _ => valid = false
        }
      }
      if !valid {
        @stdio.stderr.write("xargs: unsupported option: \{arg}\n")
        @sys.exit(1)
        return
      }
    } else {
      command.push(arg)
    }
    i += 1
  }
  if max_args_set && max_args <= 0 {
    @stdio.stderr.write("xargs: max-args must be positive\n")
    @sys.exit(1)
    return
  }
  let values = try {
    let input = read_input()
    parse_words(input, null_mode)
  } catch {
    XargsError(message) => {
      @stdio.stderr.write("xargs: \{message}\n")
      @sys.exit(1)
      return
    }
    err => {
      @stdio.stderr.write("xargs: cannot read input: \{err}\n")
      @sys.exit(1)
      return
    }
  }
  let program = if command.is_empty() { "echo" } else { command[0] }
  let prefix : Array[String] = if command.is_empty() {
    []
  } else {
    command[1:].to_owned()
  }
  if values.is_empty() && run_empty {
    return
  }
  let max_batch_bytes = 64 * 1024
  let mut prefix_bytes = 0
  for argument in prefix {
    prefix_bytes += argument.length() + 1
  }
  if prefix_bytes >= max_batch_bytes {
    @stdio.stderr.write("xargs: command arguments exceed 64 KiB safety limit\n")
    @sys.exit(1)
    return
  }
  let mut offset = 0
  let context = @process.ExecutionContext::current()
  if values.is_empty() {
    let code = @process.run(@process.child(program, prefix, context~)) catch {
      @os_error.OSError(_) as err => {
        @stdio.stderr.write("xargs: cannot run '\{program}': \{err}\n")
        @sys.exit(if err.is_ENOENT() { 127 } else { 126 })
        return
      }
      err => {
        @stdio.stderr.write("xargs: cannot run '\{program}': \{err}\n")
        @sys.exit(126)
        return
      }
    }
    if code != 0 {
      @sys.exit(code)
    }
    return
  }
  while offset < values.length() {
    let mut end = offset
    let mut batch_bytes = prefix_bytes
    while end < values.length() && (max_args <= 0 || end - offset < max_args) {
      let argument_bytes = values[end].length() + 1
      if batch_bytes + argument_bytes > max_batch_bytes {
        break
      }
      batch_bytes += argument_bytes
      end += 1
    }
    if end == offset {
      @stdio.stderr.write("xargs: one argument exceeds 64 KiB safety limit\n")
      @sys.exit(1)
      return
    }
    let batch = prefix + values[offset:end].to_owned()
    if verbose {
      @stdio.stderr.write("\{program} \{batch.join(" ")}\n")
    }
    let code = @process.run(@process.child(program, batch, context~)) catch {
      @os_error.OSError(_) as err => {
        @stdio.stderr.write("xargs: cannot run '\{program}': \{err}\n")
        @sys.exit(if err.is_ENOENT() { 127 } else { 126 })
        return
      }
      err => {
        @stdio.stderr.write("xargs: cannot run '\{program}': \{err}\n")
        @sys.exit(126)
        return
      }
    }
    if code != 0 {
      @sys.exit(code)
      return
    }
    offset = end
  }
}