///|
/// Size of a metadata checksum stored on disk.
let metadata_checksum_size : Int32 = 4

///|
fn checksum_rot(value : UInt32, bits : Int32) -> UInt32 {
  (value << bits) | (value >> (32 - bits))
}

///|
fn checksum_mix(
  a_value : UInt32,
  b_value : UInt32,
  c_value : UInt32,
) -> (UInt32, UInt32, UInt32) {
  let mut a = a_value
  let mut b = b_value
  let mut c = c_value
  a = a - c
  a = a ^ checksum_rot(c, 4)
  c = c + b
  b = b - a
  b = b ^ checksum_rot(a, 6)
  a = a + c
  c = c - b
  c = c ^ checksum_rot(b, 8)
  b = b + a
  a = a - c
  a = a ^ checksum_rot(c, 16)
  c = c + b
  b = b - a
  b = b ^ checksum_rot(a, 19)
  a = a + c
  c = c - b
  c = c ^ checksum_rot(b, 4)
  b = b + a
  (a, b, c)
}

///|
fn checksum_final(
  a_value : UInt32,
  b_value : UInt32,
  c_value : UInt32,
) -> UInt32 {
  let mut a = a_value
  let mut b = b_value
  let mut c = c_value
  c = c ^ b
  c = c - checksum_rot(b, 14)
  a = a ^ c
  a = a - checksum_rot(c, 11)
  b = b ^ a
  b = b - checksum_rot(a, 25)
  c = c ^ b
  c = c - checksum_rot(b, 16)
  a = a ^ c
  a = a - checksum_rot(c, 4)
  b = b ^ a
  b = b - checksum_rot(a, 14)
  c = c ^ b
  c - checksum_rot(b, 24)
}

///|
fn checksum_byte(
  bytes : Bytes,
  start : Int32,
  zero_start : Int32,
  zero_length : Int32,
  index : Int32,
) -> UInt32 {
  if index >= zero_start && index < zero_start + zero_length {
    0U
  } else {
    bytes.unsafe_get(start + index).to_int().reinterpret_as_uint()
  }
}

///|
/// Bob Jenkins lookup3, matching H5_checksum_lookup3.
///
/// `zero_start` and `zero_length` describe a range inside the input that is
/// treated as zero. Fractal heap direct blocks need this because their
/// checksum covers the whole block while the stored checksum field itself is
/// zeroed during computation.
fn checksum_lookup3(
  bytes : Bytes,
  start : Int32,
  length : Int32,
  zero_start : Int32,
  zero_length : Int32,
  initval : UInt32,
) -> UInt32 {
  let mut a = 0xDEADBEEFU + length.reinterpret_as_uint() + initval
  let mut b = a
  let mut c = a
  let mut remaining = length
  let mut offset = 0
  while remaining > 12 {
    a = a +
      checksum_byte(bytes, start, zero_start, zero_length, offset) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 1) << 8) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 2) << 16) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 3) << 24)
    b = b +
      checksum_byte(bytes, start, zero_start, zero_length, offset + 4) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 5) << 8) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 6) << 16) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 7) << 24)
    c = c +
      checksum_byte(bytes, start, zero_start, zero_length, offset + 8) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 9) << 8) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 10) << 16) +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 11) << 24)
    let (next_a, next_b, next_c) = checksum_mix(a, b, c)
    a = next_a
    b = next_b
    c = next_c
    remaining = remaining - 12
    offset = offset + 12
  }
  if remaining >= 1 {
    a = a + checksum_byte(bytes, start, zero_start, zero_length, offset)
  }
  if remaining >= 2 {
    a = a +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 1) << 8)
  }
  if remaining >= 3 {
    a = a +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 2) << 16)
  }
  if remaining >= 4 {
    a = a +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 3) << 24)
  }
  if remaining >= 5 {
    b = b + checksum_byte(bytes, start, zero_start, zero_length, offset + 4)
  }
  if remaining >= 6 {
    b = b +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 5) << 8)
  }
  if remaining >= 7 {
    b = b +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 6) << 16)
  }
  if remaining >= 8 {
    b = b +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 7) << 24)
  }
  if remaining >= 9 {
    c = c + checksum_byte(bytes, start, zero_start, zero_length, offset + 8)
  }
  if remaining >= 10 {
    c = c +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 9) << 8)
  }
  if remaining >= 11 {
    c = c +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 10) << 16)
  }
  if remaining >= 12 {
    c = c +
      (checksum_byte(bytes, start, zero_start, zero_length, offset + 11) << 24)
  }
  if remaining == 0 {
    return c
  }
  checksum_final(a, b, c)
}

///|
fn metadata_checksum(bytes : Bytes, start : Int32, length : Int32) -> UInt32 {
  checksum_lookup3(bytes, start, length, 0, 0, 0U)
}

///|
fn stored_metadata_checksum(
  bytes : Bytes,
  offset : Int32,
) -> UInt32 raise Hdf5Error {
  guard offset >= 0 && offset.to_int64() + 4L <= bytes.length().to_int64() else {
    raise UnexpectedEof(offset.to_int64())
  }
  bytes.unsafe_read_uint32_le(offset)
}

///|
/// Verifies a checksum stored directly after the covered bytes.
fn verify_metadata_checksum(
  bytes : Bytes,
  start : Int32,
  end : Int32,
  label : String,
) -> Unit raise Hdf5Error {
  guard start >= 0 &&
    end >= start &&
    end.to_int64() + 4L <= bytes.length().to_int64() else {
    raise UnexpectedEof(end.to_int64())
  }
  let stored = stored_metadata_checksum(bytes, end)
  let computed = metadata_checksum(bytes, start, end - start)
  guard stored == computed else {
    raise InvalidField(label + " checksum mismatch")
  }
}

///|
/// Verifies a checksum inside the covered range whose bytes are zero during
/// computation. Fractal heap direct blocks use this layout.
fn verify_metadata_checksum_zeroed(
  bytes : Bytes,
  start : Int32,
  length : Int32,
  checksum_offset : Int32,
  label : String,
) -> Unit raise Hdf5Error {
  guard start >= 0 &&
    length >= 4 &&
    checksum_offset >= start &&
    checksum_offset.to_int64() + 4L <= start.to_int64() + length.to_int64() &&
    start.to_int64() + length.to_int64() <= bytes.length().to_int64() else {
    raise UnexpectedEof(start.to_int64())
  }
  let stored = stored_metadata_checksum(bytes, checksum_offset)
  let computed = checksum_lookup3(
    bytes,
    start,
    length,
    checksum_offset - start,
    metadata_checksum_size,
    0U,
  )
  guard stored == computed else {
    raise InvalidField(label + " checksum mismatch")
  }
}