use crate::{self as libradicl, constants};
use anyhow::{self, Context};
use libradicl::rad_types::{TagSection, TagSectionLabel};
use libradicl::record::RecordContext;
use noodles::sam;
use scroll::Pread;
use std::cmp::{Eq, PartialEq};
use std::io::{Read, Write};
use derivative::Derivative;
#[derive(Debug, PartialEq, Eq)]
pub struct RadPrelude {
pub hdr: RadHeader,
pub file_tags: TagSection,
pub read_tags: TagSection,
pub aln_tags: TagSection,
}
#[derive(Derivative)]
#[derivative(Debug, PartialEq, Eq)]
pub struct RadHeader {
pub is_paired: u8,
pub ref_count: u64,
pub ref_names: Vec<String>,
#[derivative(PartialEq = "ignore")]
pub num_chunks: u64,
}
impl Default for RadHeader {
fn default() -> Self {
Self::new()
}
}
impl RadHeader {
pub fn new() -> Self {
Self {
is_paired: 0,
ref_count: 0,
ref_names: vec![],
num_chunks: 0,
}
}
pub fn num_chunks(&self) -> Option<std::num::NonZeroUsize> {
std::num::NonZeroUsize::new(self.num_chunks as usize)
}
pub fn from_bytes<T: Read>(reader: &mut T) -> anyhow::Result<RadHeader> {
let mut rh = RadHeader::new();
let mut buf = [0u8; constants::MAX_REF_NAME_LEN];
reader.read_exact(&mut buf[0..9])?;
rh.is_paired = buf.pread(0)?;
rh.ref_count = buf.pread::<u64>(1)?;
rh.ref_names.reserve_exact(rh.ref_count as usize);
let mut num_read = 0u64;
while num_read < rh.ref_count {
reader.read_exact(&mut buf[0..2])?;
let l: usize = buf.pread::<u16>(0)? as usize;
reader.read_exact(&mut buf[0..l])?;
rh.ref_names
.push(std::str::from_utf8(&buf[0..l])?.to_string());
num_read += 1;
}
reader.read_exact(&mut buf[0..8])?;
rh.num_chunks = buf.pread::<u64>(0)?;
Ok(rh)
}
pub fn from_bam_header(header: &sam::Header) -> RadHeader {
let mut rh = RadHeader {
is_paired: 0,
ref_count: 0,
ref_names: vec![],
num_chunks: 0,
};
let ref_seqs = header.reference_sequences();
rh.ref_count = ref_seqs.len() as u64;
rh.ref_names.reserve_exact(rh.ref_count as usize);
for (k, _v) in ref_seqs.iter() {
rh.ref_names.push(k.to_string());
}
rh
}
pub fn get_size(&self) -> usize {
let mut tot_size = 0usize;
tot_size += std::mem::size_of_val(&self.is_paired) + std::mem::size_of_val(&self.ref_count);
for t in self.ref_names.iter().map(|a| a.len()) {
tot_size += std::mem::size_of::<u16>() + t;
}
tot_size += std::mem::size_of_val(&self.num_chunks);
tot_size
}
pub fn summary(&self, num_refs: Option<usize>) -> anyhow::Result<String> {
use std::fmt::Write as _;
let mut s = String::new();
writeln!(&mut s, "RadHeader {{")?;
writeln!(&mut s, "is_paired: {}", self.is_paired)?;
writeln!(&mut s, "ref_count: {}", self.ref_count)?;
let refs_to_print = match num_refs {
Some(rcount) => rcount.min(self.ref_count as usize),
None => (self.ref_count as usize).min(10_usize),
};
for rn in self.ref_names.iter().take(refs_to_print) {
writeln!(&mut s, " ref: {}", rn)?;
}
if refs_to_print < self.ref_count as usize {
writeln!(&mut s, " ...")?;
}
writeln!(&mut s, "num_chunks: {}", self.num_chunks)?;
writeln!(&mut s, "}}")?;
Ok(s)
}
pub fn write<W: Write>(&self, w: &mut W) -> anyhow::Result<()> {
w.write_all(&self.is_paired.to_le_bytes())?;
let ref_count = self.ref_count;
w.write_all(&ref_count.to_le_bytes())?;
for k in self.ref_names.iter() {
let name_size = k.len() as u16;
w.write_all(&name_size.to_le_bytes())?;
w.write_all(k.as_bytes())?;
}
let initial_num_chunks = self.num_chunks;
w.write_all(&initial_num_chunks.to_le_bytes())?;
Ok(())
}
}
impl RadPrelude {
pub fn from_header_and_tag_sections(
hdr: RadHeader,
file_tags: TagSection,
read_tags: TagSection,
aln_tags: TagSection,
) -> Self {
Self {
hdr,
file_tags,
read_tags,
aln_tags,
}
}
pub fn from_bytes<T: Read>(reader: &mut T) -> anyhow::Result<Self> {
let hdr = RadHeader::from_bytes(reader)?;
let file_tags = TagSection::from_bytes_with_label(reader, TagSectionLabel::FileTags)?;
let read_tags = TagSection::from_bytes_with_label(reader, TagSectionLabel::ReadTags)?;
let aln_tags = TagSection::from_bytes_with_label(reader, TagSectionLabel::AlignmentTags)?;
Ok(Self {
hdr,
file_tags,
read_tags,
aln_tags,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> anyhow::Result<()> {
self.hdr
.write(writer)
.context("could not write the header of the prelude")?;
self.file_tags
.write(writer)
.context("could not write the file-level tags of the prelude")?;
self.read_tags
.write(writer)
.context("could not write the file-level tags of the prelude")?;
self.aln_tags
.write(writer)
.context("could not write the file-level tags of the prelude")?;
Ok(())
}
pub fn summary(&self, num_refs: Option<usize>) -> anyhow::Result<String> {
use std::fmt::Write as _;
let mut s = self.hdr.summary(num_refs)?;
writeln!(&mut s, "[[{:?}]]", self.file_tags)?;
writeln!(&mut s, "[[{:?}]]", self.read_tags)?;
writeln!(&mut s, "[[{:?}]]", self.aln_tags)?;
Ok(s)
}
pub fn get_record_context<R: RecordContext>(&self) -> anyhow::Result<R> {
R::get_context_from_tag_section(&self.file_tags, &self.read_tags, &self.aln_tags)
}
}
#[cfg(test)]
mod tests {
use super::{RadHeader, RadPrelude};
use crate::rad_types::{RadAtomicId, RadIntId, TagMap, TagSection, TagSectionLabel, TagValue};
use crate::rad_types::{RadType, TagDesc};
#[test]
fn can_write_prelude() {
let hdr = RadHeader {
is_paired: 0,
ref_count: 3,
ref_names: vec!["tgt1".to_string(), "tgt2".to_string(), "tgt3".to_string()],
num_chunks: 1,
};
let ft_desc = TagDesc {
name: "ref_lengths".to_string(),
typeid: RadType::Array(RadIntId::U32, RadAtomicId::Int(RadIntId::U32)),
};
let mut file_tags = TagSection::new_with_label(TagSectionLabel::FileTags);
file_tags.add_tag_desc(ft_desc);
let rd_desc = TagDesc {
name: "frag_map_type".to_string(),
typeid: RadType::Int(RadIntId::U8),
};
let mut read_tags = TagSection::new_with_label(TagSectionLabel::ReadTags);
read_tags.add_tag_desc(rd_desc);
let aln_coi = TagDesc {
name: "compressed_ori_ref".to_string(),
typeid: RadType::Int(RadIntId::U32),
};
let aln_mt = TagDesc {
name: "frag_map_type".to_string(),
typeid: RadType::Int(RadIntId::U32),
};
let aln_fl = TagDesc {
name: "frag_len".to_string(),
typeid: RadType::Int(RadIntId::U16),
};
let mut aln_tags = TagSection::new_with_label(TagSectionLabel::AlignmentTags);
aln_tags.add_tag_desc(aln_coi);
aln_tags.add_tag_desc(aln_mt);
aln_tags.add_tag_desc(aln_fl);
let prelude = RadPrelude {
hdr,
file_tags,
read_tags,
aln_tags,
};
let mut buf: Vec<u8> = Vec::new();
prelude
.write(&mut buf)
.expect("cannot write prelude to buffer");
let mut file_tag_map = TagMap::with_keyset(&prelude.file_tags.tags);
file_tag_map.add(TagValue::ArrayU32(vec![1, 2, 3]));
file_tag_map
.write_values(&mut buf)
.expect("cannot write file tag map");
let mut cursor = std::io::Cursor::new(buf);
let new_prelude =
RadPrelude::from_bytes(&mut cursor).expect("cannot read prelude from buffer");
let new_file_tag_map = &prelude
.file_tags
.try_parse_tags_from_bytes(&mut cursor)
.expect("cannot read file TagMap");
println!("new_prelude = {}", new_prelude.summary(None).unwrap());
println!("new_file_tag_map = {:?}", new_file_tag_map);
assert_eq!(prelude, new_prelude);
assert_eq!(&file_tag_map, new_file_tag_map);
}
#[test]
fn preludes_equal_with_different_chunks() {
let hdr = RadHeader {
is_paired: 0,
ref_count: 3,
ref_names: vec!["tgt1".to_string(), "tgt2".to_string(), "tgt3".to_string()],
num_chunks: 1,
};
let ft_desc = TagDesc {
name: "ref_lengths".to_string(),
typeid: RadType::Array(RadIntId::U32, RadAtomicId::Int(RadIntId::U32)),
};
let mut file_tags = TagSection::new_with_label(TagSectionLabel::FileTags);
file_tags.add_tag_desc(ft_desc);
let rd_desc = TagDesc {
name: "frag_map_type".to_string(),
typeid: RadType::Int(RadIntId::U8),
};
let mut read_tags = TagSection::new_with_label(TagSectionLabel::ReadTags);
read_tags.add_tag_desc(rd_desc);
let aln_coi = TagDesc {
name: "compressed_ori_ref".to_string(),
typeid: RadType::Int(RadIntId::U32),
};
let aln_mt = TagDesc {
name: "frag_map_type".to_string(),
typeid: RadType::Int(RadIntId::U32),
};
let aln_fl = TagDesc {
name: "frag_len".to_string(),
typeid: RadType::Int(RadIntId::U16),
};
let mut aln_tags = TagSection::new_with_label(TagSectionLabel::AlignmentTags);
aln_tags.add_tag_desc(aln_coi);
aln_tags.add_tag_desc(aln_mt);
aln_tags.add_tag_desc(aln_fl);
let prelude = RadPrelude {
hdr,
file_tags,
read_tags,
aln_tags,
};
let mut buf: Vec<u8> = Vec::new();
prelude
.write(&mut buf)
.expect("cannot write prelude to buffer");
let mut file_tag_map = TagMap::with_keyset(&prelude.file_tags.tags);
file_tag_map.add(TagValue::ArrayU32(vec![1, 2, 3]));
file_tag_map
.write_values(&mut buf)
.expect("cannot write file tag map");
let mut cursor = std::io::Cursor::new(buf);
let mut new_prelude =
RadPrelude::from_bytes(&mut cursor).expect("cannot read prelude from buffer");
let new_file_tag_map = &prelude
.file_tags
.try_parse_tags_from_bytes(&mut cursor)
.expect("cannot read file TagMap");
new_prelude.hdr.num_chunks = 4;
println!("new_prelude = {}", new_prelude.summary(None).unwrap());
println!("new_file_tag_map = {:?}", new_file_tag_map);
assert_eq!(prelude, new_prelude);
assert_eq!(&file_tag_map, new_file_tag_map);
}
}