///|
const PDB_STRING_TABLE_SIGNATURE : UInt32 = 0xEFFEEFFEU

///|
/// Resource limits for the PDB `/names` string table.
pub struct PdbStringTableLimits {
  max_bytes : Int32
  max_names : Int32
}

///|
pub fn PdbStringTableLimits::default() -> PdbStringTableLimits {
  PdbStringTableLimits::{ max_bytes: 64 * 1024 * 1024, max_names: 1_000_000, }
}

///|
/// Parsed PDB `/names` stream.
pub struct PdbStringTable {
  signature : UInt32
  hash_version : UInt32
  strings : DebugStringTable
  name_ids : Array[UInt32]
  name_count : UInt32
}

///|
pub fn PdbStringTable::signature(self : PdbStringTable) -> UInt32 {
  self.signature
}

///|
pub fn PdbStringTable::hash_version(self : PdbStringTable) -> UInt32 {
  self.hash_version
}

///|
pub fn PdbStringTable::strings(self : PdbStringTable) -> DebugStringTable {
  self.strings
}

///|
pub fn PdbStringTable::name_id_count(self : PdbStringTable) -> Int32 {
  self.name_ids.length()
}

///|
pub fn PdbStringTable::name_id_at(
  self : PdbStringTable,
  index : Int32,
) -> UInt32? {
  if index < 0 || index >= self.name_ids.length() {
    None
  } else {
    Some(self.name_ids[index])
  }
}

///|
pub fn PdbStringTable::name_count(self : PdbStringTable) -> UInt32 {
  self.name_count
}

///|
pub fn PdbStringTable::string_for_id(
  self : PdbStringTable,
  id : UInt32,
) -> String? {
  self.strings.string_at(id)
}

///|
/// Parse the `/names` stream referenced by PDB Info.
pub fn parse_pdb_string_table(
  data : Bytes,
  limits : PdbStringTableLimits,
) -> PdbStringTable raise SymbolError {
  if data.length() > limits.max_bytes {
    raise C13LimitExceeded("PDB string table byte limit exceeded")
  }
  if data.length() < 16 {
    raise TruncatedC13(data.length())
  }
  let (signature, cursor) = read_symbol_u32(data, 0, data.length())
  if signature != PDB_STRING_TABLE_SIGNATURE {
    raise InvalidPdbStringTable("invalid /names signature")
  }
  let (hash_version, cursor) = read_symbol_u32(data, cursor, data.length())
  if hash_version != 1U && hash_version != 2U {
    raise InvalidPdbStringTable("unsupported /names hash version")
  }
  let (byte_size_raw, cursor) = read_symbol_u32(data, cursor, data.length())
  let byte_size = symbol_uint_to_int(byte_size_raw, "PDB string byte size")
  let strings_end = checked_symbol_add(cursor, byte_size, "PDB strings")
  if strings_end > data.length() {
    raise TruncatedC13(strings_end)
  }
  let strings = DebugStringTable::{
    bytes: data.exact_view(start=cursor, end=strings_end).to_owned(),
  }
  let hash_count_offset = strings_end
  let (hash_count_raw, cursor) = read_symbol_u32(
    data,
    hash_count_offset,
    data.length(),
  )
  let hash_count = symbol_uint_to_int(hash_count_raw, "/names hash count")
  if hash_count > limits.max_names {
    raise C13LimitExceeded("/names hash count limit exceeded")
  }
  let ids_end = checked_symbol_add(
    cursor,
    checked_symbol_mul(hash_count, 4, "/names IDs"),
    "/names IDs",
  )
  if ids_end > data.length() {
    raise TruncatedC13(ids_end)
  }
  let name_ids = []
  let mut cursor = cursor
  for _ in 0.. limits.max_names {
    raise C13LimitExceeded("/names count limit exceeded")
  }
  PdbStringTable::{ signature, hash_version, strings, name_ids, name_count, }
}

///|
/// Read and parse a named PDB string table.
pub fn MsfFile::pdb_string_table(
  self : MsfFile,
  info : PdbInfo,
  name : String,
  limits : PdbStringTableLimits,
) -> PdbStringTable raise SymbolError {
  let index = match info.named_stream_index(name) {
    Some(value) => value
    None => raise PdbStringTableUnavailable
  }
  let bytes = self.stream(index) catch { _ => raise PdbStringTableUnavailable }
  parse_pdb_string_table(bytes, limits)
}