///|
/// Incremental SHA-256 calculator for HTTP message content.
pub(all) struct Sha256Stream {
  inner : @sha256.Digest
  mut bytes_written : Int
  mut finalized : Bool
}

///|
pub fn Sha256Stream::new() -> Sha256Stream {
  { inner: @sha256.Digest::new(), bytes_written: 0, finalized: false, }
}

///|
/// Add the next body chunk. Updating a finalized stream is rejected.
pub fn Sha256Stream::update(
  self : Sha256Stream,
  chunk : Bytes,
) -> Unit raise DigestFieldError {
  if self.finalized {
    raise State("STREAM_ALREADY_FINALIZED")
  }
  for byte in chunk {
    self.inner.write(byte)
  }
  self.bytes_written += chunk.length()
}

///|
/// Finalize and return the raw 32-byte SHA-256 digest.
pub fn Sha256Stream::finish(
  self : Sha256Stream,
) -> Bytes raise DigestFieldError {
  if self.finalized {
    raise State("STREAM_ALREADY_FINALIZED")
  }
  self.finalized = true
  hex_to_bytes(self.inner.check_sum())
}

///|
/// Compute SHA-256 for one byte string.
pub fn sha256(input : Bytes) -> Bytes raise DigestFieldError {
  let stream = Sha256Stream::new()
  stream.update(input)
  stream.finish()
}

///|
/// Compute SHA-256 over the UTF-8 bytes of a string.
pub fn sha256_utf8(input : String) -> Bytes raise DigestFieldError {
  sha256(@base64.str2bytes(input))
}

///|
/// Build a one-member DigestField using the active sha-256 algorithm.
pub fn sha256_digest_field(input : Bytes) -> DigestField raise DigestFieldError {
  { members: [{ algorithm: "sha-256", value: sha256(input), }], }
}

///|
/// Produce the field value used by Content-Digest or Repr-Digest.
pub fn content_digest_sha256(input : Bytes) -> String raise DigestFieldError {
  sha256_digest_field(input).to_string()
}

///|
/// Incremental binary-safe SHA-512 calculator.
pub(all) struct Sha512Stream {
  inner : @sha2.Sha512
  mut bytes_written : Int
  mut finalized : Bool
}

///|
pub fn Sha512Stream::new() -> Sha512Stream {
  { inner: @sha2.Sha512::new(), bytes_written: 0, finalized: false, }
}

///|
/// Add a body chunk without dropping embedded zero bytes.
pub fn Sha512Stream::update(
  self : Sha512Stream,
  chunk : Bytes,
) -> Unit raise DigestFieldError {
  if self.finalized {
    raise State("STREAM_ALREADY_FINALIZED")
  }
  let values : Array[UInt] = []
  for byte in chunk {
    values.push(byte.to_int().reinterpret_as_uint())
  }
  self.inner.update(values)
  self.bytes_written += chunk.length()
}

///|
/// Finalize and convert eight big-endian words to the RFC byte sequence.
pub fn Sha512Stream::finish(
  self : Sha512Stream,
) -> Bytes raise DigestFieldError {
  if self.finalized {
    raise State("STREAM_ALREADY_FINALIZED")
  }
  self.finalized = true
  let words = self.inner.finalize()
  let output : Array[Byte] = []
  for word in words {
    for shift in [56, 48, 40, 32, 24, 16, 8, 0] {
      output.push(((word >> shift) & 0xffUL).to_int().to_byte())
    }
  }
  Bytes::from_array(output)
}

///|
/// Compute binary-safe SHA-512.
pub fn sha512(input : Bytes) -> Bytes raise DigestFieldError {
  let stream = Sha512Stream::new()
  stream.update(input)
  stream.finish()
}

///|
/// Compute SHA-512 over UTF-8 string bytes.
pub fn sha512_utf8(input : String) -> Bytes raise DigestFieldError {
  sha512(@base64.str2bytes(input))
}

///|
/// Build a one-member DigestField using sha-512.
pub fn sha512_digest_field(input : Bytes) -> DigestField raise DigestFieldError {
  { members: [{ algorithm: "sha-512", value: sha512(input), }], }
}

///|
/// Produce a sha-512 field value.
pub fn content_digest_sha512(input : Bytes) -> String raise DigestFieldError {
  sha512_digest_field(input).to_string()
}

///|
/// Generate a multi-algorithm field in canonical order.
pub fn digest_field_for(
  input : Bytes,
  algorithms : Array[String],
) -> DigestField raise DigestFieldError {
  let members : Array[DigestMember] = []
  for requested in algorithms {
    let algorithm = ascii_lower(requested)
    if digest_member_index(members, algorithm) >= 0 {
      raise Policy("DUPLICATE_ALGORITHM", 0)
    }
    let value = match algorithm {
      "sha-256" => sha256(input)
      "sha-512" => sha512(input)
      _ => raise Policy("UNSUPPORTED_GENERATION_ALGORITHM", 0)
    }
    members.push({ algorithm, value, })
  }
  if members.length() == 0 {
    raise Policy("ALGORITHM_REQUIRED", 0)
  }
  { members, }
}