///|
/// The debug directory (slot 6), including CodeView records that carry the PDB
/// path.
///
/// The PDB path is what lets a tool go from a stripped release binary back to
/// its symbols, so it is worth exposing even though the debug data itself is
/// not interpreted.

///|
pub(all) struct DebugEntry {
  kind : Int32
  kind_name : String
  timestamp : Int32
  size : Int32
  address : Int32
  pdb_signature : String?
  pdb_age : Int32?
  pdb_path : String?
} derive(@debug.Debug)

///|
/// The documented name of a debug-directory type.
pub fn debug_type_name(kind : Int32) -> String {
  match kind {
    0 => "Unknown"
    1 => "Coff"
    2 => "CodeView"
    3 => "FPO"
    4 => "Misc"
    5 => "Exception"
    6 => "Fixup"
    9 => "Borland"
    10 => "Reserved10"
    11 => "Clsid"
    12 => "VCFeature"
    13 => "POGO"
    14 => "ILTCG"
    15 => "MPX"
    16 => "Repro"
    17 => "EmbeddedPortablePDB"
    19 => "PdbChecksum"
    20 => "ExDllCharacteristics"
    _ => "Unknown(" + kind.to_string() + ")"
  }
}

///|
/// Read every debug-directory entry.
pub fn Pe::debug_entries(self : Pe) -> Array[DebugEntry] raise PeError {
  let out : Array[DebugEntry] = []
  let directory = match self.directory(6) {
    None => return out
    Some(directory) => directory
  }
  if directory.virtual_address == 0 || directory.size == 0 {
    return out
  }
  let reader = match self.reader_at_rva(directory.virtual_address) {
    Some(reader) => reader
    None =>
      raise PeError::Malformed(
        "debug directory RVA " +
        hex(directory.virtual_address) +
        " is not backed by file bytes",
        0,
      )
  }
  let count = directory.size / 28
  let mut i = 0
  while i < count {
    reader.skip(4)
    let timestamp = reader.u32()
    reader.skip(4)
    let kind = reader.u32()
    let size = reader.u32()
    let address = reader.u32()
    let pointer = reader.u32()
    let mut pdb_signature : String? = None
    let mut pdb_age : Int32? = None
    let mut pdb_path : String? = None
    if kind == 2 && address != 0 {
      let (signature, age, path) = self.read_codeview(address, size)
      pdb_signature = Some(signature)
      pdb_age = Some(age)
      pdb_path = path
    } else if kind == 2 && pointer != 0 {
      let (signature, age, path) = self.read_codeview_at_offset(pointer, size)
      pdb_signature = Some(signature)
      pdb_age = Some(age)
      pdb_path = path
    }
    out.push({
      kind,
      kind_name: debug_type_name(kind),
      timestamp,
      size,
      address,
      pdb_signature,
      pdb_age,
      pdb_path,
    })
    i = i + 1
  }
  out
}

///|
fn Pe::read_codeview(
  self : Pe,
  rva : Int32,
  size : Int32,
) -> (String, Int32, String?) raise PeError {
  match self.rva_to_offset(rva) {
    Some(offset) => self.read_codeview_at_offset(offset, size)
    None =>
      raise PeError::Malformed(
        "CodeView record RVA " + hex(rva) + " is not backed by file bytes",
        0,
      )
  }
}

///|
/// Decode an `RSDS` (PDB 7.0) or `NB10` (PDB 2.0) record.
fn Pe::read_codeview_at_offset(
  self : Pe,
  offset : Int32,
  size : Int32,
) -> (String, Int32, String?) raise PeError {
  let reader = match self.reader_at(offset) {
    Some(reader) => reader
    None =>
      raise PeError::Malformed(
        "CodeView record offset is outside the file", offset,
      )
  }
  let signature = reader.ascii(4)
  if signature == "RSDS" {
    reader.skip(16)
    let age = reader.u32()
    let path = reader.cstring(if size > 24 { size - 24 } else { 512 })
    ("RSDS", age, Some(path))
  } else if signature == "NB10" {
    reader.skip(4)
    reader.skip(4)
    let age = reader.u32()
    let path = reader.cstring(if size > 16 { size - 16 } else { 512 })
    ("NB10", age, Some(path))
  } else {
    (signature, 0, None)
  }
}