///|
/// Incremental RFC 1321 state; only one partial block is retained.
struct Md5 {
  h : Array[UInt32]
  pending : FixedArray[Byte]
  mut used : Int32
  words : Array[UInt32]
  mut length : Int64
}

///|
fn Md5::new() -> Md5 {
  {
    h: [0x67452301U, 0xefcdab89U, 0x98badcfeU, 0x10325476U],
    pending: FixedArray::make(64, 0),
    used: 0,
    words: Array::make(16, 0U),
    length: 0L,
  }
}

///|
// Generated by scripts/faster-than-libflac-md5-unroll.py from the archived RFC1321 tables.
// Private block contract: callers invoke only with exactly 64 pending bytes;
// new/digest allocate exactly 16 scratch words and four state words.
fn Md5::block(self : Md5, bytes : FixedArray[Byte]) -> Unit {
  let words = self.words
  for i in 0..<16 {
    let j = i * 4
    words.unsafe_set(
      i,
      bytes.unsafe_get(j).to_uint() |
      (bytes.unsafe_get(j + 1).to_uint() << 8) |
      (bytes.unsafe_get(j + 2).to_uint() << 16) |
      (bytes.unsafe_get(j + 3).to_uint() << 24),
    )
  }
  let mut a = self.h.unsafe_get(0)
  let mut b = self.h.unsafe_get(1)
  let mut c = self.h.unsafe_get(2)
  let mut d = self.h.unsafe_get(3)
  // RFC1321 operation 1: round 1, word 0, rotate 7.
  let t0 = a + ((b & c) | (b.lnot() & d)) + 0xd76aa478U + words.unsafe_get(0)
  a = b + ((t0 << 7) | (t0 >> 25))
  // RFC1321 operation 2: round 1, word 1, rotate 12.
  let t1 = d + ((a & b) | (a.lnot() & c)) + 0xe8c7b756U + words.unsafe_get(1)
  d = a + ((t1 << 12) | (t1 >> 20))
  // RFC1321 operation 3: round 1, word 2, rotate 17.
  let t2 = c + ((d & a) | (d.lnot() & b)) + 0x242070dbU + words.unsafe_get(2)
  c = d + ((t2 << 17) | (t2 >> 15))
  // RFC1321 operation 4: round 1, word 3, rotate 22.
  let t3 = b + ((c & d) | (c.lnot() & a)) + 0xc1bdceeeU + words.unsafe_get(3)
  b = c + ((t3 << 22) | (t3 >> 10))
  // RFC1321 operation 5: round 1, word 4, rotate 7.
  let t4 = a + ((b & c) | (b.lnot() & d)) + 0xf57c0fafU + words.unsafe_get(4)
  a = b + ((t4 << 7) | (t4 >> 25))
  // RFC1321 operation 6: round 1, word 5, rotate 12.
  let t5 = d + ((a & b) | (a.lnot() & c)) + 0x4787c62aU + words.unsafe_get(5)
  d = a + ((t5 << 12) | (t5 >> 20))
  // RFC1321 operation 7: round 1, word 6, rotate 17.
  let t6 = c + ((d & a) | (d.lnot() & b)) + 0xa8304613U + words.unsafe_get(6)
  c = d + ((t6 << 17) | (t6 >> 15))
  // RFC1321 operation 8: round 1, word 7, rotate 22.
  let t7 = b + ((c & d) | (c.lnot() & a)) + 0xfd469501U + words.unsafe_get(7)
  b = c + ((t7 << 22) | (t7 >> 10))
  // RFC1321 operation 9: round 1, word 8, rotate 7.
  let t8 = a + ((b & c) | (b.lnot() & d)) + 0x698098d8U + words.unsafe_get(8)
  a = b + ((t8 << 7) | (t8 >> 25))
  // RFC1321 operation 10: round 1, word 9, rotate 12.
  let t9 = d + ((a & b) | (a.lnot() & c)) + 0x8b44f7afU + words.unsafe_get(9)
  d = a + ((t9 << 12) | (t9 >> 20))
  // RFC1321 operation 11: round 1, word 10, rotate 17.
  let t10 = c + ((d & a) | (d.lnot() & b)) + 0xffff5bb1U + words.unsafe_get(10)
  c = d + ((t10 << 17) | (t10 >> 15))
  // RFC1321 operation 12: round 1, word 11, rotate 22.
  let t11 = b + ((c & d) | (c.lnot() & a)) + 0x895cd7beU + words.unsafe_get(11)
  b = c + ((t11 << 22) | (t11 >> 10))
  // RFC1321 operation 13: round 1, word 12, rotate 7.
  let t12 = a + ((b & c) | (b.lnot() & d)) + 0x6b901122U + words.unsafe_get(12)
  a = b + ((t12 << 7) | (t12 >> 25))
  // RFC1321 operation 14: round 1, word 13, rotate 12.
  let t13 = d + ((a & b) | (a.lnot() & c)) + 0xfd987193U + words.unsafe_get(13)
  d = a + ((t13 << 12) | (t13 >> 20))
  // RFC1321 operation 15: round 1, word 14, rotate 17.
  let t14 = c + ((d & a) | (d.lnot() & b)) + 0xa679438eU + words.unsafe_get(14)
  c = d + ((t14 << 17) | (t14 >> 15))
  // RFC1321 operation 16: round 1, word 15, rotate 22.
  let t15 = b + ((c & d) | (c.lnot() & a)) + 0x49b40821U + words.unsafe_get(15)
  b = c + ((t15 << 22) | (t15 >> 10))
  // RFC1321 operation 17: round 2, word 1, rotate 5.
  let t16 = a + ((d & b) | (d.lnot() & c)) + 0xf61e2562U + words.unsafe_get(1)
  a = b + ((t16 << 5) | (t16 >> 27))
  // RFC1321 operation 18: round 2, word 6, rotate 9.
  let t17 = d + ((c & a) | (c.lnot() & b)) + 0xc040b340U + words.unsafe_get(6)
  d = a + ((t17 << 9) | (t17 >> 23))
  // RFC1321 operation 19: round 2, word 11, rotate 14.
  let t18 = c + ((b & d) | (b.lnot() & a)) + 0x265e5a51U + words.unsafe_get(11)
  c = d + ((t18 << 14) | (t18 >> 18))
  // RFC1321 operation 20: round 2, word 0, rotate 20.
  let t19 = b + ((a & c) | (a.lnot() & d)) + 0xe9b6c7aaU + words.unsafe_get(0)
  b = c + ((t19 << 20) | (t19 >> 12))
  // RFC1321 operation 21: round 2, word 5, rotate 5.
  let t20 = a + ((d & b) | (d.lnot() & c)) + 0xd62f105dU + words.unsafe_get(5)
  a = b + ((t20 << 5) | (t20 >> 27))
  // RFC1321 operation 22: round 2, word 10, rotate 9.
  let t21 = d + ((c & a) | (c.lnot() & b)) + 0x02441453U + words.unsafe_get(10)
  d = a + ((t21 << 9) | (t21 >> 23))
  // RFC1321 operation 23: round 2, word 15, rotate 14.
  let t22 = c + ((b & d) | (b.lnot() & a)) + 0xd8a1e681U + words.unsafe_get(15)
  c = d + ((t22 << 14) | (t22 >> 18))
  // RFC1321 operation 24: round 2, word 4, rotate 20.
  let t23 = b + ((a & c) | (a.lnot() & d)) + 0xe7d3fbc8U + words.unsafe_get(4)
  b = c + ((t23 << 20) | (t23 >> 12))
  // RFC1321 operation 25: round 2, word 9, rotate 5.
  let t24 = a + ((d & b) | (d.lnot() & c)) + 0x21e1cde6U + words.unsafe_get(9)
  a = b + ((t24 << 5) | (t24 >> 27))
  // RFC1321 operation 26: round 2, word 14, rotate 9.
  let t25 = d + ((c & a) | (c.lnot() & b)) + 0xc33707d6U + words.unsafe_get(14)
  d = a + ((t25 << 9) | (t25 >> 23))
  // RFC1321 operation 27: round 2, word 3, rotate 14.
  let t26 = c + ((b & d) | (b.lnot() & a)) + 0xf4d50d87U + words.unsafe_get(3)
  c = d + ((t26 << 14) | (t26 >> 18))
  // RFC1321 operation 28: round 2, word 8, rotate 20.
  let t27 = b + ((a & c) | (a.lnot() & d)) + 0x455a14edU + words.unsafe_get(8)
  b = c + ((t27 << 20) | (t27 >> 12))
  // RFC1321 operation 29: round 2, word 13, rotate 5.
  let t28 = a + ((d & b) | (d.lnot() & c)) + 0xa9e3e905U + words.unsafe_get(13)
  a = b + ((t28 << 5) | (t28 >> 27))
  // RFC1321 operation 30: round 2, word 2, rotate 9.
  let t29 = d + ((c & a) | (c.lnot() & b)) + 0xfcefa3f8U + words.unsafe_get(2)
  d = a + ((t29 << 9) | (t29 >> 23))
  // RFC1321 operation 31: round 2, word 7, rotate 14.
  let t30 = c + ((b & d) | (b.lnot() & a)) + 0x676f02d9U + words.unsafe_get(7)
  c = d + ((t30 << 14) | (t30 >> 18))
  // RFC1321 operation 32: round 2, word 12, rotate 20.
  let t31 = b + ((a & c) | (a.lnot() & d)) + 0x8d2a4c8aU + words.unsafe_get(12)
  b = c + ((t31 << 20) | (t31 >> 12))
  // RFC1321 operation 33: round 3, word 5, rotate 4.
  let t32 = a + (b ^ c ^ d) + 0xfffa3942U + words.unsafe_get(5)
  a = b + ((t32 << 4) | (t32 >> 28))
  // RFC1321 operation 34: round 3, word 8, rotate 11.
  let t33 = d + (a ^ b ^ c) + 0x8771f681U + words.unsafe_get(8)
  d = a + ((t33 << 11) | (t33 >> 21))
  // RFC1321 operation 35: round 3, word 11, rotate 16.
  let t34 = c + (d ^ a ^ b) + 0x6d9d6122U + words.unsafe_get(11)
  c = d + ((t34 << 16) | (t34 >> 16))
  // RFC1321 operation 36: round 3, word 14, rotate 23.
  let t35 = b + (c ^ d ^ a) + 0xfde5380cU + words.unsafe_get(14)
  b = c + ((t35 << 23) | (t35 >> 9))
  // RFC1321 operation 37: round 3, word 1, rotate 4.
  let t36 = a + (b ^ c ^ d) + 0xa4beea44U + words.unsafe_get(1)
  a = b + ((t36 << 4) | (t36 >> 28))
  // RFC1321 operation 38: round 3, word 4, rotate 11.
  let t37 = d + (a ^ b ^ c) + 0x4bdecfa9U + words.unsafe_get(4)
  d = a + ((t37 << 11) | (t37 >> 21))
  // RFC1321 operation 39: round 3, word 7, rotate 16.
  let t38 = c + (d ^ a ^ b) + 0xf6bb4b60U + words.unsafe_get(7)
  c = d + ((t38 << 16) | (t38 >> 16))
  // RFC1321 operation 40: round 3, word 10, rotate 23.
  let t39 = b + (c ^ d ^ a) + 0xbebfbc70U + words.unsafe_get(10)
  b = c + ((t39 << 23) | (t39 >> 9))
  // RFC1321 operation 41: round 3, word 13, rotate 4.
  let t40 = a + (b ^ c ^ d) + 0x289b7ec6U + words.unsafe_get(13)
  a = b + ((t40 << 4) | (t40 >> 28))
  // RFC1321 operation 42: round 3, word 0, rotate 11.
  let t41 = d + (a ^ b ^ c) + 0xeaa127faU + words.unsafe_get(0)
  d = a + ((t41 << 11) | (t41 >> 21))
  // RFC1321 operation 43: round 3, word 3, rotate 16.
  let t42 = c + (d ^ a ^ b) + 0xd4ef3085U + words.unsafe_get(3)
  c = d + ((t42 << 16) | (t42 >> 16))
  // RFC1321 operation 44: round 3, word 6, rotate 23.
  let t43 = b + (c ^ d ^ a) + 0x04881d05U + words.unsafe_get(6)
  b = c + ((t43 << 23) | (t43 >> 9))
  // RFC1321 operation 45: round 3, word 9, rotate 4.
  let t44 = a + (b ^ c ^ d) + 0xd9d4d039U + words.unsafe_get(9)
  a = b + ((t44 << 4) | (t44 >> 28))
  // RFC1321 operation 46: round 3, word 12, rotate 11.
  let t45 = d + (a ^ b ^ c) + 0xe6db99e5U + words.unsafe_get(12)
  d = a + ((t45 << 11) | (t45 >> 21))
  // RFC1321 operation 47: round 3, word 15, rotate 16.
  let t46 = c + (d ^ a ^ b) + 0x1fa27cf8U + words.unsafe_get(15)
  c = d + ((t46 << 16) | (t46 >> 16))
  // RFC1321 operation 48: round 3, word 2, rotate 23.
  let t47 = b + (c ^ d ^ a) + 0xc4ac5665U + words.unsafe_get(2)
  b = c + ((t47 << 23) | (t47 >> 9))
  // RFC1321 operation 49: round 4, word 0, rotate 6.
  let t48 = a + (c ^ (b | d.lnot())) + 0xf4292244U + words.unsafe_get(0)
  a = b + ((t48 << 6) | (t48 >> 26))
  // RFC1321 operation 50: round 4, word 7, rotate 10.
  let t49 = d + (b ^ (a | c.lnot())) + 0x432aff97U + words.unsafe_get(7)
  d = a + ((t49 << 10) | (t49 >> 22))
  // RFC1321 operation 51: round 4, word 14, rotate 15.
  let t50 = c + (a ^ (d | b.lnot())) + 0xab9423a7U + words.unsafe_get(14)
  c = d + ((t50 << 15) | (t50 >> 17))
  // RFC1321 operation 52: round 4, word 5, rotate 21.
  let t51 = b + (d ^ (c | a.lnot())) + 0xfc93a039U + words.unsafe_get(5)
  b = c + ((t51 << 21) | (t51 >> 11))
  // RFC1321 operation 53: round 4, word 12, rotate 6.
  let t52 = a + (c ^ (b | d.lnot())) + 0x655b59c3U + words.unsafe_get(12)
  a = b + ((t52 << 6) | (t52 >> 26))
  // RFC1321 operation 54: round 4, word 3, rotate 10.
  let t53 = d + (b ^ (a | c.lnot())) + 0x8f0ccc92U + words.unsafe_get(3)
  d = a + ((t53 << 10) | (t53 >> 22))
  // RFC1321 operation 55: round 4, word 10, rotate 15.
  let t54 = c + (a ^ (d | b.lnot())) + 0xffeff47dU + words.unsafe_get(10)
  c = d + ((t54 << 15) | (t54 >> 17))
  // RFC1321 operation 56: round 4, word 1, rotate 21.
  let t55 = b + (d ^ (c | a.lnot())) + 0x85845dd1U + words.unsafe_get(1)
  b = c + ((t55 << 21) | (t55 >> 11))
  // RFC1321 operation 57: round 4, word 8, rotate 6.
  let t56 = a + (c ^ (b | d.lnot())) + 0x6fa87e4fU + words.unsafe_get(8)
  a = b + ((t56 << 6) | (t56 >> 26))
  // RFC1321 operation 58: round 4, word 15, rotate 10.
  let t57 = d + (b ^ (a | c.lnot())) + 0xfe2ce6e0U + words.unsafe_get(15)
  d = a + ((t57 << 10) | (t57 >> 22))
  // RFC1321 operation 59: round 4, word 6, rotate 15.
  let t58 = c + (a ^ (d | b.lnot())) + 0xa3014314U + words.unsafe_get(6)
  c = d + ((t58 << 15) | (t58 >> 17))
  // RFC1321 operation 60: round 4, word 13, rotate 21.
  let t59 = b + (d ^ (c | a.lnot())) + 0x4e0811a1U + words.unsafe_get(13)
  b = c + ((t59 << 21) | (t59 >> 11))
  // RFC1321 operation 61: round 4, word 4, rotate 6.
  let t60 = a + (c ^ (b | d.lnot())) + 0xf7537e82U + words.unsafe_get(4)
  a = b + ((t60 << 6) | (t60 >> 26))
  // RFC1321 operation 62: round 4, word 11, rotate 10.
  let t61 = d + (b ^ (a | c.lnot())) + 0xbd3af235U + words.unsafe_get(11)
  d = a + ((t61 << 10) | (t61 >> 22))
  // RFC1321 operation 63: round 4, word 2, rotate 15.
  let t62 = c + (a ^ (d | b.lnot())) + 0x2ad7d2bbU + words.unsafe_get(2)
  c = d + ((t62 << 15) | (t62 >> 17))
  // RFC1321 operation 64: round 4, word 9, rotate 21.
  let t63 = b + (d ^ (c | a.lnot())) + 0xeb86d391U + words.unsafe_get(9)
  b = c + ((t63 << 21) | (t63 >> 11))
  self.h.unsafe_set(0, self.h.unsafe_get(0) + a)
  self.h.unsafe_set(1, self.h.unsafe_get(1) + b)
  self.h.unsafe_set(2, self.h.unsafe_get(2) + c)
  self.h.unsafe_set(3, self.h.unsafe_get(3) + d)
}

