///|
pub(all) enum ByteDifferenceKind {
  ByteAdded
  ByteRemoved
  ByteChanged
} derive(Eq, Debug)

///|
pub struct ByteDifference {
  address_value : UInt64
  kind_value : ByteDifferenceKind
  left_value : Byte?
  right_value : Byte?
} derive(Eq, Debug)

///|
pub struct ImageComparison {
  difference_values : Array[ByteDifference]
  added_count_value : Int
  removed_count_value : Int
  changed_count_value : Int
} derive(Eq, Debug)

///|
fn image_cells(image : FirmwareImage) -> (Array[UInt64], Array[Byte]) {
  let addresses : Array[UInt64] = []
  let values : Array[Byte] = []
  for segment in image.segments() {
    let data = segment.data()
    for offset = 0; offset < data.length(); offset = offset + 1 {
      addresses.push(segment.address() + offset.to_uint64())
      values.push(data[offset])
    }
  }
  (addresses, values)
}

///|
fn comparison_limit_error() -> FirmwareError {
  FirmwareError::new(
    IntegrityViolation,
    "image comparison exceeds the configured difference evidence limit",
    SourcePosition::line(0),
  )
}

///|
/// Compare sparse images without treating absent addresses as zero bytes.
pub fn compare_images(
  left : FirmwareImage,
  right : FirmwareImage,
  max_differences? : Int = 10000,
) -> Result[ImageComparison, FirmwareError] {
  if max_differences < 0 {
    return Err(comparison_limit_error())
  }
  let (left_addresses, left_values) = image_cells(left)
  let (right_addresses, right_values) = image_cells(right)
  let differences : Array[ByteDifference] = []
  let mut left_index = 0
  let mut right_index = 0
  let mut added = 0
  let mut removed = 0
  let mut changed = 0
  while left_index < left_addresses.length() ||
        right_index < right_addresses.length() {
    if left_index >= left_addresses.length() {
      differences.push({
        address_value: right_addresses[right_index],
        kind_value: ByteAdded,
        left_value: None,
        right_value: Some(right_values[right_index]),
      })
      added = added + 1
      right_index = right_index + 1
    } else if right_index >= right_addresses.length() {
      differences.push({
        address_value: left_addresses[left_index],
        kind_value: ByteRemoved,
        left_value: Some(left_values[left_index]),
        right_value: None,
      })
      removed = removed + 1
      left_index = left_index + 1
    } else if left_addresses[left_index] < right_addresses[right_index] {
      differences.push({
        address_value: left_addresses[left_index],
        kind_value: ByteRemoved,
        left_value: Some(left_values[left_index]),
        right_value: None,
      })
      removed = removed + 1
      left_index = left_index + 1
    } else if right_addresses[right_index] < left_addresses[left_index] {
      differences.push({
        address_value: right_addresses[right_index],
        kind_value: ByteAdded,
        left_value: None,
        right_value: Some(right_values[right_index]),
      })
      added = added + 1
      right_index = right_index + 1
    } else {
      if left_values[left_index] != right_values[right_index] {
        differences.push({
          address_value: left_addresses[left_index],
          kind_value: ByteChanged,
          left_value: Some(left_values[left_index]),
          right_value: Some(right_values[right_index]),
        })
        changed = changed + 1
      }
      left_index = left_index + 1
      right_index = right_index + 1
    }
    if differences.length() > max_differences {
      return Err(comparison_limit_error())
    }
  }
  Ok({
    difference_values: differences,
    added_count_value: added,
    removed_count_value: removed,
    changed_count_value: changed,
  })
}

///|
pub fn ByteDifference::address(self : ByteDifference) -> UInt64 {
  self.address_value
}

///|
pub fn ByteDifference::kind(self : ByteDifference) -> ByteDifferenceKind {
  self.kind_value
}

///|
pub fn ByteDifference::left(self : ByteDifference) -> Byte? {
  self.left_value
}

///|
pub fn ByteDifference::right(self : ByteDifference) -> Byte? {
  self.right_value
}

///|
pub fn ImageComparison::differences(
  self : ImageComparison,
) -> Array[ByteDifference] {
  self.difference_values.copy()
}

///|
pub fn ImageComparison::added_count(self : ImageComparison) -> Int {
  self.added_count_value
}

///|
pub fn ImageComparison::removed_count(self : ImageComparison) -> Int {
  self.removed_count_value
}

///|
pub fn ImageComparison::changed_count(self : ImageComparison) -> Int {
  self.changed_count_value
}

///|
pub fn ImageComparison::identical(self : ImageComparison) -> Bool {
  self.difference_values.length() == 0
}