///|
fn positive(value : String) -> Int raise {
  let result = @string.parse_int(value)
  guard result > 0 else { fail("invalid positive count: \{value}") }
  result
}

///|
fn byte_count(value : String) -> Int raise {
  let mut end = 0
  while end < value.length() && value[end] >= '0' && value[end] <= '9' {
    end += 1
  }
  guard end > 0 else { fail("invalid byte count: \{value}") }
  let count = positive(value[:end].to_owned())
  let suffix = value[end:].to_owned()
  let multiplier = match suffix {
    "" => 1
    "b" => 512
    "kB" => 1000
    "K" | "KiB" => 1024
    "MB" => 1000000
    "M" | "MiB" => 1048576
    "GB" => 1000000000
    "G" | "GiB" => 1073741824
    _ => fail("unsupported byte multiplier: \{suffix}")
  }
  guard count <= 2147483647 / multiplier else {
    fail("byte count is too large")
  }
  count * multiplier
}

///|
fn boundaries(data : Bytes, size : Int, lines : Bool) -> Array[(Int, Int)] {
  let result : Array[(Int, Int)] = []
  let mut start = 0
  let mut count = 0
  for index in 0.. Bool {
  let mut position = digits.length() - 1
  while position >= 0 {
    digits[position] += 1
    if digits[position] < radix {
      break
    }
    digits[position] = 0
    position -= 1
  }
  if position < 0 {
    return false
  }
  if automatic {
    let prefix_length = (digits.length() - 2) / 2
    if digits[prefix_length] == radix - 1 {
      digits.clear()
      for _ in 0..<(prefix_length + 1) {
        digits.push(radix - 1)
      }
      for _ in 0..<(prefix_length + 3) {
        digits.push(0)
      }
    }
  }
  true
}

///|
fn suffix_name(digits : Array[Int], numeric : Bool) -> String {
  let output = StringBuilder()
  let base = if numeric { 48 } else { 97 }
  for digit in digits {
    output.write_char((base + digit).unsafe_to_char())
  }
  output.to_string()
}

///|
fn help_message() -> String {
  let message =
    #|Usage: split [OPTION]... [FILE [PREFIX]]
    #|Split FILE (or stdin) into pieces named PREFIXaa, PREFIXab, ...
    #|The default PREFIX is x; the default piece size is 1000 lines.
    #|
    #|  -l, --lines=NUMBER        Put NUMBER lines in each output.
    #|  -b, --bytes=SIZE          Put SIZE bytes in each output (K, M, G, kB, MB, GB).
    #|  -a, --suffix-length=N     Use fixed-width suffixes of N characters.
    #|  -d, --numeric-suffixes    Use numeric suffixes starting at zero.
    #|      --additional-suffix=SUFFIX  Append SUFFIX after generated suffixes.
    #|      --help               display this help and exit
    #|      --version            display version and exit
    #|
    #|Existing output paths are refused. Chunk distribution, filters, and
    #|line-byte mode are unsupported.
  message
}

///|
async fn main {
  let metadata_args = @env.args()[1:]
  if metadata_args.length() == 1 && metadata_args[0] == "--version" {
    @stdio.stdout.write("split 0.1.1\n")
    return
  }
  let parsed = @cli.parse(@env.args()[1:], [
    @cli.option("lines", short='l'),
    @cli.option("bytes", short='b'),
    @cli.option("suffix-length", short='a'),
    @cli.flag("numeric-suffixes", short='d'),
    @cli.option("additional-suffix"),
    @cli.flag("help"),
    @cli.flag("version"),
  ]) catch {
    @cli.CliError(message~, option~, ..) => {
      @stdio.stderr.write("split: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(help_message() + "\n")
    return
  }
  if parsed.contains("version") {
    @stdio.stdout.write("split 0.1.1\n")
    return
  }
  let (size, lines, width, additional) = try {
    guard parsed.operands.length() <= 2 else { fail("too many operands") }
    guard !(parsed.contains("bytes") && parsed.contains("lines")) else {
      fail("cannot split in more than one way")
    }
    let lines = !parsed.contains("bytes")
    let size = if lines {
      positive(parsed.last_value("lines").unwrap_or("1000"))
    } else {
      byte_count(parsed.last_value("bytes").unwrap())
    }
    let width = positive(parsed.last_value("suffix-length").unwrap_or("2"))
    guard width <= 1024 else {
      fail("suffix length exceeds this build's limit of 1024")
    }
    let additional = parsed.last_value("additional-suffix").unwrap_or("")
    guard !additional.contains("/") && !additional.contains("\\") else {
      fail("additional suffix must not contain a path separator")
    }
    (size, lines, width, additional)
  } catch {
    err => {
      @stdio.stderr.write("split: \{err}\n")
      @sys.exit(2)
      return
    }
  }
  let input = parsed.operands.get(0).unwrap_or("-")
  let prefix = parsed.operands.get(1).unwrap_or("x")
  let numeric = parsed.contains("numeric-suffixes")
  try {
    let data = if input == "-" {
      @stdio.stdin.read_all().binary()
    } else {
      @fs.read_file(input).binary()
    }
    let chunks = boundaries(data, size, lines)
    let digits = Array::make(width, 0)
    for index, (start, end) in chunks {
      if index > 0 &&
        !advance_suffix(
          digits,
          if numeric {
            10
          } else {
            26
          },
          !parsed.contains("suffix-length"),
        ) {
        fail("output file suffixes exhausted")
      }
      let output = prefix + suffix_name(digits, numeric) + additional
      if @fs.exists(output) {
        fail("existing output is unsupported: \{output}")
      }
      @fs.write_file(output, data[start:end], create_mode=CreateNew)
    }
  } catch {
    err => {
      @stdio.stderr.write("split: \{err}\n")
      @sys.exit(1)
    }
  }
}