///|
/// Bulk-copy input into one fixed 64-byte block. used is always in 0..63 on
/// entry/exit; only a fully initialized 64-byte block reaches compression.
fn Md5::update(self : Md5, bytes : Bytes) -> Unit {
  self.length += bytes.length().to_int64()
  let mut cursor = 0
  while cursor < bytes.length() {
    let room = 64 - self.used
    let remaining = bytes.length() - cursor
    let count = if remaining < room { remaining } else { room }
    self.pending.blit_from_bytes(self.used, bytes, cursor, count)
    self.used += count
    cursor += count
    if self.used == 64 {
      self.block(self.pending)
      self.used = 0
    }
  }
}

///|
/// Finalization operates on a copy, so callers may keep appending afterward.
fn Md5::digest(self : Md5) -> Bytes {
  let copy : Md5 = {
    h: self.h.copy(),
    pending: self.pending.copy(),
    used: self.used,
    words: Array::make(16, 0U),
    length: self.length,
  }
  let bit_length = self.length << 3
  let padding = if self.used < 56 { 56 - self.used } else { 120 - self.used }
  copy.update(Bytes::makei(padding, i => if i == 0 { 0x80 } else { 0 }))
  copy.update(
    Bytes::makei(8, i => ((bit_length >> (i * 8)) & 255L).to_int().to_byte()),
  )
  Bytes::makei(16, i => ((copy.h[i / 4] >> (i % 4 * 8)) & 255U).to_byte())
}

