///|
fn starts_with_at(data : Bytes, index : Int32, separator : Bytes) -> Bool {
  if index + separator.length() > data.length() {
    return false
  }
  for offset in 0.. Bytes {
  let output : Array[Byte] = []
  let text = if regex is Some(_) { @bytere.text(data) } else { "" }
  let mut search_end = data.length()
  let mut past_end = data.length()
  let mut first = true
  while search_end > 0 {
    let match_span = if regex is Some(pattern) {
      pattern.rfind_span(text, search_end)
    } else {
      let mut position = search_end - separator.length()
      while position >= 0 && !starts_with_at(data, position, separator) {
        position -= 1
      }
      if position < 0 {
        None
      } else {
        Some((position, position + separator.length()))
      }
    }
    guard match_span is Some((start, end)) else { break }
    let boundary = if before { start } else { end }
    if before || !first || boundary != past_end {
      output.append(data[boundary:past_end].to_array())
    }
    past_end = boundary
    search_end = start
    first = false
  }
  output.append(data[:past_end].to_array())
  Bytes::from_array(output)
}

///|
fn help_message() -> String {
  let message =
    #|Usage: tac [OPTION]... [FILE]...
    #|      --help      display this help and exit
    #|      --version   display version and exit
    #|
    #|Write each FILE to standard output, last line first.
    #|With no FILE, or when FILE is -, read standard input.
    #|
    #|  -b, --before             Attach the separator before records.
    #|  -s, --separator=STRING   Use STRING instead of newline.
    #|  -r, --regex              Use an Emacs-style byte regular expression.
  message
}

///|
async fn main {
  let metadata_args = @env.args()[1:]
  if metadata_args.length() == 1 && metadata_args[0] == "--version" {
    @stdio.stdout.write("tac 0.1.3\n")
    return
  }
  let parsed = @cli.parse(@env.args()[1:], [
    @cli.flag("before", short='b'),
    @cli.option("separator", short='s'),
    @cli.flag("regex", short='r'),
    @cli.flag("help"),
  ]) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("tac: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(help_message() + "\n")
    return
  }
  let value = parsed.last_value("separator").unwrap_or("\n")
  if parsed.contains("regex") && value.is_empty() {
    @stdio.stderr.write("tac: separator cannot be empty\n")
    @sys.exit(1)
    return
  }
  let regex = try {
    if parsed.contains("regex") {
      Some(@bytere.compile(@bytere.argument(value), emacs=true))
    } else {
      None
    }
  } catch {
    @bytere.PatternError(message) => {
      let message = match message {
        "unmatched opening parenthesis" => "Unmatched ( or \\("
        "unmatched closing parenthesis" => "Unmatched ) or \\)"
        "unterminated bracket expression" => "Unmatched [, [^, [:, [., or [="
        "trailing backslash in regular expression" => "Trailing backslash"
        "invalid backreference" => "Invalid back reference"
        _ => message
      }
      @stdio.stderr.write("tac: " + message + "\n")
      @sys.exit(1)
      return
    }
    error => {
      @stdio.stderr.write("tac: " + error.to_string() + "\n")
      @sys.exit(1)
      return
    }
  }
  let separator = if value.is_empty() { b"\x00" } else { @utf8.encode(value) }
  let paths = if parsed.operands.is_empty() { ["-"] } else { parsed.operands }
  let mut status = 0
  for path in paths {
    let mut opened = path == "-"
    try {
      let data = if path == "-" {
        @stdio.stdin.read_all().binary()
      } else {
        let file = @fs.open(path, mode=ReadOnly)
        opened = true
        defer file.close()
        if file.kind() == @fs.FileKind::Directory {
          @stdio.stderr.write("tac: " + path + ": read error: Is a directory\n")
          status = 1
          continue
        }
        file.read_all().binary()
      }
      @stdio.stdout.write(
        reverse_records(data, separator, parsed.contains("before"), regex~),
      )
    } catch {
      error => {
        let message = error_message(error)
        if path == "-" {
          @stdio.stderr.write(
            "tac: 'standard input': read error: " +
            message +
            "\ntac: -: " +
            message +
            "\n",
          )
        } else if opened {
          @stdio.stderr.write(
            "tac: " + path + ": read error: " + message + "\n",
          )
        } else {
          @stdio.stderr.write(
            "tac: failed to open '" +
            path.replace_all(old="'", new="'\\''") +
            "' for reading: " +
            message +
            "\n",
          )
        }
        status = 1
      }
    }
  }
  if status != 0 {
    @sys.exit(status)
  }
}

///|
fn error_message(error : Error) -> String {
  match error {
    @os_error.OSError(code, ..) as error =>
      if error.is_ENOENT() {
        "No such file or directory"
      } else if error.is_EACCES() {
        "Permission denied"
      } else if error.is_ENOTDIR() {
        "Not a directory"
      } else if !@platform.is_windows() && code == 21 {
        "Is a directory"
      } else if !@platform.is_windows() && code == 9 {
        "Bad file descriptor"
      } else {
        error.to_string()
      }
    _ => error.to_string()
  }
}