fastars 0.1.0

Ultra-fast QC and trimming for short and long reads
Documentation
//! Report generation module.
//!
//! This module provides report generation in various formats:
//!
//! - JSON for programmatic access (fastp-compatible)
//! - HTML for interactive human-readable reports with Chart.js visualizations
//!
//! # Example
//!
//! ```no_run
//! use fastars::qc::{Mode, QcStats};
//! use fastars::report::{write_json_report, write_html_report, ReportConfig};
//! use std::fs::File;
//!
//! let stats = QcStats::new(Mode::Short);
//!
//! // Generate JSON report
//! let mut json_file = File::create("report.json").unwrap();
//! write_json_report(&stats, &mut json_file).unwrap();
//!
//! // Generate HTML report
//! let mut html_file = File::create("report.html").unwrap();
//! write_html_report(&stats, &mut html_file).unwrap();
//! ```

pub mod html;
pub mod json;

// Re-export main types and functions
pub use html::{
    write_html_report, write_html_report_filtering, write_html_report_from_filtering_stats,
    write_html_report_with_config, HtmlConfig,
};
pub use json::{
    write_filtering_json_report, write_full_json_report, write_json_report,
    write_json_report_with_config, AdapterStats, BeforeAfterSummary, ContentCurves,
    CorrectionInfo, DuplicationHistogram, DuplicationInfo, FilteringResult, InsertSizeInfo,
    JsonConfig, JsonReport, LengthBin, LengthHistogram, OverrepresentedSeq, ReadStats, Summary,
    TimingInfo,
};

// ============================================================================
// Unified Report Configuration
// ============================================================================

/// Unified configuration for report generation.
///
/// This struct provides a single entry point for configuring both
/// JSON and HTML report generation.
#[derive(Debug, Clone)]
pub struct ReportConfig {
    /// JSON-specific configuration
    pub json: JsonConfig,
    /// HTML-specific configuration
    pub html: HtmlConfig,
    /// Whether to generate JSON report
    pub generate_json: bool,
    /// Whether to generate HTML report
    pub generate_html: bool,
    /// Output path prefix for reports
    pub output_prefix: Option<String>,
}

impl Default for ReportConfig {
    fn default() -> Self {
        Self {
            json: JsonConfig::default(),
            html: HtmlConfig::default(),
            generate_json: true,
            generate_html: true,
            output_prefix: None,
        }
    }
}

impl ReportConfig {
    /// Create a new report configuration with default settings.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the output path prefix.
    pub fn with_output_prefix(mut self, prefix: impl Into<String>) -> Self {
        self.output_prefix = Some(prefix.into());
        self
    }

    /// Enable or disable JSON report generation.
    pub fn with_json(mut self, enabled: bool) -> Self {
        self.generate_json = enabled;
        self
    }

    /// Enable or disable HTML report generation.
    pub fn with_html(mut self, enabled: bool) -> Self {
        self.generate_html = enabled;
        self
    }

    /// Configure JSON output.
    pub fn with_json_config(mut self, config: JsonConfig) -> Self {
        self.json = config;
        self
    }

    /// Configure HTML output.
    pub fn with_html_config(mut self, config: HtmlConfig) -> Self {
        self.html = config;
        self
    }

    /// Set JSON pretty printing.
    pub fn with_pretty_json(mut self, pretty: bool) -> Self {
        self.json.pretty = pretty;
        self
    }

    /// Set HTML report title.
    pub fn with_html_title(mut self, title: impl Into<String>) -> Self {
        self.html.title = title.into();
        self
    }

    /// Get the JSON output path.
    pub fn json_path(&self) -> String {
        match &self.output_prefix {
            Some(prefix) => format!("{}.json", prefix),
            None => "fastars_report.json".to_string(),
        }
    }

    /// Get the HTML output path.
    pub fn html_path(&self) -> String {
        match &self.output_prefix {
            Some(prefix) => format!("{}.html", prefix),
            None => "fastars_report.html".to_string(),
        }
    }
}

// ============================================================================
// Convenience Functions
// ============================================================================

use crate::qc::{FilteringStats, QcStats};
use std::fs::File;
use std::io::BufWriter;

/// Generate all configured reports from QC statistics.
///
/// # Arguments
/// * `stats` - The QC statistics to report
/// * `config` - Report configuration
///
/// # Returns
/// Result indicating success or failure.
pub fn generate_reports(stats: &QcStats, config: &ReportConfig) -> anyhow::Result<()> {
    if config.generate_json {
        let path = config.json_path();
        let file = File::create(&path)?;
        let mut writer = BufWriter::new(file);
        write_json_report_with_config(stats, &config.json, &mut writer)?;
        log::info!("JSON report written to: {}", path);
    }

    if config.generate_html {
        let path = config.html_path();
        let file = File::create(&path)?;
        let mut writer = BufWriter::new(file);
        write_html_report_with_config(stats, None, &config.html, &mut writer)?;
        log::info!("HTML report written to: {}", path);
    }

    Ok(())
}

