// SPDX-License-Identifier: GPL-2.0-only
// MD5 (RFC 1321) and SHA-1 (RFC 3174) digests, ported for the checksum
// algorithm set of boofuzz/blocks/checksum.py at 518c139. Pure core; only
// the digest bytes are produced — the big-endian word swap upstream applies
// to md5/sha1 happens in the checksum node rendering.

///|
/// MD5 digest (16 bytes) of the input.
pub fn md5(message : Bytes) -> Bytes {
  // Padding: 0x80, zeros, 64-bit little-endian bit length.
  let bit_length = message.length().to_int64() * 8L
  let padded = message.length() +
    1 +
    ((55 - message.length()) % 64 + 64) % 64 +
    8
  let buffer : Array[Byte] = []
  for byte in message {
    buffer.push(byte)
  }
  buffer.push(0x80)
  while buffer.length() % 64 != 56 {
    buffer.push(0)
  }
  for i in 0..<8 {
    buffer.push(((bit_length >> (i * 8)) & 0xffL).to_byte())
  }
  ignore(padded)
  let mut a0 = 0x67452301U
  let mut b0 = 0xefcdab89U
  let mut c0 = 0x98badcfeU
  let mut d0 = 0x10325476U
  let shifts : Array[Int] = [
    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5,
    9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16,
    23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
  ]
  let constants : Array[UInt] = [
    0xd76aa478U, 0xe8c7b756U, 0x242070dbU, 0xc1bdceeeU, 0xf57c0fafU, 0x4787c62aU,
    0xa8304613U, 0xfd469501U, 0x698098d8U, 0x8b44f7afU, 0xffff5bb1U, 0x895cd7beU,
    0x6b901122U, 0xfd987193U, 0xa679438eU, 0x49b40821U, 0xf61e2562U, 0xc040b340U,
    0x265e5a51U, 0xe9b6c7aaU, 0xd62f105dU, 0x02441453U, 0xd8a1e681U, 0xe7d3fbc8U,
    0x21e1cde6U, 0xc33707d6U, 0xf4d50d87U, 0x455a14edU, 0xa9e3e905U, 0xfcefa3f8U,
    0x676f02d9U, 0x8d2a4c8aU, 0xfffa3942U, 0x8771f681U, 0x6d9d6122U, 0xfde5380cU,
    0xa4beea44U, 0x4bdecfa9U, 0xf6bb4b60U, 0xbebfbc70U, 0x289b7ec6U, 0xeaa127faU,
    0xd4ef3085U, 0x04881d05U, 0xd9d4d039U, 0xe6db99e5U, 0x1fa27cf8U, 0xc4ac5665U,
    0xf4292244U, 0x432aff97U, 0xab9423a7U, 0xfc93a039U, 0x655b59c3U, 0x8f0ccc92U,
    0xffeff47dU, 0x85845dd1U, 0x6fa87e4fU, 0xfe2ce6e0U, 0xa3014314U, 0x4e0811a1U,
    0xf7537e82U, 0xbd3af235U, 0x2ad7d2bbU, 0xeb86d391U,
  ]
  for chunk in 0..<(buffer.length() / 64) {
    let words : Array[UInt] = Array::make(16, 0U)
    for w in 0..<16 {
      let mut value = 0U
      for b in 0..<4 {
        value = value | (buffer[chunk * 64 + w * 4 + b].to_uint() << (b * 8))
      }
      words[w] = value
    }
    let mut a = a0
    let mut b = b0
    let mut c = c0
    let mut d = d0
    for i in 0..<64 {
      let (f, g) = match i {
        0..<16 =>
          (
            (b & c) | (b.reinterpret_as_int().lnot().reinterpret_as_uint() & d),
            i,
          )
        16..<32 =>
          (
            (d & b) | (d.reinterpret_as_int().lnot().reinterpret_as_uint() & c),
            (5 * i + 1) % 16,
          )
        32..<48 => (b ^ c ^ d, (3 * i + 5) % 16)
        _ =>
          (
            c ^ (b | d.reinterpret_as_int().lnot().reinterpret_as_uint()),
            7 * i % 16,
          )
      }
      let f2 = f + a + constants[i] + words[g]
      a = d
      d = c
      c = b
      let shift = shifts[i]
      b = b + ((f2 << shift) | (f2 >> (32 - shift)))
    }
    a0 = a0 + a
    b0 = b0 + b
    c0 = c0 + c
    d0 = d0 + d
  }
  digest_bytes([a0, b0, c0, d0], Little)
}

///|
/// SHA-1 digest (20 bytes) of the input.
pub fn sha1(message : Bytes) -> Bytes {
  let bit_length = message.length().to_int64() * 8L
  let buffer : Array[Byte] = []
  for byte in message {
    buffer.push(byte)
  }
  buffer.push(0x80)
  while buffer.length() % 64 != 56 {
    buffer.push(0)
  }
  for i in 0..<8 {
    buffer.push(((bit_length >> (56 - i * 8)) & 0xffL).to_byte())
  }
  let mut h0 = 0x67452301U
  let mut h1 = 0xefcdab89U
  let mut h2 = 0x98badcfeU
  let mut h3 = 0x10325476U
  let mut h4 = 0xc3d2e1f0U
  for chunk in 0..<(buffer.length() / 64) {
    let words : Array[UInt] = Array::make(80, 0U)
    for w in 0..<16 {
      let mut value = 0U
      for b in 0..<4 {
        value = (value << 8) | buffer[chunk * 64 + w * 4 + b].to_uint()
      }
      words[w] = value
    }
    for w in 16..<80 {
      let mixed = words[w - 3] ^ words[w - 8] ^ words[w - 14] ^ words[w - 16]
      words[w] = (mixed << 1) | (mixed >> 31)
    }
    let mut a = h0
    let mut b = h1
    let mut c = h2
    let mut d = h3
    let mut e = h4
    for i in 0..<80 {
      let (f, k) = match i {
        0..<20 =>
          (
            (b & c) | (b.reinterpret_as_int().lnot().reinterpret_as_uint() & d),
            0x5a827999U,
          )
        20..<40 => (b ^ c ^ d, 0x6ed9eba1U)
        40..<60 => ((b & c) | (b & d) | (c & d), 0x8f1bbcdcU)
        _ => (b ^ c ^ d, 0xca62c1d6U)
      }
      let rotated = (a << 5) | (a >> 27)
      let temp = rotated + f + e + k + words[i]
      e = d
      d = c
      c = (b << 30) | (b >> 2)
      b = a
      a = temp
    }
    h0 = h0 + a
    h1 = h1 + b
    h2 = h2 + c
    h3 = h3 + d
    h4 = h4 + e
  }
  digest_bytes([h0, h1, h2, h3, h4], Big)
}

///|
priv enum WordOrder {
  Little
  Big
}

///|
fn digest_bytes(words : Array[UInt], order : WordOrder) -> Bytes {
  let bytes : Array[Byte] = []
  for word in words {
    for i in 0..<4 {
      let shift = match order {
        Little => i * 8
        Big => 24 - i * 8
      }
      bytes.push(((word >> shift) & 255U).to_byte())
    }
  }
  Bytes::from_array(bytes)
}