qbix 0.0.4

Random access to BAM records by read name using a compact .qbi index
use crate::error::Result;
use std::os::raw::c_char;

const BIOSYN_FORMAT_SAM: u32 = 3;
const INITIAL_SPAN_CAP: usize = 128;

#[repr(C)]
#[derive(Clone, Copy)]
struct BiosynSpan {
    start: u64,
    length: u64,
    class_id: u32,
    reserved: u32,
}

extern "C" {
    fn biosyn_highlight_line(
        format: u32,
        line: *const c_char,
        len: u64,
        zero_based_line_no: u64,
        out: *mut BiosynSpan,
        out_cap: u64,
    ) -> u64;
    fn biosyn_render_ansi_line(
        line: *const c_char,
        len: u64,
        spans: *const BiosynSpan,
        span_count: u64,
        out: *mut c_char,
        out_cap: u64,
    ) -> u64;
}

pub(crate) fn render_sam_ansi(line: &[u8], zero_based_line_no: u64) -> Result<Vec<u8>> {
    let len = u64::try_from(line.len())
        .map_err(|_| "[qbix] SAM line is too long for libbiosyntax".to_string())?;
    let spans = highlight_sam(line, len, zero_based_line_no)?;
    render_ansi(line, len, &spans)
}

fn highlight_sam(line: &[u8], len: u64, zero_based_line_no: u64) -> Result<Vec<BiosynSpan>> {
    let mut spans = vec![empty_span(); INITIAL_SPAN_CAP];
    loop {
        let count = unsafe {
            biosyn_highlight_line(
                BIOSYN_FORMAT_SAM,
                line.as_ptr().cast(),
                len,
                zero_based_line_no,
                spans.as_mut_ptr(),
                spans.len() as u64,
            )
        };
        let count = usize::try_from(count)
            .map_err(|_| "[qbix] libbiosyntax span count is too large".to_string())?;
        if count <= spans.len() {
            spans.truncate(count);
            return Ok(spans);
        }
        spans.resize(count, empty_span());
    }
}

fn render_ansi(line: &[u8], len: u64, spans: &[BiosynSpan]) -> Result<Vec<u8>> {
    let span_count = u64::try_from(spans.len())
        .map_err(|_| "[qbix] libbiosyntax span count is too large".to_string())?;
    let mut out = vec![0u8; line.len().saturating_mul(8).saturating_add(64)];
    loop {
        let required = unsafe {
            biosyn_render_ansi_line(
                line.as_ptr().cast(),
                len,
                spans.as_ptr(),
                span_count,
                out.as_mut_ptr().cast(),
                out.len() as u64,
            )
        };
        let required = usize::try_from(required)
            .map_err(|_| "[qbix] libbiosyntax output is too large".to_string())?;
        if required <= out.len() {
            out.truncate(required);
            return Ok(out);
        }
        out.resize(required, 0);
    }
}

fn empty_span() -> BiosynSpan {
    BiosynSpan {
        start: 0,
        length: 0,
        class_id: 0,
        reserved: 0,
    }
}