/// Generate all configured reports from filtering statistics.
///
/// # Arguments
/// * `filtering_stats` - The filtering statistics (before/after)
/// * `config` - Report configuration
///
/// # Returns
/// Result indicating success or failure.
pub fn generate_filtering_reports(
    filtering_stats: &FilteringStats,
    config: &ReportConfig,
) -> anyhow::Result<()> {
    if config.generate_json {
        let path = config.json_path();
        let file = File::create(&path)?;
        let mut writer = BufWriter::new(file);
        write_filtering_json_report(filtering_stats, "", &mut writer)?;
        log::info!("JSON report written to: {}", path);
    }

    if config.generate_html {
        let path = config.html_path();
        let file = File::create(&path)?;
        let mut writer = BufWriter::new(file);
        write_html_report_from_filtering_stats(filtering_stats, &mut writer)?;
        log::info!("HTML report written to: {}", path);
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::qc::Mode;

    #[test]
    fn test_report_module_exists() {
        assert!(true);
    }

    #[test]
    fn test_report_config_default() {
        let config = ReportConfig::default();
        assert!(config.generate_json);
        assert!(config.generate_html);
        assert!(config.output_prefix.is_none());
    }

    #[test]
    fn test_report_config_builder() {
        let config = ReportConfig::new()
            .with_output_prefix("test_output")
            .with_json(true)
            .with_html(false)
            .with_pretty_json(false)
            .with_html_title("Custom Title");

        assert_eq!(config.output_prefix, Some("test_output".to_string()));
        assert!(config.generate_json);
        assert!(!config.generate_html);
        assert!(!config.json.pretty);
        assert_eq!(config.html.title, "Custom Title");
    }

    #[test]
    fn test_report_config_paths() {
        let config = ReportConfig::new().with_output_prefix("results/sample");

        assert_eq!(config.json_path(), "results/sample.json");
        assert_eq!(config.html_path(), "results/sample.html");
    }

    #[test]
    fn test_report_config_default_paths() {
        let config = ReportConfig::new();

        assert_eq!(config.json_path(), "fastars_report.json");
        assert_eq!(config.html_path(), "fastars_report.html");
    }

    #[test]
    fn test_json_report_reexport() {
        // Verify that the main types are properly exported
        let _config = JsonConfig::new();
        let _filtering_result = FilteringResult::default();
    }

    #[test]
    fn test_html_report_reexport() {
        // Verify that the main types are properly exported
        let _config = HtmlConfig::new();
    }

    #[test]
    fn test_generate_reports_creates_files() {
        use tempfile::TempDir;

        let temp_dir = TempDir::new().unwrap();
        let prefix = temp_dir.path().join("test_report");

        let mut stats = QcStats::new(Mode::Short);
        stats.update_raw(b"ATGC", b"IIII");

        let config = ReportConfig::new()
            .with_output_prefix(prefix.to_str().unwrap());

        generate_reports(&stats, &config).unwrap();

        assert!(temp_dir.path().join("test_report.json").exists());
        assert!(temp_dir.path().join("test_report.html").exists());
    }

    #[test]
    fn test_generate_filtering_reports() {
        use tempfile::TempDir;

        let temp_dir = TempDir::new().unwrap();
        let prefix = temp_dir.path().join("filter_report");

        let mut filtering_stats = FilteringStats::new(Mode::Short);
        filtering_stats.before.update_raw(b"ATGC", b"IIII");
        filtering_stats.after.update_raw(b"ATGC", b"IIII");

        let config = ReportConfig::new()
            .with_output_prefix(prefix.to_str().unwrap());

        generate_filtering_reports(&filtering_stats, &config).unwrap();

        assert!(temp_dir.path().join("filter_report.json").exists());
        assert!(temp_dir.path().join("filter_report.html").exists());
    }

    #[test]
    fn test_json_only_report() {
        use tempfile::TempDir;

        let temp_dir = TempDir::new().unwrap();
        let prefix = temp_dir.path().join("json_only");

        let stats = QcStats::new(Mode::Short);
        let config = ReportConfig::new()
            .with_output_prefix(prefix.to_str().unwrap())
            .with_json(true)
            .with_html(false);

        generate_reports(&stats, &config).unwrap();

        assert!(temp_dir.path().join("json_only.json").exists());
        assert!(!temp_dir.path().join("json_only.html").exists());
    }

    #[test]
    fn test_html_only_report() {
        use tempfile::TempDir;

        let temp_dir = TempDir::new().unwrap();
        let prefix = temp_dir.path().join("html_only");

        let stats = QcStats::new(Mode::Short);
        let config = ReportConfig::new()
            .with_output_prefix(prefix.to_str().unwrap())
            .with_json(false)
            .with_html(true);

        generate_reports(&stats, &config).unwrap();

        assert!(!temp_dir.path().join("html_only.json").exists());
        assert!(temp_dir.path().join("html_only.html").exists());
    }
}