///|
/// Common firmware-level checksums. These operate on reconstructed image bytes;
/// they are distinct from Intel HEX and S-Record record checksums.
pub(all) enum ImageChecksumAlgorithm {
  Crc32Ieee
  Crc16Ccitt
  Sum8
  Sum16
  Sum32
} derive(Eq, Debug)

///|
/// Result retains the exact address window and fill choice used for holes.
pub struct ImageChecksum {
  algorithm : ImageChecksumAlgorithm
  range : @model.AddressRange
  value : Int64
  processed_bytes : Int
  filled_bytes : Int
  fill : Byte?
} derive(Eq, Debug)

///|
fn validate_checksum_window(
  image : @model.FirmwareImage,
  range : @model.AddressRange,
  fill : Byte?,
  max_bytes : Int,
) -> Int raise @model.FirmwareError {
  if max_bytes < 0 ||
    max_bytes > 64 * 1024 * 1024 ||
    range.length() > max_bytes.to_int64() {
    raise @model.FirmwareError(
      @model.diagnostic(
        ResourceLimit,
        "checksum window exceeds configured limit",
      ),
    )
  }
  let holes = image.memory.holes_in(range)
  if !holes.is_empty() && fill == None {
    let hole = holes[0]
    raise @model.FirmwareError(
      @model.diagnostic(
        GapRequiresFill,
        "checksum window contains an address gap; specify fill",
        address=hole.start,
        end_address=hole.end - 1L,
      ),
    )
  }
  holes.fold(init=0L, fn(total, hole) { total + hole.length() }).to_int()
}

///|
fn byte_at(image : @model.FirmwareImage, address : Int64, fill : Byte?) -> Byte {
  image.memory.read(address).unwrap_or(fill.unwrap_or(b'\x00'))
}

///|
fn crc32_ieee(
  image : @model.FirmwareImage,
  range : @model.AddressRange,
  fill : Byte?,
) -> Int64 {
  let mut crc = 0xFFFFFFFFL
  let mut address = range.start
  while address < range.end {
    crc = crc ^ byte_at(image, address, fill).to_int().to_int64()
    for _ in 0..<8 {
      crc = if (crc & 1L) != 0L { (crc >> 1) ^ 0xEDB88320L } else { crc >> 1 }
    }
    address += 1L
  }
  (crc ^ 0xFFFFFFFFL) & 0xFFFFFFFFL
}

///|
fn crc16_ccitt_false(
  image : @model.FirmwareImage,
  range : @model.AddressRange,
  fill : Byte?,
) -> Int64 {
  let mut crc = 0xFFFFL
  let mut address = range.start
  while address < range.end {
    crc = crc ^ (byte_at(image, address, fill).to_int().to_int64() << 8)
    for _ in 0..<8 {
      crc = if (crc & 0x8000L) != 0L {
        ((crc << 1) ^ 0x1021L) & 0xFFFFL
      } else {
        (crc << 1) & 0xFFFFL
      }
    }
    address += 1L
  }
  crc
}

///|
fn additive_checksum(
  image : @model.FirmwareImage,
  range : @model.AddressRange,
  fill : Byte?,
  mask : Int64,
) -> Int64 {
  let mut sum = 0L
  let mut address = range.start
  while address < range.end {
    sum = (sum + byte_at(image, address, fill).to_int().to_int64()) & mask
    address += 1L
  }
  sum
}

///|
/// Calculate a checksum over an explicit half-open address window. Sparse gaps
/// require an explicit fill byte, preventing accidental checksums over invented
/// data. Work and allocation are bounded independently of the highest address.
pub fn checksum_image(
  image : @model.FirmwareImage,
  range : @model.AddressRange,
  algorithm? : ImageChecksumAlgorithm = Crc32Ieee,
  fill? : Byte,
  max_bytes? : Int = 16 * 1024 * 1024,
) -> ImageChecksum raise @model.FirmwareError {
  let filled = validate_checksum_window(image, range, fill, max_bytes)
  let value = match algorithm {
    Crc32Ieee => crc32_ieee(image, range, fill)
    Crc16Ccitt => crc16_ccitt_false(image, range, fill)
    Sum8 => additive_checksum(image, range, fill, 0xFFL)
    Sum16 => additive_checksum(image, range, fill, 0xFFFFL)
    Sum32 => additive_checksum(image, range, fill, 0xFFFFFFFFL)
  }
  {
    algorithm,
    range,
    value,
    processed_bytes: range.length().to_int(),
    filled_bytes: filled,
    fill,
  }
}

///|
/// Uppercase checksum text with the conventional width of its algorithm.
pub fn ImageChecksum::hex(self : ImageChecksum) -> String {
  let digits = match self.algorithm {
    Sum8 => 2
    Crc16Ccitt | Sum16 => 4
    Crc32Ieee | Sum32 => 8
  }
  "0x" + self.value.to_string(radix=16).to_upper().pad_start(digits, '0')
}