///|
fn rotate_left(value : UInt, shift : Int) -> UInt {
  (value << shift) | (value >> (32 - shift))
}

///|
fn read_u32_le(data : Array[Byte], offset : Int) -> UInt {
  data[offset].to_uint() |
  (data[offset + 1].to_uint() << 8) |
  (data[offset + 2].to_uint() << 16) |
  (data[offset + 3].to_uint() << 24)
}

///|
fn append_u32_le(output : Array[Byte], value : UInt) -> Unit {
  output.push(value.to_byte())
  output.push((value >> 8).to_byte())
  output.push((value >> 16).to_byte())
  output.push((value >> 24).to_byte())
}

///|
fn md5(data : Bytes) -> Bytes {
  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,
  ]
  let padded = data.to_array()
  let bit_length = data.length().to_uint64() * 8UL
  padded.push(0x80)
  while padded.length() % 64 != 56 {
    padded.push(0)
  }
  for shift = 0; shift < 64; shift = shift + 8 {
    padded.push((bit_length >> shift).to_byte())
  }
  let mut a0 = 0x67452301U
  let mut b0 = 0xefcdab89U
  let mut c0 = 0x98badcfeU
  let mut d0 = 0x10325476U
  for offset = 0; offset < padded.length(); offset = offset + 64 {
    let words : Array[UInt] = []
    for index = 0; index < 16; index = index + 1 {
      words.push(read_u32_le(padded, offset + index * 4))
    }
    let mut a = a0
    let mut b = b0
    let mut c = c0
    let mut d = d0
    for index = 0; index < 64; index = index + 1 {
      let (function_value, word_index) = if index < 16 {
        ((b & c) | (b.lnot() & d), index)
      } else if index < 32 {
        ((d & b) | (d.lnot() & c), (5 * index + 1) % 16)
      } else if index < 48 {
        (b ^ c ^ d, (3 * index + 5) % 16)
      } else {
        (c ^ (b | d.lnot()), 7 * index % 16)
      }
      let previous_d = d
      d = c
      c = b
      b = b +
        rotate_left(
          a + function_value + constants[index] + words[word_index],
          shifts[index],
        )
      a = previous_d
    }
    a0 += a
    b0 += b
    c0 += c
    d0 += d
  }
  let output : Array[Byte] = []
  append_u32_le(output, a0)
  append_u32_le(output, b0)
  append_u32_le(output, c0)
  append_u32_le(output, d0)
  Bytes::from_array(output)
}

///|
pub fn long_term_key(
  username : String,
  realm : String,
  password : String,
) -> Bytes {
  md5(@utf8.encode(username + ":" + realm + ":" + password))
}