// RFC 1321 MD5, implemented for stable directive identities (not security use).

///|
let md5_constants : Array[UInt32] = [
  3614090360U, 3905402710U, 606105819U, 3250441966U, 4118548399U, 1200080426U, 2821735955U,
  4249261313U, 1770035416U, 2336552879U, 4294925233U, 2304563134U, 1804603682U, 4254626195U,
  2792965006U, 1236535329U, 4129170786U, 3225465664U, 643717713U, 3921069994U, 3593408605U,
  38016083U, 3634488961U, 3889429448U, 568446438U, 3275163606U, 4107603335U, 1163531501U,
  2850285829U, 4243563512U, 1735328473U, 2368359562U, 4294588738U, 2272392833U, 1839030562U,
  4259657740U, 2763975236U, 1272893353U, 4139469664U, 3200236656U, 681279174U, 3936430074U,
  3572445317U, 76029189U, 3654602809U, 3873151461U, 530742520U, 3299628645U, 4096336452U,
  1126891415U, 2878612391U, 4237533241U, 1700485571U, 2399980690U, 4293915773U, 2240044497U,
  1873313359U, 4264355552U, 2734768916U, 1309151649U, 4149444226U, 3174756917U, 718787259U,
  3951481745U,
]

///|
let md5_shifts : Array[Int32] = [
  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,
]

///|
fn query_md5(text : String) -> String {
  let bytes : Array[UInt32] = []
  for c in text.iter() {
    let n = c.to_int().reinterpret_as_uint()
    if n < 128U {
      bytes.push(n)
    } else if n < 2048U {
      bytes.append([192U | (n >> 6), 128U | (n & 63U)])
    } else if n < 65536U {
      bytes.append([224U | (n >> 12), 128U | ((n >> 6) & 63U), 128U | (n & 63U)])
    } else {
      bytes.append([
        240U | (n >> 18),
        128U | ((n >> 12) & 63U),
        128U | ((n >> 6) & 63U),
        128U | (n & 63U),
      ])
    }
  }
  let length = bytes.length().to_int64() * 8L
  bytes.push(128U)
  while bytes.length() % 64 != 56 {
    bytes.push(0U)
  }
  for i in 0..<8 {
    bytes.push(
      ((length >> (8 * i)) & 255L).to_int().reinterpret_as_uint() & 255U,
    )
  }
  let state = [0x67452301U, 0xefcdab89U, 0x98badcfeU, 0x10325476U]
  let words : Array[UInt32] = Array::make(16, 0U)
  for block = 0; block < bytes.length(); block = block + 64 {
    for j in 0..<16 {
      let k = block + j * 4
      words[j] = bytes[k] |
        (bytes[k + 1] << 8) |
        (bytes[k + 2] << 16) |
        (bytes[k + 3] << 24)
    }
    let mut a = state[0]
    let mut b = state[1]
    let mut c = state[2]
    let mut d = state[3]
    for i in 0..<64 {
      let (f, g) = if i < 16 {
        ((b & c) | ((b ^ 0xffffffffU) & d), i)
      } else if i < 32 {
        ((d & b) | ((d ^ 0xffffffffU) & c), (5 * i + 1) % 16)
      } else if i < 48 {
        (b ^ c ^ d, (3 * i + 5) % 16)
      } else {
        (c ^ (b | (d ^ 0xffffffffU)), 7 * i % 16)
      }
      let n = a + f + md5_constants[i] + words[g]
      let shift = md5_shifts[i]
      a = d
      d = c
      c = b
      b = b + ((n << shift) | (n >> (32 - shift)))
    }
    state[0] = state[0] + a
    state[1] = state[1] + b
    state[2] = state[2] + c
    state[3] = state[3] + d
  }
  let out = StringBuilder()
  for word in state {
    for j in 0..<4 {
      out.write_string(
        repr_hex(((word >> (j * 8)) & 255U).reinterpret_as_int(), 2),
      )
    }
  }
  out.to_string()
}

///|
test "MD5 identity encoding known vectors" {
  assert_eq(query_md5(""), "d41d8cd98f00b204e9800998ecf8427e")
  assert_eq(query_md5("abc"), "900150983cd24fb0d6963f7d28e17f72")
  assert_eq(
    query_md5("abcdefghijklmnopqrstuvwxyz"),
    "c3fcd3d76192e4007dfb496cca67e13b",
  )
}