use anyhow::{bail, Context, Result};
use byteorder::{ByteOrder, LittleEndian};
use rustc_hash::{FxHashMap, FxHashSet};
use std::path::Path;
use crate::{append_suffix, safe_mmap_readonly};
#[derive(Debug)]
pub struct A2fMap {
aid_to_fids: FxHashMap<u32, Vec<u32>>,
fid_to_aid: Vec<Option<u32>>,
}
impl A2fMap {
pub fn new(aid_to_fids: FxHashMap<u32, Vec<u32>>, fid_to_aid: Vec<Option<u32>>) -> Self {
Self { aid_to_fids, fid_to_aid }
}
#[inline]
pub fn fids_for_aid(&self, aid: u32) -> Option<&[u32]> {
self.aid_to_fids.get(&aid).map(|v| v.as_slice())
}
#[inline]
pub fn aid_for_fid(&self, fid: u32) -> Option<u32> {
self.fid_to_aid.get(fid as usize).and_then(|x| *x)
}
#[inline]
pub fn map_aids_to_fids_set(&self, aids: &FxHashSet<u32>) -> FxHashSet<u32> {
let mut out = FxHashSet::default();
for &aid in aids {
if let Some(fids) = self.aid_to_fids.get(&aid) {
out.extend(fids.iter().copied());
} else {
eprintln!("[WARN] AID {} not found (no FIDs).", aid);
}
}
out
}
#[inline]
pub fn map_aids_to_fids_vec(&self, aids: &[u32]) -> Vec<u32> {
let mut out = Vec::new();
for &aid in aids {
if let Some(fids) = self.aid_to_fids.get(&aid) {
out.extend_from_slice(fids);
} else {
eprintln!("[WARN] AID {} not found (no FIDs).", aid);
}
}
out
}
#[inline]
pub fn len_fids(&self) -> usize { self.fid_to_aid.len() }
#[inline]
pub fn is_empty(&self) -> bool { self.fid_to_aid.is_empty() }
}
pub fn load_a2f<P: AsRef<Path>>(gff_path: P) -> Result<A2fMap> {
let path = gff_path.as_ref();
let a2f_path = append_suffix(path, ".a2f");
let mmap = safe_mmap_readonly(&a2f_path)
.with_context(|| format!("Failed to mmap {}", a2f_path.display()))?;
if mmap.len() % 4 != 0 {
bail!(
"Corrupted A2F ({}): length {} not aligned to u32",
a2f_path.display(),
mmap.len()
);
}
let n = mmap.len() / 4;
let mut fid_to_aid: Vec<Option<u32>> = Vec::with_capacity(n);
let mut aid_to_fids: FxHashMap<u32, Vec<u32>> = FxHashMap::default();
for (fid, chunk) in mmap.chunks_exact(4).enumerate() {
let raw = LittleEndian::read_u32(chunk);
let aid = if raw == u32::MAX { None } else { Some(raw) };
fid_to_aid.push(aid);
if let Some(aid_val) = aid {
aid_to_fids.entry(aid_val).or_default().push(fid as u32);
}
}
for fids in aid_to_fids.values_mut() {
fids.sort_unstable();
fids.dedup();
}
Ok(A2fMap::new(aid_to_fids, fid_to_aid))
}