///|
/// Lazy table of ELF section headers.
///
/// The table wraps the raw section-header byte range and decodes individual
/// `SectionHeader` values only when requested.
pub struct SectionHeaderTable {
  table : ParsingTable[SectionHeader]
}

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

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

///|
/// Returns `true` when the file has an empty section-header table.
pub fn SectionHeaderTable::is_empty(self : SectionHeaderTable) -> Bool {
  self.table.is_empty()
}

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

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

///|
/// ELF section header normalized across ELF32 and ELF64 layouts.
///
/// The field names match the System V ABI. Width-dependent ELF32 fields are
/// widened to `UInt64` where the ELF64 form uses 64 bits.
pub(all) struct SectionHeader {
  /// Offset into the section-name string table.
  sh_name : UInt
  /// Section type such as `SHT_PROGBITS`, `SHT_STRTAB`, or `SHT_DYNSYM`.
  sh_type : UInt
  /// Section flags bitmask (`SHF_*`).
  sh_flags : UInt64
  /// Virtual address where the section appears in memory, if loaded.
  sh_addr : UInt64
  /// File offset of this section's data.
  sh_offset : UInt64
  /// Size of this section's file data in bytes.
  sh_size : UInt64
  /// Section-type-specific link field, often another section index.
  sh_link : UInt
  /// Section-type-specific info field.
  sh_info : UInt
  /// Required section alignment.
  sh_addralign : UInt64
  /// Entry size when the section data is a table, or zero otherwise.
  sh_entsize : UInt64
} derive(Debug, Eq)

///|
impl ParseAt for SectionHeader with parse_at(endian, class, offset, data) {
  let end = checked_add(offset, SectionHeader::size_for(class))
  let record = slice_checked(data, offset, end)
  let shdr = match (class, endian) {
    (ELF32, Little) =>
      match record {
        [
          u32le(sh_name),
          u32le(sh_type),
          u32le(sh_flags32),
          u32le(sh_addr32),
          u32le(sh_offset32),
          u32le(sh_size32),
          u32le(sh_link),
          u32le(sh_info),
          u32le(sh_addralign32),
          u32le(sh_entsize32),
        ] =>
          {
            sh_name,
            sh_type,
            sh_flags: sh_flags32.to_uint64(),
            sh_addr: sh_addr32.to_uint64(),
            sh_offset: sh_offset32.to_uint64(),
            sh_size: sh_size32.to_uint64(),
            sh_link,
            sh_info,
            sh_addralign: sh_addralign32.to_uint64(),
            sh_entsize: sh_entsize32.to_uint64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF32, Big) =>
      match record {
        [
          u32be(sh_name),
          u32be(sh_type),
          u32be(sh_flags32),
          u32be(sh_addr32),
          u32be(sh_offset32),
          u32be(sh_size32),
          u32be(sh_link),
          u32be(sh_info),
          u32be(sh_addralign32),
          u32be(sh_entsize32),
        ] =>
          {
            sh_name,
            sh_type,
            sh_flags: sh_flags32.to_uint64(),
            sh_addr: sh_addr32.to_uint64(),
            sh_offset: sh_offset32.to_uint64(),
            sh_size: sh_size32.to_uint64(),
            sh_link,
            sh_info,
            sh_addralign: sh_addralign32.to_uint64(),
            sh_entsize: sh_entsize32.to_uint64(),
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Little) =>
      match record {
        [
          u32le(sh_name),
          u32le(sh_type),
          u64le(sh_flags),
          u64le(sh_addr),
          u64le(sh_offset),
          u64le(sh_size),
          u32le(sh_link),
          u32le(sh_info),
          u64le(sh_addralign),
          u64le(sh_entsize),
        ] =>
          {
            sh_name,
            sh_type,
            sh_flags,
            sh_addr,
            sh_offset,
            sh_size,
            sh_link,
            sh_info,
            sh_addralign,
            sh_entsize,
          }
        _ => raise SliceReadError(offset, end)
      }
    (ELF64, Big) =>
      match record {
        [
          u32be(sh_name),
          u32be(sh_type),
          u64be(sh_flags),
          u64be(sh_addr),
          u64be(sh_offset),
          u64be(sh_size),
          u32be(sh_link),
          u32be(sh_info),
          u64be(sh_addralign),
          u64be(sh_entsize),
        ] =>
          {
            sh_name,
            sh_type,
            sh_flags,
            sh_addr,
            sh_offset,
            sh_size,
            sh_link,
            sh_info,
            sh_addralign,
            sh_entsize,
          }
        _ => raise SliceReadError(offset, end)
      }
  }
  (shdr, end)
}

///|
/// Size in bytes of one section header for the given ELF class.
pub fn SectionHeader::size_for(class : Class) -> Int {
  match class {
    ELF32 => 40
    ELF64 => 64
  }
}

///|
// Convert this section's `(sh_offset, sh_size)` pair to a checked byte range.
fn SectionHeader::get_data_range(
  self : SectionHeader,
) -> (Int, Int) raise ParseError {
  let start = u64_to_int_checked(self.sh_offset)
  let size = u64_to_int_checked(self.sh_size)
  let end = checked_add(start, size)
  (start, end)
}