///|
/// Lazy table for relocation entries without explicit addends (`SHT_REL`).
pub struct RelIterator {
  table : ParsingTable[Rel]
}

///|
/// Lazy table for relocation entries with explicit addends (`SHT_RELA`).
pub struct RelaIterator {
  table : ParsingTable[Rela]
}

///|
fn RelIterator::RelIterator(
  endian : Endian,
  class : Class,
  data : BytesView,
) -> RelIterator {
  {
    table: ParsingTable(
      endian,
      class,
      data,
      Rel::size_for(class),
      Rel::parse_at,
    ),
  }
}

///|
fn RelaIterator::RelaIterator(
  endian : Endian,
  class : Class,
  data : BytesView,
) -> RelaIterator {
  {
    table: ParsingTable(
      endian,
      class,
      data,
      Rela::size_for(class),
      Rela::parse_at,
    ),
  }
}

///|
/// Number of `Rel` entries.
pub fn RelIterator::len(self : RelIterator) -> Int {
  self.table.len()
}

///|
/// Returns `true` when there are no `Rel` entries.
pub fn RelIterator::is_empty(self : RelIterator) -> Bool {
  self.table.is_empty()
}

///|
/// Parse the `Rel` entry at `index`.
pub fn RelIterator::get(
  self : RelIterator,
  index : Int,
) -> Rel raise ParseError {
  self.table.get(index)
}

///|
/// Iterate over `Rel` entries lazily.
pub fn RelIterator::iter(self : RelIterator) -> Iter[Rel] {
  self.table.iter()
}

///|
/// Number of `Rela` entries.
pub fn RelaIterator::len(self : RelaIterator) -> Int {
  self.table.len()
}

///|
/// Returns `true` when there are no `Rela` entries.
pub fn RelaIterator::is_empty(self : RelaIterator) -> Bool {
  self.table.is_empty()
}

///|
/// Parse the `Rela` entry at `index`.
pub fn RelaIterator::get(
  self : RelaIterator,
  index : Int,
) -> Rela raise ParseError {
  self.table.get(index)
}

///|
/// Iterate over `Rela` entries lazily.
pub fn RelaIterator::iter(self : RelaIterator) -> Iter[Rela] {
  self.table.iter()
}

///|
/// ELF relocation entry without an explicit addend.
///
/// The packed ELF `r_info` word is split into `r_sym` and `r_type` while
/// parsing, using the ELF32 or ELF64 packing rule.
pub(all) struct Rel {
  /// Location to relocate. Interpretation depends on file type and architecture.
  r_offset : UInt64
  /// Symbol table index referenced by this relocation.
  r_sym : UInt
  /// Processor-specific relocation type.
  r_type : UInt
} derive(Debug, Eq)

///|
impl ParseAt for Rel with parse_at(endian, class, offset, data) {
  let end = checked_add(offset, Rel::size_for(class))
  let record = slice_checked(data, offset, end)
  let rel = match (class, endian) {
    (ELF32, Little) =>
      match record {
        [u32le(r_offset32), u32le(r_info)] =>
          {
            r_offset: r_offset32.to_uint64(),
            r_sym: r_info >> 8,
            r_type: r_info & 0xffU,
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF32, Big) =>
      match record {
        [u32be(r_offset32), u32be(r_info)] =>
          {
            r_offset: r_offset32.to_uint64(),
            r_sym: r_info >> 8,
            r_type: r_info & 0xffU,
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Little) =>
      match record {
        [u64le(r_offset), u64le(r_info)] =>
          {
            r_offset,
            r_sym: (r_info >> 32).to_uint(),
            r_type: (r_info & 0xffffffffUL).to_uint(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Big) =>
      match record {
        [u64be(r_offset), u64be(r_info)] =>
          {
            r_offset,
            r_sym: (r_info >> 32).to_uint(),
            r_type: (r_info & 0xffffffffUL).to_uint(),
          }
        _ => raise SliceReadError(offset, end)
      }
  }
  (rel, end)
}

///|
/// Size in bytes of one no-addend relocation for the given ELF class.
pub fn Rel::size_for(class : Class) -> Int {
  match class {
    ELF32 => 8
    ELF64 => 16
  }
}

///|
/// ELF relocation entry with an explicit signed addend.
///
/// Like `Rel`, the packed `r_info` word is split into `r_sym` and `r_type`.
pub(all) struct Rela {
  /// Location to relocate. Interpretation depends on file type and architecture.
  r_offset : UInt64
  /// Symbol table index referenced by this relocation.
  r_sym : UInt
  /// Processor-specific relocation type.
  r_type : UInt
  /// Explicit addend supplied by the relocation entry.
  r_addend : Int64
} derive(Debug, Eq)

///|
impl ParseAt for Rela with parse_at(endian, class, offset, data) {
  let end = checked_add(offset, Rela::size_for(class))
  let record = slice_checked(data, offset, end)
  let rela = match (class, endian) {
    (ELF32, Little) =>
      match record {
        [u32le(r_offset32), u32le(r_info), u32le(r_addend32)] =>
          {
            r_offset: r_offset32.to_uint64(),
            r_sym: r_info >> 8,
            r_type: r_info & 0xffU,
            r_addend: r_addend32.reinterpret_as_int().to_int64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF32, Big) =>
      match record {
        [u32be(r_offset32), u32be(r_info), u32be(r_addend32)] =>
          {
            r_offset: r_offset32.to_uint64(),
            r_sym: r_info >> 8,
            r_type: r_info & 0xffU,
            r_addend: r_addend32.reinterpret_as_int().to_int64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Little) =>
      match record {
        [u64le(r_offset), u64le(r_info), u64le(r_addend)] =>
          {
            r_offset,
            r_sym: (r_info >> 32).to_uint(),
            r_type: (r_info & 0xffffffffUL).to_uint(),
            r_addend: r_addend.reinterpret_as_int64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Big) =>
      match record {
        [u64be(r_offset), u64be(r_info), u64be(r_addend)] =>
          {
            r_offset,
            r_sym: (r_info >> 32).to_uint(),
            r_type: (r_info & 0xffffffffUL).to_uint(),
            r_addend: r_addend.reinterpret_as_int64(),
          }
        _ => raise SliceReadError(offset, end)
      }
  }
  (rela, end)
}

///|
/// Size in bytes of one addend relocation for the given ELF class.
pub fn Rela::size_for(class : Class) -> Int {
  match class {
    ELF32 => 12
    ELF64 => 24
  }
}