///|
fn pad_left(text : String, width : Int) -> String {
  let sb = StringBuilder()
  for _ in 0..<(width - text.length()) {
    sb.write_char(' ')
  }
  sb.write_string(text)
  sb.to_string()
}

///|
fn equal_line(left : Bytes, right : Bytes, ignore_case : Bool) -> Bool {
  if left.length() != right.length() {
    return false
  }
  for index = 0; index < left.length(); index = index + 1 {
    let l = left[index].to_int()
    let r = right[index].to_int()
    let l = if ignore_case && l >= 0x41 && l <= 0x5A { l + 0x20 } else { l }
    let r = if ignore_case && r >= 0x41 && r <= 0x5A { r + 0x20 } else { r }
    if l != r {
      return false
    }
  }
  true
}

///|
async fn write_group(
  line : Bytes,
  count : Int,
  show_count : Bool,
  only_repeated : Bool,
  only_unique : Bool,
) -> Unit {
  let selected = if only_repeated && only_unique {
    false
  } else if only_repeated {
    count > 1
  } else if only_unique {
    count == 1
  } else {
    true
  }
  if selected {
    if show_count {
      @stdio.stdout.write(pad_left(count.to_string(), 7) + " ")
    }
    @stdio.stdout.write(line)
    @stdio.stdout.write("\n")
  }
}

///|
async fn process_reader(
  reader : &@io.Reader,
  show_count : Bool,
  only_repeated : Bool,
  only_unique : Bool,
  ignore_case : Bool,
) -> Unit {
  let scanner = @stream.LineScanner::new(reader)
  let mut previous : Bytes? = None
  let mut count = 0
  while scanner.next() is Some(line) {
    match previous {
      Some(value) if equal_line(value, line.data, ignore_case) => count += 1
      Some(value) => {
        write_group(value, count, show_count, only_repeated, only_unique)
        previous = Some(line.data)
        count = 1
      }
      None => {
        previous = Some(line.data)
        count = 1
      }
    }
  }
  if previous is Some(value) {
    write_group(value, count, show_count, only_repeated, only_unique)
  }
}

///|
async fn main {
  let args = @env.args()[1:]
  let parsed = @cli.parse(args, [
    @cli.flag("count", short='c'),
    @cli.flag("repeated", short='d'),
    @cli.flag("unique", short='u'),
    @cli.flag("ignore-case", short='i'),
    @cli.flag("help"),
  ]) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("uniq: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write("Usage: uniq [-cdui] [FILE]\n")
    return
  }
  if parsed.operands.length() > 1 {
    @stdio.stderr.write("uniq: extra operand: '\{parsed.operands[1]}'\n")
    @sys.exit(2)
    return
  }
  let show_count = parsed.contains("count")
  let only_repeated = parsed.contains("repeated")
  let only_unique = parsed.contains("unique")
  let ignore_case = parsed.contains("ignore-case")
  let inputs = parsed.operands
  let path = if inputs.is_empty() { "-" } else { inputs[0] }
  try {
    if path == "-" {
      process_reader(
        @stdio.stdin, show_count, only_repeated, only_unique, ignore_case,
      )
    } else {
      let file = @fs.open(path, mode=ReadOnly)
      defer file.close()
      process_reader(file, show_count, only_repeated, only_unique, ignore_case)
    }
  } catch {
    err => {
      @stdio.stderr.write("uniq: \{err}\n")
      @sys.exit(1)
      return
    }
  }
}