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

///|
let crc_polynomial : UInt64 = 0x04C11DB7UL

///|
let crc_mask : UInt64 = 0xFFFFFFFFUL

///|
fn crc_update(crc : UInt64, byte : Byte) -> UInt64 {
  let mut value = (crc ^ (byte.to_uint64() << 24)) & crc_mask
  for _ in 0..<8 {
    value = if (value & 0x80000000UL) != 0UL {
      ((value << 1) ^ crc_polynomial) & crc_mask
    } else {
      (value << 1) & crc_mask
    }
  }
  value
}

///|
fn crc_length(crc : UInt64, length : UInt64) -> UInt64 {
  let mut value = crc
  let mut remaining = length
  while remaining != 0UL {
    value = crc_update(value, (remaining & 0xFFUL).to_byte())
    remaining = remaining >> 8
  }
  value ^ crc_mask
}

///|
async fn checksum_reader(
  reader : &@io.Reader,
) -> (UInt64, UInt64) raise CksumError {
  let mut crc : UInt64 = 0UL
  let mut length : UInt64 = 0UL
  while (reader.read_some(max_len=65536) catch {
          err => raise CksumError("\{err}")
        })
        is Some(chunk) {
    length += chunk.length().to_uint64()
    for byte in chunk {
      crc = crc_update(crc, byte)
    }
  }
  (crc_length(crc, length), length)
}

///|
async fn checksum_source(path : String) -> (UInt64, UInt64) raise CksumError {
  if path == "-" {
    checksum_reader(@stdio.stdin)
  } else {
    let file = @fs.open(path, mode=ReadOnly) catch {
      err => raise CksumError("\{err}")
    }
    defer file.close()
    checksum_reader(file)
  }
}

///|
fn help_message() -> String {
  let message =
    #|Usage: cksum [FILE]...
    #|
    #|Print CRC checksum and byte count for each FILE.
    #|With no FILE, or when FILE is -, read standard input.
    #|      --help       display this help and exit
    #|      --version    display version and exit
    #|
    #|This release implements the POSIX CRC-32 form. GNU alternate
    #|algorithms and tagged output are not included in this strict subset.
  message
}

///|
async fn main {
  let parsed = @cli.parse(@env.args()[1:], [
    @cli.flag("help"),
    @cli.flag("version"),
  ]) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("cksum: \{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("cksum \{command_version}\n")
    return
  }
  if parsed.operands.is_empty() {
    let (checksum, length) = checksum_source("-") catch {
      CksumError(message) => {
        @stdio.stderr.write("cksum: standard input: \{message}\n")
        @sys.exit(1)
        return
      }
    }
    @stdio.stdout.write("\{checksum.to_string()} \{length.to_string()}\n")
    return
  }
  let sources = parsed.operands
  let mut failed = false
  for source in sources {
    let (checksum, length) = checksum_source(source) catch {
      CksumError(message) => {
        @stdio.stderr.write("cksum: '\{source}': \{message}\n")
        failed = true
        continue
      }
    }
    @stdio.stdout.write(
      "\{checksum.to_string()} \{length.to_string()} \{source}\n",
    )
  }
  if failed {
    @sys.exit(1)
  }
}