use std::io::{self, Write};
use std::path::Path;
use anyhow::{Context, Result};
use bgzf::{CompressionLevel, Writer as BgzfWriter};
use fgumi_raw_bam::RawRecord;
use noodles_sam::Header;
use noodles_sam::io::Writer as SamWriter;
use crate::io_threading::ThreadedWriter;
const BAM_MAGIC: &[u8; 4] = b"BAM\x01";
enum Sink {
File(std::fs::File),
Stdout(io::Stdout),
}
impl Write for Sink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Sink::File(f) => f.write(buf),
Sink::Stdout(s) => s.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Sink::File(f) => f.flush(),
Sink::Stdout(s) => s.flush(),
}
}
}
pub(crate) struct RawBamWriter {
bgzf: Option<BgzfWriter<ThreadedWriter>>,
}
impl RawBamWriter {
pub(crate) fn open(
path: Option<&Path>,
header: &Header,
ring_bytes: usize,
level: CompressionLevel,
) -> Result<Self> {
let sink = match path {
Some(p) if p.to_string_lossy() != "-" => {
let f = std::fs::File::create(p)
.with_context(|| format!("creating {}", p.display()))?;
Sink::File(f)
}
_ => Sink::Stdout(io::stdout()),
};
let threaded = ThreadedWriter::new(sink, ring_bytes);
let mut bgzf = BgzfWriter::new(threaded, level);
write_bam_header(&mut bgzf, header)?;
Ok(Self { bgzf: Some(bgzf) })
}
pub(crate) fn write_record(&mut self, rec: &RawRecord) -> io::Result<()> {
let w = self.bgzf.as_mut().expect("writer already finished");
let block_size = u32::try_from(rec.len())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "record exceeds u32"))?;
w.write_all(&block_size.to_le_bytes())?;
w.write_all(rec.as_ref())?;
Ok(())
}
pub(crate) fn finish(mut self) -> Result<()> {
if let Some(w) = self.bgzf.take() {
let threaded = w.finish().context("flushing BGZF writer")?;
threaded.finish().context("flushing IO writer thread")?;
}
Ok(())
}
}
fn write_bam_header<W: Write>(writer: &mut W, header: &Header) -> Result<()> {
writer.write_all(BAM_MAGIC).context("writing BAM magic")?;
let text = serialize_sam_text(header)?;
let l_text =
u32::try_from(text.len()).map_err(|_| anyhow::anyhow!("BAM header text exceeds u32"))?;
writer.write_all(&l_text.to_le_bytes()).context("writing l_text")?;
writer.write_all(&text).context("writing SAM text")?;
let refs = header.reference_sequences();
let n_ref = u32::try_from(refs.len()).map_err(|_| anyhow::anyhow!("n_ref exceeds u32"))?;
writer.write_all(&n_ref.to_le_bytes()).context("writing n_ref")?;
for (name, map) in refs {
let l_name = u32::try_from(name.len() + 1)
.map_err(|_| anyhow::anyhow!("ref name length exceeds u32"))?;
writer.write_all(&l_name.to_le_bytes()).context("writing l_name")?;
writer.write_all(name).context("writing ref name")?;
writer.write_all(&[0]).context("writing ref name NUL")?;
let l_ref = u32::try_from(usize::from(map.length()))
.map_err(|_| anyhow::anyhow!("ref length exceeds u32"))?;
writer.write_all(&l_ref.to_le_bytes()).context("writing l_ref")?;
}
Ok(())
}
fn serialize_sam_text(header: &Header) -> Result<Vec<u8>> {
let mut buf = SamWriter::new(Vec::new());
buf.write_header(header).context("serializing SAM header text")?;
Ok(buf.into_inner())
}