///|
fn image_as_chunks(image : FirmwareImage) -> Array[FirmwareChunk] {
  let chunks : Array[FirmwareChunk] = []
  for index, segment in image.segments() {
    chunks.push(FirmwareChunk::new(segment.address(), segment.data(), index))
  }
  chunks
}

///|
fn invalid_image_range(message : String) -> FirmwareError {
  FirmwareError::new(ImageInvalidRange, message, SourcePosition::line(0))
}

///|
/// Merge two images while applying an explicit byte-overlap policy.
pub fn FirmwareImage::merge(
  self : FirmwareImage,
  other : FirmwareImage,
  policy? : OverlapPolicy = RejectOverlap,
) -> Result[FirmwareImage, FirmwareError] {
  let chunks = image_as_chunks(self)
  for chunk in image_as_chunks(other) {
    chunks.push(chunk)
  }
  let entry = match (self.entry_point(), other.entry_point()) {
    (Some(left), Some(right)) if left != right =>
      return Err(
        FirmwareError::new(
          IntegrityViolation,
          "cannot merge images with different entry points",
          SourcePosition::line(0),
        ),
      )
    (Some(value), _) | (_, Some(value)) => Some(value)
    _ => None
  }
  FirmwareImage::from_chunks(chunks, policy~, entry_point=entry)
}

///|
/// Return bytes intersecting the half-open address range `[start, end)`.
pub fn FirmwareImage::slice(
  self : FirmwareImage,
  start : UInt64,
  end_exclusive : UInt64,
) -> Result[FirmwareImage, FirmwareError] {
  if start >= end_exclusive {
    return Err(invalid_image_range("image slice start must be below its end"))
  }
  let chunks : Array[FirmwareChunk] = []
  for index, segment in self.segments() {
    let overlap_start = if segment.address() > start {
      segment.address()
    } else {
      start
    }
    let overlap_end = if segment.end_exclusive() < end_exclusive {
      segment.end_exclusive()
    } else {
      end_exclusive
    }
    if overlap_start < overlap_end {
      let source = segment.data()
      let offset = (overlap_start - segment.address()).to_int()
      let length = (overlap_end - overlap_start).to_int()
      let data = Bytes::makei(length, item => source[offset + item])
      chunks.push(FirmwareChunk::new(overlap_start, data, index))
    }
  }
  let entry = match self.entry_point() {
    Some(value) if value >= start && value < end_exclusive => Some(value)
    _ => None
  }
  FirmwareImage::from_chunks(chunks, entry_point=entry)
}

///|
fn relocate_address(
  value : UInt64,
  old_base : UInt64,
  new_base : UInt64,
) -> Result[UInt64, FirmwareError] {
  if new_base >= old_base {
    let delta = new_base - old_base
    if value > 0xFFFFFFFFFFFFFFFFUL - delta {
      Err(invalid_image_range("image relocation overflows UInt64"))
    } else {
      Ok(value + delta)
    }
  } else {
    let delta = old_base - new_base
    if value < delta {
      Err(invalid_image_range("image relocation underflows address zero"))
    } else {
      Ok(value - delta)
    }
  }
}

///|
/// Move the lowest image address to `new_base` while preserving distances.
pub fn FirmwareImage::relocate(
  self : FirmwareImage,
  new_base : UInt64,
) -> Result[FirmwareImage, FirmwareError] {
  let old_base = match self.lowest_address() {
    Some(value) => value
    None => return Err(invalid_image_range("cannot relocate an empty image"))
  }
  let chunks : Array[FirmwareChunk] = []
  for index, segment in self.segments() {
    let address = match
      relocate_address(segment.address(), old_base, new_base) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let chunk = match
      FirmwareChunk::create(address, segment.data(), line_index=index) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    chunks.push(chunk)
  }
  let entry = match self.entry_point() {
    Some(value) =>
      match relocate_address(value, old_base, new_base) {
        Ok(relocated) => Some(relocated)
        Err(error) => return Err(error)
      }
    None => None
  }
  FirmwareImage::from_chunks(chunks, entry_point=entry)
}

///|
/// Fill only gaps between existing segments, subject to an allocation bound.
pub fn FirmwareImage::fill_gaps(
  self : FirmwareImage,
  value : Byte,
  max_fill_bytes : Int,
) -> Result[FirmwareImage, FirmwareError] {
  if max_fill_bytes < 0 {
    return Err(invalid_image_range("maximum fill bytes cannot be negative"))
  }
  let gaps = self.gaps()
  let mut total = 0UL
  for gap in gaps {
    total = total + gap.length()
  }
  if total > max_fill_bytes.to_uint64() || total > 0x7FFFFFFFUL {
    return Err(
      invalid_image_range("gap fill exceeds the configured byte limit"),
    )
  }
  let chunks = image_as_chunks(self)
  for index, gap in gaps {
    let data = Bytes::makei(gap.length().to_int(), _ => value)
    chunks.push(FirmwareChunk::new(gap.start(), data, index))
  }
  FirmwareImage::from_chunks(
    chunks,
    policy=AllowIdentical,
    entry_point=self.entry_point(),
  )
}