Documentation
use std::collections::HashSet;

use gskits::{
    gsbam::bam_record_ext::{BamRecord, BamRecordExt},
    utils::Range,
};
use minimap2::{Aligner, PresetSet};

pub trait TOverrideAlignerParam {
    fn modify_aligner(&self, aligner: &mut Aligner<PresetSet>);
}

#[derive(Default, Debug)]
pub struct InputFilterParams {
    pub np_range: Option<Range<i32>>,
    pub rq_range: Option<Range<f32>>,
    pub ch_idx: Option<usize>,
}

impl InputFilterParams {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_np_range(mut self, np_range_str: &str) -> Self {
        self.np_range = Some(Range::<i32>::new(np_range_str));
        self
    }

    pub fn set_rq_range(mut self, rq_range_str: &str) -> Self {
        self.rq_range = Some(Range::<f32>::new(rq_range_str));
        self
    }

    pub fn set_ch_idx(mut self, ch_idx: Option<usize>) -> Self {
        self.ch_idx = ch_idx;
        self
    }

    pub fn valid(&self, record: &BamRecord) -> bool {
        let record_ext = BamRecordExt::new(record);

        if let Some(np_range_) = &self.np_range {
            if let Some(np) = record_ext.get_np() {
                if !np_range_.within_range(np as i32) {
                    return false;
                }
            }
        }

        if let Some(rq_range_) = &self.rq_range {
            if let Some(rq) = record_ext.get_rq() {
                if !rq_range_.within_range(rq) {
                    return false;
                }
            }
        }

        if let Some(ch_idx) = self.ch_idx {
            return record_ext
                .get_ch()
                .map(|v| v as usize)
                .unwrap_or(usize::MAX)
                == ch_idx;
        }

        true
    }
}

#[derive(Debug, Clone, Default)]
pub struct AlignParams {
    pub matching_score: Option<i32>,
    pub mismatch_penalty: Option<i32>,
    pub gap_open_penalty: Option<String>,
    pub gap_extension_penalty: Option<String>,
}

impl AlignParams {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_m_score(mut self, m_score: i32) -> Self {
        self.matching_score = Some(m_score);
        self
    }

    pub fn set_mm_score(mut self, mm_score: i32) -> Self {
        self.mismatch_penalty = Some(mm_score);
        self
    }

    pub fn set_gap_open_penalty(mut self, go: String) -> Self {
        self.gap_open_penalty = Some(go);
        self
    }

    pub fn set_gap_extension_penalty(mut self, ge: String) -> Self {
        self.gap_extension_penalty = Some(ge);
        self
    }
}

impl TOverrideAlignerParam for AlignParams {
    fn modify_aligner(&self, aligner: &mut Aligner<PresetSet>) {
        if let Some(m) = self.matching_score {
            aligner.mapopt.a = m;
        }

        if let Some(mm) = self.mismatch_penalty {
            aligner.mapopt.b = mm;
        }

        if let Some(gap_o) = &self.gap_open_penalty {
            if gap_o.contains(",") {
                let (o1, o2) = gap_o.rsplit_once(",").unwrap();
                aligner.mapopt.q = o1.parse::<i32>().unwrap();
                aligner.mapopt.q2 = o2.parse::<i32>().unwrap();
            } else {
                aligner.mapopt.q = gap_o.parse::<i32>().unwrap();
            }
        }

        if let Some(gap_e) = &self.gap_extension_penalty {
            if gap_e.contains(",") {
                let (e1, e2) = gap_e.rsplit_once(",").unwrap();
                aligner.mapopt.e = e1.parse::<i32>().unwrap();
                aligner.mapopt.e2 = e2.parse::<i32>().unwrap();
            } else {
                aligner.mapopt.e = gap_e.parse::<i32>().unwrap();
            }
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct MapParams {}

impl TOverrideAlignerParam for MapParams {
    fn modify_aligner(&self, _aligner: &mut Aligner<PresetSet>) {}
}

#[derive(Debug, Clone, Copy, Default)]
pub struct IndexParams {
    pub kmer: Option<usize>,
    pub wins: Option<usize>,
}

impl IndexParams {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_kmer(mut self, kmer: usize) -> Self {
        self.kmer = Some(kmer);
        self
    }

    pub fn set_wins(mut self, wins: usize) -> Self {
        self.wins = Some(wins);
        self
    }
}

impl TOverrideAlignerParam for IndexParams {
    fn modify_aligner(&self, aligner: &mut Aligner<PresetSet>) {
        if let Some(k) = self.kmer {
            aligner.idxopt.k = k as i16;
        }
        if let Some(w) = self.wins {
            aligner.idxopt.w = w as i16;
        }
    }
}

#[derive(Debug, Clone)]
pub struct OupParams {
    pub discard_secondary: bool,
    pub discard_supplementary: bool,
    pub oup_identity_threshold: f32,
    pub oup_coverage_threshold: f32,
    pub discard_multi_align_reads: bool,
    pub pass_through_tags: HashSet<String>,
}

impl Default for OupParams {
    fn default() -> Self {
        Self {
            discard_secondary: false,
            discard_supplementary: false,
            oup_identity_threshold: -1.0,
            oup_coverage_threshold: -1.0,
            discard_multi_align_reads: false,
            pass_through_tags: HashSet::new(),
        }
    }
}

impl OupParams {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_discard_secondary(mut self, discard_secondary: bool) -> Self {
        if discard_secondary {
            assert!(self.discard_multi_align_reads == false);
        }
        self.discard_secondary = discard_secondary;
        self
    }

    pub fn set_discard_supplementary(mut self, discard_supplementary: bool) -> Self {
        if discard_supplementary {
            assert!(self.discard_multi_align_reads == false);
        }
        self.discard_supplementary = discard_supplementary;
        self
    }
    pub fn set_oup_identity_threshold(mut self, oup_identity_threshold: f32) -> Self {
        self.oup_identity_threshold = oup_identity_threshold;
        self
    }
    pub fn set_oup_coverage_threshold(mut self, oup_coverage_threshold: f32) -> Self {
        self.oup_coverage_threshold = oup_coverage_threshold;
        self
    }
    pub fn set_discard_multi_align_reads(mut self, discard_multi_align_reads: bool) -> Self {
        if discard_multi_align_reads {
            assert!(self.discard_secondary == false);
            assert!(self.discard_supplementary == false);
        }

        self.discard_multi_align_reads = discard_multi_align_reads;
        self
    }

    pub fn set_pass_through_tags(mut self, tags: Option<&String>) -> Self {
        if let Some(tags) = tags {
            self.pass_through_tags = tags
                .trim()
                .split(",")
                .map(|v| v.to_string())
                .collect::<HashSet<_>>();
        }

        self
    }

    pub fn valid(&self, record: &BamRecord) -> bool {
        if self.discard_secondary && record.is_secondary() {
            return false;
        }

        if self.discard_supplementary && record.is_supplementary() {
            return false;
        }

        let record_ext = BamRecordExt::new(&record);
        let iy = record_ext.compute_identity();
        let ec = record_ext.compute_query_coverage();

        if iy < self.oup_identity_threshold {
            return false;
        }

        if ec < self.oup_coverage_threshold {
            return false;
        }

        true
    }
}

impl TOverrideAlignerParam for OupParams {
    fn modify_aligner(&self, aligner: &mut Aligner<PresetSet>) {
        if self.discard_secondary {
            aligner.mapopt.flag &= !0x1000000000;
        }
    }
}