const IPAD : UInt = 0x36U
const OPAD : UInt = 0x5cU

fn bytes_to_uints(bytes: Bytes) -> Array[UInt] {
  let arr : Array[UInt] = []
  for i in 0.. Array[UInt] {
  let bytes = @utf8.encode(value)
  let arr : Array[UInt] = []
  for i in 0.. Array[UInt] {
  let result = Array::make(64, 0U)
  for i in 0..<8 {
    let w = words[i]
    let base = i * 8
    result[base] = ((w >> 56) & 0xFFUL).to_uint()
    result[base + 1] = ((w >> 48) & 0xFFUL).to_uint()
    result[base + 2] = ((w >> 40) & 0xFFUL).to_uint()
    result[base + 3] = ((w >> 32) & 0xFFUL).to_uint()
    result[base + 4] = ((w >> 24) & 0xFFUL).to_uint()
    result[base + 5] = ((w >> 16) & 0xFFUL).to_uint()
    result[base + 6] = ((w >> 8) & 0xFFUL).to_uint()
    result[base + 7] = (w & 0xFFUL).to_uint()
  }
  result
}

fn sha512_words_to_fixed_bytes(words: FixedArray[UInt64]) -> FixedArray[UInt] {
  let result = FixedArray::make(64, 0U)
  for i in 0..<8 {
    let w = words[i]
    let base = i * 8
    result[base] = ((w >> 56) & 0xFFUL).to_uint()
    result[base + 1] = ((w >> 48) & 0xFFUL).to_uint()
    result[base + 2] = ((w >> 40) & 0xFFUL).to_uint()
    result[base + 3] = ((w >> 32) & 0xFFUL).to_uint()
    result[base + 4] = ((w >> 24) & 0xFFUL).to_uint()
    result[base + 5] = ((w >> 16) & 0xFFUL).to_uint()
    result[base + 6] = ((w >> 8) & 0xFFUL).to_uint()
    result[base + 7] = (w & 0xFFUL).to_uint()
  }
  result
}

fn constant_time_eq_bytes(left: FixedArray[UInt], right: Array[UInt]) -> Bool {
  if right.length() != left.length() {
    return false
  }
  let mut diff : UInt = 0U
  for i in 0.. Array[UInt] {
  let block_size = 64
  let block = Array::make(block_size, 0U)
  if key.length() > block_size {
    let key_hash = @sha2.Sha256::digest(key)
    for i in 0.. Array[UInt] {
  let block_size = 128
  let block = Array::make(block_size, 0U)
  if key.length() > block_size {
    let key_hash = @sha2.Sha512::digest(key)
    let key_bytes = sha512_words_to_bytes(key_hash)
    for i in 0.. Array[UInt] {
  let pad = Array::make(block.length(), 0U)
  for i in 0.. FixedArray[UInt] {
  let key_block = prepare_key_block_sha256(key)
  let inner_pad = build_pad(key_block, IPAD)
  let outer_pad = build_pad(key_block, OPAD)

  let inner_data : Array[UInt] = []
  for i in 0.. FixedArray[UInt] {
  hmac_sha256(bytes_to_uints(message), bytes_to_uints(key))
}

pub fn hmac_sha256_string(message: String, key: String) -> FixedArray[UInt] {
  hmac_sha256(string_to_uints(message), string_to_uints(key))
}

pub fn verify_hmac_sha256(message: Array[UInt], key: Array[UInt], mac: Array[UInt]) -> Bool {
  let expected = hmac_sha256(message, key)
  constant_time_eq_bytes(expected, mac)
}

pub fn verify_hmac_sha256_bytes(message: Bytes, key: Bytes, mac: Array[UInt]) -> Bool {
  verify_hmac_sha256(bytes_to_uints(message), bytes_to_uints(key), mac)
}

pub fn hmac_sha512(message: Array[UInt], key: Array[UInt]) -> FixedArray[UInt] {
  let key_block = prepare_key_block_sha512(key)
  let inner_pad = build_pad(key_block, IPAD)
  let outer_pad = build_pad(key_block, OPAD)

  let inner_data : Array[UInt] = []
  for i in 0.. FixedArray[UInt] {
  hmac_sha512(bytes_to_uints(message), bytes_to_uints(key))
}

pub fn hmac_sha512_string(message: String, key: String) -> FixedArray[UInt] {
  hmac_sha512(string_to_uints(message), string_to_uints(key))
}

pub fn verify_hmac_sha512(message: Array[UInt], key: Array[UInt], mac: Array[UInt]) -> Bool {
  let expected = hmac_sha512(message, key)
  constant_time_eq_bytes(expected, mac)
}

pub fn verify_hmac_sha512_bytes(message: Bytes, key: Bytes, mac: Array[UInt]) -> Bool {
  verify_hmac_sha512(bytes_to_uints(message), bytes_to_uints(key), mac)
}

pub fn generateMAC(message: Array[UInt], key: Array[UInt]) -> FixedArray[UInt] {
  hmac_sha256(message, key)
}

pub fn generateMAC_bytes(message: Bytes, key: Bytes) -> FixedArray[UInt] {
  hmac_sha256_bytes(message, key)
}

pub fn generateMAC_string(message: String, key: String) -> FixedArray[UInt] {
  hmac_sha256_string(message, key)
}

pub fn verifyMAC(message: Array[UInt], key: Array[UInt], mac: Array[UInt]) -> Bool {
  verify_hmac_sha256(message, key, mac)
}

pub fn verifyMAC_bytes(message: Bytes, key: Bytes, mac: Array[UInt]) -> Bool {
  verify_hmac_sha256_bytes(message, key, mac)
}

pub fn verifyMAC_string(message: String, key: String, mac: Array[UInt]) -> Bool {
  verify_hmac_sha256(string_to_uints(message), string_to_uints(key), mac)
}