use std::{num::NonZeroUsize, path::PathBuf, rc::Rc};
use anyhow::bail;
use itertools::Itertools;
use noodles::sam;
use noodles::sam::header::record::value::map::ReferenceSequence;
use noodles::sam::header::record::value::Map;
use noodles::sam::record::ReferenceSequenceName;
use noodles::sam::Header;
use sam::alignment::Record;
use crate::utils::genome::ReferenceGenome;
use self::record_based::features::FeatureNames;
use self::record_based::features::GenomicFeaturesFacet;
use self::record_based::gc_content::GCContentFacet;
use self::record_based::general::GeneralMetricsFacet;
use self::record_based::quality_scores::QualityScoreFacet;
use self::record_based::template_length::TemplateLengthFacet;
use self::sequence_based::coverage::CoverageFacet;
use self::sequence_based::edits::EditsFacet;
pub mod command;
pub mod record_based;
pub mod results;
pub mod sequence_based;
type RecordBasedQualityControlFacetBoxedVec<'a> = Vec<Box<dyn RecordBasedQualityControlFacet + 'a>>;
type SequenceBasedQualityControlFacetBoxedVec<'a> =
Vec<Box<dyn SequenceBasedQualityControlFacet + 'a>>;
pub fn get_qc_facets<'a>(
features_gff: Option<PathBuf>,
feature_names: Option<&'a FeatureNames>,
header: Option<&'a Header>,
reference_fasta: Option<PathBuf>,
reference_genome: Rc<Box<dyn ReferenceGenome>>,
only_facet: Option<String>,
vafs_file_path: Option<PathBuf>,
) -> anyhow::Result<(
RecordBasedQualityControlFacetBoxedVec<'a>,
SequenceBasedQualityControlFacetBoxedVec<'a>,
)> {
let mut record_based_facets: Vec<Box<dyn RecordBasedQualityControlFacet>> = vec![
Box::<GeneralMetricsFacet>::default(),
Box::new(TemplateLengthFacet::with_capacity(1024)),
Box::<GCContentFacet>::default(),
Box::<QualityScoreFacet>::default(),
];
if let Some(features_src) = features_gff {
if let Some(feature_names) = feature_names {
if let Some(header) = header {
record_based_facets.push(Box::new(GenomicFeaturesFacet::try_from(
features_src,
feature_names,
header,
Rc::clone(&reference_genome),
)?));
}
}
}
let mut sequence_based_facets: Vec<Box<dyn SequenceBasedQualityControlFacet>> =
vec![Box::new(CoverageFacet::new(
Rc::clone(&reference_genome),
NonZeroUsize::new(50_000).unwrap(),
))];
if let Some(fasta) = reference_fasta {
sequence_based_facets.push(Box::new(EditsFacet::try_from(&fasta, vafs_file_path)?));
}
if let Some(only) = only_facet {
let record_based_filtered = record_based_facets
.into_iter()
.filter(|x| x.name().eq_ignore_ascii_case(&only))
.collect_vec();
let sequence_based_filtered = sequence_based_facets
.into_iter()
.filter(|x| x.name().eq_ignore_ascii_case(&only))
.collect_vec();
let selected_facets_count = record_based_filtered.len() + sequence_based_filtered.len();
match selected_facets_count {
0 => bail!("No facets matched the specified `--only` flag: {}", only),
1 => return Ok((record_based_filtered, sequence_based_filtered)),
_ => bail!(
"Too many facets matched the specified `--only` flag: {}. This is a \
very strange error, and it should be reported on the Github issues page.",
only
),
}
}
Ok((record_based_facets, sequence_based_facets))
}
#[derive(Debug)]
pub enum ComputationalLoad {
Light,
Moderate,
Heavy,
}
pub trait RecordBasedQualityControlFacet {
fn name(&self) -> &'static str;
fn computational_load(&self) -> ComputationalLoad;
fn process(&mut self, record: &Record) -> anyhow::Result<()>;
fn summarize(&mut self) -> anyhow::Result<()>;
fn aggregate(&self, results: &mut results::Results);
}
pub trait SequenceBasedQualityControlFacet {
fn name(&self) -> &'static str;
fn computational_load(&self) -> ComputationalLoad;
fn supports_sequence_name(&self, name: &str) -> bool;
fn setup(
&mut self,
name: &ReferenceSequenceName,
sequence: &Map<ReferenceSequence>,
) -> anyhow::Result<()>;
fn process(
&mut self,
name: &ReferenceSequenceName,
sequence: &Map<ReferenceSequence>,
record: &Record,
) -> anyhow::Result<()>;
fn teardown(
&mut self,
name: &ReferenceSequenceName,
sequence: &Map<ReferenceSequence>,
) -> anyhow::Result<()>;
fn aggregate(&mut self, results: &mut results::Results);
}
#[cfg(test)]
mod tests {
use crate::utils::genome::get_reference_genome;
use super::*;
#[test]
pub fn it_returns_the_correct_number_of_facets_by_default() {
let (record_based, sequence_based) = get_qc_facets(
None,
None,
None,
None,
Rc::new(get_reference_genome("GRCh38_no_alt_AnalysisSet").unwrap()),
None,
None,
)
.unwrap();
assert_eq!(record_based.len(), 4);
assert_eq!(sequence_based.len(), 1);
}
#[test]
pub fn it_returns_the_correct_number_of_facets_when_only_is_specified() {
let (record_based, sequence_based) = get_qc_facets(
None,
None,
None,
None,
Rc::new(get_reference_genome("GRCh38_no_alt_AnalysisSet").unwrap()),
Some(String::from("GC Content")),
None,
)
.unwrap();
assert_eq!(record_based.len(), 1);
assert_eq!(sequence_based.len(), 0);
assert!(record_based.get(0).unwrap().name() == "GC Content");
}
}