use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::ptr::NonNull;
use crate::alloc::MemoryPool;
use crate::hts_ffi::{
hts_close, hts_itr_next, hts_open, hts_set_fai_filename, htslib_obj, sam_hdr_destroy,
sam_hdr_read, sam_index_load, sam_itr_queryi, sam_read1, HtslibReturnValue,
};
use crate::{HtsError, HtsResult};
use super::alignment::{Alignment, AlignmentReader};
enum IndexFile {
OnDisk(Vec<u8>),
Loaded(NonNull<htslib_obj::HtsIndex>),
}
impl IndexFile {
fn new(path: &Path) -> HtsResult<Self> {
let path = CString::new({
if let Some(path) = path.to_str() {
path
} else {
return Err(HtsError::InvalidFileName);
}
})
.map_err(|_| HtsError::InvalidFileName)?;
Ok(IndexFile::OnDisk(path.into_bytes()))
}
fn ensure_index_loaded(
this: &RefCell<Self>,
file: *mut htslib_obj::HtsFile,
) -> HtsResult<NonNull<htslib_obj::HtsIndex>> {
match this.borrow().deref() {
Self::Loaded(idx) => Ok(*idx),
Self::OnDisk(path) => {
let ret = unsafe {
NonNull::new_unchecked(
sam_index_load(file, path.as_ptr() as *const _).into_hts_result()?,
)
};
let mut this = this.borrow_mut();
*this = Self::Loaded(ret);
Ok(ret)
}
}
}
}
pub struct AlignmentFile {
header: NonNull<htslib_obj::SamHeader>,
file: *mut htslib_obj::HtsFile,
index: RefCell<IndexFile>,
pool: MemoryPool<htslib_obj::Bam>,
chrom_name: Box<[HtsResult<String>]>,
}
impl AlignmentReader for AlignmentFile {
fn read_next(&self) -> HtsResult<Option<Alignment>> {
if let Some(mut core_obj) = self.pool.alloc_obj() {
let rc = unsafe { sam_read1(self.file, self.header.as_ptr(), core_obj.deref_mut()) };
if rc == -1 {
return Ok(None);
}
rc.into_hts_result()?;
return Ok(Some(Alignment::from_hts_object(core_obj, self)));
}
Err(HtsError::HtsLibError)
}
}
pub struct AlignmentFileView<'a> {
file: &'a AlignmentFile,
iter: *mut htslib_obj::HtsIterator,
}
impl<'a> AlignmentReader for AlignmentFileView<'a> {
fn read_next(&self) -> HtsResult<Option<Alignment>> {
if let Some(mut core_obj) = self.file.pool.alloc_obj() {
let rc = unsafe {
hts_itr_next(
(*self.file.file).fp.bgzf,
self.iter,
core_obj.deref_mut() as *mut _ as _,
self.file.file as _,
)
};
if rc == -1 {
return Ok(None);
}
rc.into_hts_result()?;
return Ok(Some(Alignment::from_hts_object(core_obj, self.file)));
}
Err(HtsError::HtsLibError)
}
}
impl AlignmentFile {
pub(crate) fn header(&self) -> *const htslib_obj::SamHeader {
self.header.as_ptr()
}
fn raw_chrom_names(&self) -> impl Iterator<Item = &CStr> {
unsafe {
std::slice::from_raw_parts(
self.header.as_ref().target_name,
self.header.as_ref().n_targets as usize,
)
.iter()
.map(|&p| CStr::from_ptr(p))
}
}
pub fn with_reference<P: AsRef<Path>>(&mut self, fai: P) -> HtsResult<()> {
if let Some(Ok(fai)) = fai.as_ref().to_str().map(|e| CString::new(e)) {
unsafe {
hts_set_fai_filename(self.file, fai.as_bytes().as_ptr() as _).into_hts_result()?
};
return Ok(());
}
Err(HtsError::InvalidFileName)
}
pub fn get_chrom_id_by_name(&self, name: &str) -> Option<usize> {
self.raw_chrom_names()
.enumerate()
.find(|(_, this)| this.to_bytes() == name.as_bytes())
.map(|(result, _)| result)
}
pub fn get_chrom_name_by_id(&self, id: usize) -> HtsResult<&str> {
let ntargets = unsafe { self.header.as_ref().n_targets as usize };
if id >= ntargets {
return Err(HtsError::NoSuchChromosome);
}
match &self.chrom_name[id] {
Ok(ref name) => Ok(name),
Err(e) => Err(*e),
}
}
pub fn ranged<'a>(
&'a self,
chrom: &str,
begin: i64,
end: i64,
) -> HtsResult<AlignmentFileView<'a>> {
let index = IndexFile::ensure_index_loaded(&self.index, self.file)?;
if let Some(cid) = self.get_chrom_id_by_name(chrom) {
let iter = unsafe {
sam_itr_queryi(index.as_ref(), cid as i32, begin, end).into_hts_result()?
};
return Ok(AlignmentFileView { file: self, iter });
}
Err(HtsError::NoSuchChromosome)
}
pub fn open<T: AsRef<Path>>(path: T) -> HtsResult<Self> {
let index = IndexFile::new(path.as_ref())?;
let file = if let IndexFile::OnDisk(ref path) = index {
unsafe {
hts_open(path.as_ptr() as *const _, b"rb\0".as_ptr() as *const _)
.into_hts_result()?
}
} else {
unreachable!();
};
let header = unsafe { NonNull::new_unchecked(sam_hdr_read(file).into_hts_result()?) };
let pool = MemoryPool::with_capacity(128);
let mut chrom_name = vec![];
for &raw_chrom_name in unsafe {
std::slice::from_raw_parts(
header.as_ref().target_name,
header.as_ref().n_targets as usize,
)
} {
let raw_chrom_name = unsafe { CStr::from_ptr(raw_chrom_name) };
chrom_name.push(
raw_chrom_name
.to_str()
.map(|s| s.to_owned())
.map_err(|_| HtsError::InvalidChromName),
);
}
let chrom_name = chrom_name.into_boxed_slice();
Ok(Self {
header,
file,
pool,
index: RefCell::new(index),
chrom_name,
})
}
}
impl Drop for AlignmentFile {
fn drop(&mut self) {
unsafe {
sam_hdr_destroy(self.header.as_mut());
hts_close(self.file);
}
}
}