///|
pub struct ImageStatistics {
  segment_count_value : Int
  occupied_bytes_value : Int
  address_span_value : UInt64
  gap_bytes_value : UInt64
  largest_segment_bytes_value : Int
  largest_gap_bytes_value : UInt64
  lowest_address_value : UInt64?
  highest_address_value : UInt64?
} derive(Eq, Debug)

///|
/// Calculate sparse-image metrics without materializing missing addresses.
pub fn analyze_image(image : FirmwareImage) -> ImageStatistics {
  let segments = image.segments()
  let gaps = image.gaps()
  let occupied = image.total_bytes()
  let mut gap_bytes = 0UL
  let mut largest_gap = 0UL
  let mut largest_segment = 0
  for gap in gaps {
    let length = gap.length()
    gap_bytes = gap_bytes + length
    if length > largest_gap {
      largest_gap = length
    }
  }
  for segment in segments {
    if segment.length() > largest_segment {
      largest_segment = segment.length()
    }
  }
  let span = match (image.lowest_address(), image.highest_address()) {
    (Some(lowest), Some(highest)) => highest - lowest + 1UL
    _ => 0UL
  }
  {
    segment_count_value: segments.length(),
    occupied_bytes_value: occupied,
    address_span_value: span,
    gap_bytes_value: gap_bytes,
    largest_segment_bytes_value: largest_segment,
    largest_gap_bytes_value: largest_gap,
    lowest_address_value: image.lowest_address(),
    highest_address_value: image.highest_address(),
  }
}

///|
pub fn ImageStatistics::segment_count(self : ImageStatistics) -> Int {
  self.segment_count_value
}

///|
pub fn ImageStatistics::occupied_bytes(self : ImageStatistics) -> Int {
  self.occupied_bytes_value
}

///|
pub fn ImageStatistics::address_span(self : ImageStatistics) -> UInt64 {
  self.address_span_value
}

///|
pub fn ImageStatistics::gap_bytes(self : ImageStatistics) -> UInt64 {
  self.gap_bytes_value
}

///|
pub fn ImageStatistics::largest_segment_bytes(self : ImageStatistics) -> Int {
  self.largest_segment_bytes_value
}

///|
pub fn ImageStatistics::largest_gap_bytes(self : ImageStatistics) -> UInt64 {
  self.largest_gap_bytes_value
}

///|
pub fn ImageStatistics::lowest_address(self : ImageStatistics) -> UInt64? {
  self.lowest_address_value
}

///|
pub fn ImageStatistics::highest_address(self : ImageStatistics) -> UInt64? {
  self.highest_address_value
}

///|
pub fn ImageStatistics::density(self : ImageStatistics) -> Double {
  if self.address_span_value == 0UL {
    0.0
  } else {
    self.occupied_bytes_value.to_double() / self.address_span_value.to_double()
  }
}

///|
pub fn ImageStatistics::average_segment_bytes(self : ImageStatistics) -> Double {
  if self.segment_count_value == 0 {
    0.0
  } else {
    self.occupied_bytes_value.to_double() / self.segment_count_value.to_double()
  }
}

///|
pub fn render_statistics_text(statistics : ImageStatistics) -> String {
  let mut result = "firmware-statistics segments=" +
    statistics.segment_count().to_string() +
    " occupied=" +
    statistics.occupied_bytes().to_string() +
    " span=" +
    statistics.address_span().to_string() +
    " gap-bytes=" +
    statistics.gap_bytes().to_string() +
    " largest-segment=" +
    statistics.largest_segment_bytes().to_string() +
    " largest-gap=" +
    statistics.largest_gap_bytes().to_string()
  match statistics.lowest_address() {
    Some(value) => result = result + " lowest=" + hex_address(value)
    None => ()
  }
  match statistics.highest_address() {
    Some(value) => result = result + " highest=" + hex_address(value)
    None => ()
  }
  result
}

///|
pub fn render_statistics_json(statistics : ImageStatistics) -> String {
  Json::object({
    "type": Json::string("firmware_image_statistics"),
    "segment_count": Json::number(statistics.segment_count().to_double()),
    "occupied_bytes": Json::number(statistics.occupied_bytes().to_double()),
    "address_span": Json::string(statistics.address_span().to_string()),
    "gap_bytes": Json::string(statistics.gap_bytes().to_string()),
    "largest_segment_bytes": Json::number(
      statistics.largest_segment_bytes().to_double(),
    ),
    "largest_gap_bytes": Json::string(
      statistics.largest_gap_bytes().to_string(),
    ),
    "lowest_address": optional_address_json(statistics.lowest_address()),
    "highest_address": optional_address_json(statistics.highest_address()),
    "density": Json::number(statistics.density()),
    "average_segment_bytes": Json::number(statistics.average_segment_bytes()),
  }).stringify()
}