///|
/// Width-specialized signed little-endian stores preserve arbitrary prior
/// update alignment. The rare crossing path works for every width, including
/// 24-bit PCM and a nonaligned prefix before 16/32-bit PCM.
fn md5_update_frame(state : Md5, frame : Frame) -> Unit {
  let width = (frame.bits_per_sample + 7) / 8
  state.length += (frame.block_size * frame.channels.length() * width).to_int64()
  match width {
    1 =>
      for i in 0..> 0) & 255).to_byte())
          state.used = used + 1
          if state.used == 64 {
            state.block(state.pending)
            state.used = 0
          }
        }
      }
    2 =>
      for i in 0..> 0) & 255).to_byte())
            state.pending.unsafe_set(used + 1, ((sample >> 8) & 255).to_byte())
            state.used = used + 2
            if state.used == 64 {
              state.block(state.pending)
              state.used = 0
            }
          } else {
            for byte in 0..<2 {
              state.pending.unsafe_set(
                state.used,
                ((sample >> (byte * 8)) & 255).to_byte(),
              )
              state.used += 1
              if state.used == 64 {
                state.block(state.pending)
                state.used = 0
              }
            }
          }
        }
      }
    3 =>
      for i in 0..> 0) & 255).to_byte())
            state.pending.unsafe_set(used + 1, ((sample >> 8) & 255).to_byte())
            state.pending.unsafe_set(used + 2, ((sample >> 16) & 255).to_byte())
            state.used = used + 3
            if state.used == 64 {
              state.block(state.pending)
              state.used = 0
            }
          } else {
            for byte in 0..<3 {
              state.pending.unsafe_set(
                state.used,
                ((sample >> (byte * 8)) & 255).to_byte(),
              )
              state.used += 1
              if state.used == 64 {
                state.block(state.pending)
                state.used = 0
              }
            }
          }
        }
      }
    4 =>
      for i in 0..> 0) & 255).to_byte())
            state.pending.unsafe_set(used + 1, ((sample >> 8) & 255).to_byte())
            state.pending.unsafe_set(used + 2, ((sample >> 16) & 255).to_byte())
            state.pending.unsafe_set(used + 3, ((sample >> 24) & 255).to_byte())
            state.used = used + 4
            if state.used == 64 {
              state.block(state.pending)
              state.used = 0
            }
          } else {
            for byte in 0..<4 {
              state.pending.unsafe_set(
                state.used,
                ((sample >> (byte * 8)) & 255).to_byte(),
              )
              state.used += 1
              if state.used == 64 {
                state.block(state.pending)
                state.used = 0
              }
            }
          }
        }
      }
    _ => abort("invalid internal MD5 PCM byte width")
  }
}