use crate::{
alloc::{MemoryPoolObject, PooledObject},
HtsResult,
};
use crate::{
hts_ffi::{
bcf_destroy, bcf_get_fmt, bcf_get_info, bcf_init, bcf_is_snp, bcf_unpack, htslib_obj,
vcf_format, HtslibReturnValue, BCF_DT_CTG, BCF_DT_ID, BCF_UN_FLT, BCF_UN_FMT, BCF_UN_INFO,
BCF_UN_STR,
},
Nucleotide,
};
use std::{
ffi::CStr,
fmt::{Display, Formatter, Result as FmtResult},
ptr::NonNull,
};
use super::{info::VcfRecordInfo, PerSampleInfo, VcfFile};
impl MemoryPoolObject for htslib_obj::Bcf {
fn alloc() -> Option<NonNull<Self>> {
Some(unsafe { NonNull::new_unchecked(bcf_init().into_hts_result().ok()?) })
}
fn dealloc(mut obj: NonNull<Self>) {
unsafe { bcf_destroy(obj.as_mut()) }
}
}
pub trait VcfReader {
fn read_next(&self) -> HtsResult<Option<VcfRecord>>;
fn vcf_iter(&self) -> VcfIter<'_, Self> {
VcfIter(self)
}
}
pub struct VcfIter<'a, T: VcfReader + ?Sized>(&'a T);
impl<'a, T: VcfReader> Iterator for VcfIter<'a, T> {
type Item = VcfRecord<'a>;
fn next(&mut self) -> Option<Self::Item> {
(self.0).read_next().ok()?
}
}
pub struct VcfRecord<'a> {
inner: PooledObject<'a, htslib_obj::Bcf>,
file: &'a VcfFile,
}
impl<'a> Display for VcfRecord<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut buf = htslib_obj::KString::new();
if unsafe { vcf_format(self.file.header(), self.inner() as _, &mut buf as _) } < 0 {
return Err(std::fmt::Error);
}
extern "C" {
fn free(s: *mut std::ffi::c_void);
}
let s = unsafe { std::ffi::CStr::from_ptr(buf.s) }
.to_str()
.unwrap_or("<Enocding Error>");
write!(f, "{}", s)?;
unsafe {
free(buf.s as _);
}
Ok(())
}
}
impl<'a> VcfRecord<'a> {
pub(crate) fn from_inner(
inner: PooledObject<'a, htslib_obj::Bcf>,
file: &'a VcfFile,
) -> HtsResult<Self> {
Ok(Self { inner, file })
}
pub(crate) fn inner(&self) -> &htslib_obj::Bcf {
&self.inner
}
unsafe fn get_mut_inner(&self) -> *mut htslib_obj::Bcf {
self.inner() as *const _ as *mut _
}
unsafe fn unpack_and_call<
F: Fn(&htslib_obj::Bcf, &htslib_obj::BcfHeader) -> HtsResult<T>,
T,
>(
&self,
level: u32,
f: F,
) -> HtsResult<T> {
let bcf_obj = self.get_mut_inner();
let hdr_obj = self.file.header() as *const _;
bcf_unpack(bcf_obj, level as i32).into_hts_result()?;
f(&*bcf_obj, &*hdr_obj)
}
pub fn chrom_id(&self) -> i32 {
self.inner().rid
}
pub fn chrom_name(&self) -> Option<&str> {
let chrom_id = self.chrom_id();
let header = self.file.header();
if chrom_id < 0 || chrom_id >= header.n[BCF_DT_CTG as usize] {
return None;
}
let raw_name = unsafe { (*header.id[BCF_DT_CTG as usize]).key };
unsafe { CStr::from_ptr(raw_name).to_str().ok() }
}
pub fn id(&self) -> HtsResult<Option<&str>> {
unsafe {
self.unpack_and_call(BCF_UN_STR, |hdr, _| {
let raw_id = (*hdr).d.id;
Ok(CStr::from_ptr(raw_id).to_str().ok())
})
}
}
pub fn pos(&self) -> i64 {
self.inner().pos
}
pub fn ref_len(&self) -> i64 {
self.inner().rlen
}
pub fn qual(&self) -> f32 {
self.inner().qual
}
pub fn is_snp(&self) -> bool {
unsafe { bcf_is_snp(self.get_mut_inner()) > 0 }
}
pub(crate) fn get_fmt_raw(&self, key: &[u8]) -> HtsResult<&htslib_obj::BcfFmt> {
unsafe {
let fmt_ptr = bcf_get_fmt(
self.file.header() as _,
self.get_mut_inner(),
key.as_ptr() as _,
)
.into_hts_result()?;
Ok(&*fmt_ptr)
}
}
pub fn per_sample_info(&self, key: &[u8]) -> HtsResult<PerSampleInfo> {
Ok(PerSampleInfo::from_inner(
self.get_fmt_raw(key)?,
self.file,
self.inner().n_sample(),
))
}
pub(crate) fn get_info_raw(&self, key: &[u8]) -> Option<&htslib_obj::BcfInfo> {
unsafe {
bcf_get_info(
self.file.header() as _,
self.get_mut_inner(),
key.as_ptr() as _,
)
.as_ref()
}
}
pub fn get_info(&self, key: &[u8]) -> Option<VcfRecordInfo> {
let raw = self.get_info_raw(key)?;
Some(VcfRecordInfo::from_inner(raw, self.file))
}
pub fn format_iter(&self) -> HtsResult<impl Iterator<Item = PerSampleInfo<'_>>> {
let all_formats = unsafe {
self.unpack_and_call(BCF_UN_FMT, |rec, _| {
let n_fmts = rec.n_fmt() as usize;
let raw_fmts = rec.d.fmt;
Ok(std::slice::from_raw_parts(raw_fmts, n_fmts))
})?
};
Ok(all_formats
.into_iter()
.map(move |fmt| PerSampleInfo::from_inner(fmt, self.file, self.inner().n_sample())))
}
pub fn info_iter(&self) -> HtsResult<impl Iterator<Item = VcfRecordInfo<'_>>> {
let all_info = unsafe {
self.unpack_and_call(BCF_UN_INFO, |rec, _| {
let n_info = rec.n_info() as usize;
let raw_info = rec.d.info;
Ok(std::slice::from_raw_parts(raw_info, n_info))
})?
};
Ok(all_info
.into_iter()
.map(move |info| VcfRecordInfo::from_inner(info, self.file)))
}
pub fn allele(&self) -> HtsResult<impl Iterator<Item = Option<&str>>> {
let allele = unsafe {
self.unpack_and_call(BCF_UN_STR, |bcf, _| {
Ok(std::slice::from_raw_parts(
bcf.d.allele,
bcf.n_allele() as usize,
))
})?
};
Ok(allele.into_iter().map(move |&all_ptr| {
let raw_name = unsafe { CStr::from_ptr(all_ptr) };
raw_name.to_str().ok()
}))
}
pub(crate) fn get_filter_raw(&self) -> HtsResult<&[i32]> {
unsafe {
self.unpack_and_call(BCF_UN_FLT, |bcf, _| {
Ok(std::slice::from_raw_parts(bcf.d.flt, bcf.d.n_flt as usize))
})
}
}
pub fn filters(&self) -> HtsResult<impl Iterator<Item = Option<&str>>> {
let raw_filter_list = self.get_filter_raw()?;
Ok(raw_filter_list.into_iter().map(move |&idx| {
let raw_name =
unsafe { (*self.file.header().id[BCF_DT_ID as usize].offset(idx as isize)).key };
let raw_name_cstr = unsafe { CStr::from_ptr(raw_name) };
raw_name_cstr.to_str().ok()
}))
}
pub(crate) fn get_ref_seq_raw(&self) -> HtsResult<&CStr> {
unsafe {
let ptr = self.unpack_and_call(BCF_UN_STR, |bcf, _| Ok(bcf.d.als))?;
Ok(CStr::from_ptr(ptr))
}
}
pub fn ref_seq_str(&self) -> HtsResult<Option<&str>> {
self.get_ref_seq_raw().map(|s| s.to_str().ok())
}
pub fn ref_seq(&self) -> HtsResult<impl Iterator<Item = Nucleotide> + '_> {
let raw_seq = self.get_ref_seq_raw()?;
Ok(raw_seq
.to_bytes()
.into_iter()
.filter_map(|&a| Nucleotide::from_ascii(a)))
}
pub(crate) fn get_alt_seq_raw(&self) -> HtsResult<&CStr> {
let ref_seq = self.get_ref_seq_raw()?;
let alt_seq = ref_seq.to_bytes_with_nul().last().unwrap() as *const u8;
unsafe { Ok(CStr::from_ptr(alt_seq.offset(1) as _)) }
}
pub fn alt_seq_str(&self) -> HtsResult<Option<&str>> {
self.get_alt_seq_raw().map(|s| s.to_str().ok())
}
pub fn alt_seq(&self) -> HtsResult<impl Iterator<Item = Nucleotide> + '_> {
let raw_seq = self.get_alt_seq_raw()?;
Ok(raw_seq
.to_bytes()
.into_iter()
.filter_map(|&a| Nucleotide::from_ascii(a)))
}
}