///|
/// Lazy table of ELF program headers.
///
/// Program headers describe segments used by loaders. The table stores a raw
/// byte range and parses `ProgramHeader` entries only on demand.
pub struct SegmentTable {
  table : ParsingTable[ProgramHeader]
}

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

///|
/// Number of program headers in the table.
pub fn SegmentTable::len(self : SegmentTable) -> Int {
  self.table.len()
}

///|
/// Returns `true` when the file has no program headers.
pub fn SegmentTable::is_empty(self : SegmentTable) -> Bool {
  self.table.is_empty()
}

///|
/// Parse the program header at `index`.
pub fn SegmentTable::get(
  self : SegmentTable,
  index : Int,
) -> ProgramHeader raise ParseError {
  self.table.get(index)
}

///|
/// Iterate over program headers, parsing each header lazily.
pub fn SegmentTable::iter(self : SegmentTable) -> Iter[ProgramHeader] {
  self.table.iter()
}

///|
/// ELF program header normalized across ELF32 and ELF64 layouts.
///
/// Program headers identify the file ranges and memory ranges used by the
/// runtime loader. Width-dependent ELF32 fields are widened to `UInt64`.
pub(all) struct ProgramHeader {
  /// Segment type such as `PT_LOAD`, `PT_DYNAMIC`, or `PT_NOTE`.
  p_type : UInt
  /// File offset where this segment starts.
  p_offset : UInt64
  /// Virtual address where the segment should be loaded.
  p_vaddr : UInt64
  /// Physical address, meaningful only for systems that use it.
  p_paddr : UInt64
  /// Number of bytes occupied by this segment in the file.
  p_filesz : UInt64
  /// Number of bytes occupied by this segment in memory.
  p_memsz : UInt64
  /// Segment permission and processor flags (`PF_*`).
  p_flags : UInt
  /// File and memory alignment requirement.
  p_align : UInt64
} derive(Debug, Eq)

///|
impl ParseAt for ProgramHeader with parse_at(endian, class, offset, data) {
  let end = checked_add(offset, ProgramHeader::size_for(class))
  let record = slice_checked(data, offset, end)
  let phdr = match (class, endian) {
    (ELF32, Little) =>
      match record {
        [
          u32le(p_type),
          u32le(p_offset32),
          u32le(p_vaddr32),
          u32le(p_paddr32),
          u32le(p_filesz32),
          u32le(p_memsz32),
          u32le(p_flags),
          u32le(p_align32),
        ] =>
          {
            p_type,
            p_offset: p_offset32.to_uint64(),
            p_vaddr: p_vaddr32.to_uint64(),
            p_paddr: p_paddr32.to_uint64(),
            p_filesz: p_filesz32.to_uint64(),
            p_memsz: p_memsz32.to_uint64(),
            p_flags,
            p_align: p_align32.to_uint64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF32, Big) =>
      match record {
        [
          u32be(p_type),
          u32be(p_offset32),
          u32be(p_vaddr32),
          u32be(p_paddr32),
          u32be(p_filesz32),
          u32be(p_memsz32),
          u32be(p_flags),
          u32be(p_align32),
        ] =>
          {
            p_type,
            p_offset: p_offset32.to_uint64(),
            p_vaddr: p_vaddr32.to_uint64(),
            p_paddr: p_paddr32.to_uint64(),
            p_filesz: p_filesz32.to_uint64(),
            p_memsz: p_memsz32.to_uint64(),
            p_flags,
            p_align: p_align32.to_uint64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Little) =>
      match record {
        [
          u32le(p_type),
          u32le(p_flags),
          u64le(p_offset),
          u64le(p_vaddr),
          u64le(p_paddr),
          u64le(p_filesz),
          u64le(p_memsz),
          u64le(p_align),
        ] =>
          {
            p_type,
            p_offset,
            p_vaddr,
            p_paddr,
            p_filesz,
            p_memsz,
            p_flags,
            p_align,
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Big) =>
      match record {
        [
          u32be(p_type),
          u32be(p_flags),
          u64be(p_offset),
          u64be(p_vaddr),
          u64be(p_paddr),
          u64be(p_filesz),
          u64be(p_memsz),
          u64be(p_align),
        ] =>
          {
            p_type,
            p_offset,
            p_vaddr,
            p_paddr,
            p_filesz,
            p_memsz,
            p_flags,
            p_align,
          }
        _ => raise SliceReadError(offset, end)
      }
  }
  (phdr, end)
}

///|
/// Size in bytes of one program header for the given ELF class.
pub fn ProgramHeader::size_for(class : Class) -> Int {
  match class {
    ELF32 => 32
    ELF64 => 56
  }
}

///|
// Convert this segment's `(p_offset, p_filesz)` pair to a checked file range.
fn ProgramHeader::get_file_data_range(
  self : ProgramHeader,
) -> (Int, Int) raise ParseError {
  let start = u64_to_int_checked(self.p_offset)
  let size = u64_to_int_checked(self.p_filesz)
  let end = checked_add(start, size)
  (start, end)
}