use clap::ArgMatches;
use d4::{Chrom, Dictionary};
use rayon::ThreadPoolBuildError;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;

pub type AppResult<T> = Result<T, Box<dyn std::error::Error>>;

pub enum InputType {
    Alignment,
    BedGraph,
    BiwWig,
    Unsupported,
}

impl InputType {
    pub fn detect(path: &Path) -> InputType {
        let ext = path
            .extension()
            .map(|e| e.to_str())
            .flatten()
            .unwrap_or("")
            .to_lowercase();
        match ext.as_str() {
            "sam" | "bam" | "cram" => Self::Alignment,
            "bw" | "bigwig" => Self::BiwWig,
            "txt" | "bedgraph" => Self::BedGraph,
            _ => Self::Unsupported,
        }
    }
}

pub fn parse_genome_file<P: AsRef<Path>>(file: P) -> std::io::Result<Vec<Chrom>> {
    let file = BufReader::new(File::open(file)?);
    Ok(file
        .lines()
        .filter_map(|line| {
            if let Ok(line) = line {
                let tokenized: Vec<_> = line.split(|c| c == '\t').take(2).collect();
                if tokenized.len() == 2 {
                    if let Ok(size) = tokenized[1].parse() {
                        return Some((tokenized[0].to_owned(), size));
                    }
                }
            }
            None
        })
        .map(|(name, size)| Chrom { name, size })
        .collect())
}
pub fn parse_bed_file<P: AsRef<Path>>(
    file: P,
) -> std::io::Result<impl Iterator<Item = (String, u32, u32, f32)>> {
    let file = BufReader::new(File::open(file)?);
    Ok(file.lines().filter_map(|line| {
        if let Ok(line) = line {
            let tokenized: Vec<_> = line.split(|c| c == '\t').take(4).collect();
            if tokenized.len() == 3 {
                if let Ok(pos) = tokenized[1].parse() {
                    if let Ok(dep) = tokenized[2].parse() {
                        return Some((tokenized[0].to_owned(), pos, pos + 1, dep));
                    }
                }
            } else if tokenized.len() == 4 {
                if let Ok(begin) = tokenized[1].parse() {
                    if let Ok(end) = tokenized[2].parse() {
                        if let Ok(dep) = tokenized[3].parse() {
                            return Some((tokenized[0].to_owned(), begin, end, dep));
                        }
                    }
                }
            }
        }
        None
    }))
}

pub fn make_dictionary(
    range_spec: Option<&str>,
    file_spec: Option<&str>,
) -> std::io::Result<Dictionary> {
    if let Some(spec) = range_spec {
        let pattern = regex::Regex::new(r"(?P<from>\d*)-(?P<to>\d*)").unwrap();
        if let Some(caps) = pattern.captures(spec) {
            let from = caps.name("from").unwrap().as_str().parse().unwrap();
            let to = caps.name("to").unwrap().as_str().parse().unwrap();
            return d4::Dictionary::new_simple_range_dict(from, to);
        }
    }
    if let Some(spec) = file_spec {
        let fp = File::open(spec)?;
        return d4::Dictionary::new_dictionary_from_file(fp);
    }
    d4::Dictionary::new_simple_range_dict(0, 64)
}

pub fn setup_thread_pool(matches: &ArgMatches) -> Result<(), ThreadPoolBuildError> {
    if let Some(threads) = matches.value_of("threads") {
        if let Ok(threads) = threads.parse() {
            rayon::ThreadPoolBuilder::new()
                .num_threads(threads)
                .build_global()?;
        }
    }
    Ok(())
}

pub fn check_reference_consistency<'a, I: Iterator<Item = &'a [Chrom]>>(iter: I) -> bool {
    let chrom_matrix: Vec<_> = iter.collect();

    if chrom_matrix.len() > 1 {
        for (i, chrom) in chrom_matrix[0].iter().enumerate() {
            for j in 1..chrom_matrix.len() {
                if &chrom_matrix[j][i] != chrom {
                    return false;
                }
            }
        }
    }

    true
}
