use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

pub fn parse_families<P>(filename: P) -> HashMap<String, u64>
where
    P: AsRef<Path>,
{
    let mut map: HashMap<String, u64> = HashMap::new();
    if let Ok(lines) = read_lines(filename) {
        for line in lines.flatten() {
            let parts: Vec<&str> = line.split("\t").collect();
            let gene = parts[0];
            let family: u64 = parts[1].parse().unwrap();
            map.insert(gene.to_string(), family);
        }
    }
    map
}

pub fn parse_position<P>(filename: P) -> Vec<(String, String, u64, u64)>
where
    P: AsRef<Path>,
{
    let mut vec: Vec<(String, String, u64, u64)> = Vec::new();
    if let Ok(lines) = read_lines(filename) {
        for line in lines.flatten() {
            let parts: Vec<&str> = line.split("\t").collect();
            let gene = parts[0];
            let chromosome = parts[1];
            let start: u64 = parts[2].parse().unwrap();
            let end: u64 = parts[3].parse().unwrap();
            vec.push((gene.to_string(), chromosome.to_string(), start, end));
        }
    }
    vec
}

pub fn compare_gene_position(
    position_a: &(String, String, u64, u64),
    position_b: &(String, String, u64, u64),
) -> Ordering {
    let (_gene_a, chromosome_a, start_a, _end_b) = position_a;
    let (_gene_b, chromosome_b, start_b, _end_b) = position_b;
    if chromosome_a < chromosome_b {
        return Ordering::Less;
    } else if chromosome_a > chromosome_b {
        return Ordering::Greater;
    } else {
        if start_a < start_b {
            return Ordering::Less;
        } else {
            return Ordering::Greater;
        }
    }
}

pub fn detect_tag_of_definitions(
    definitions: &Vec<u64>,
    gene_positions: &Vec<(String, String, u64, u64)>,
    gene_families: &HashMap<String, u64>,
) -> HashMap<String, (String, Vec<Option<u64>>)> {
    let mut tag_numbers: HashMap<String, (String, Vec<Option<u64>>)> = HashMap::new();
    for definition in definitions {
        let tag_numbers_def = detect_tag_of_definition(*definition, gene_positions, gene_families);
        for (gene, (family, tag)) in tag_numbers_def {
            if tag_numbers.contains_key(&gene) {
                let (_gene, tags) = tag_numbers.get_mut(&gene).unwrap();
                tags.push(tag);
            } else {
                tag_numbers.insert(gene, (family, vec![tag]));
            }
        }
    }
    tag_numbers
}

pub fn detect_tag_of_definition(
    definition: u64,
    gene_positions: &Vec<(String, String, u64, u64)>,
    gene_families: &HashMap<String, u64>,
) -> HashMap<String, (String, Option<u64>)> {
    let mut tag_numbers: HashMap<String, (String, Option<u64>)> = HashMap::new();
    let mut spacer_index: u64 = 0;
    let mut tag_index: u64 = 0;
    for (i, (gene_i, chromosome_i, _start_i, _end_i)) in gene_positions.iter().enumerate() {
        let family_repr: String;
        let mut is_on_tag: bool = false;
        if let Some(family_i) = gene_families.get(gene_i) {
            family_repr = format!("{}", family_i);
            let mut nb_intra_tag_spacers: u64 = 0;
            if !tag_numbers.contains_key(gene_i) {
                for (gene_j, chromosome_j, _start_j, _end_j) in &gene_positions[(i + 1)..] {
                    if *chromosome_i != *chromosome_j {
                        break;
                    }
                    if let Some(family_j) = gene_families.get(gene_j) {
                        if *family_i == *family_j {
                            nb_intra_tag_spacers = 0;
                            if !is_on_tag {
                                is_on_tag = true;
                                tag_index += 1;
                                tag_numbers.insert(gene_i.clone(), (family_repr.clone(), Some(tag_index)));
                            }
                            tag_numbers.insert(gene_j.clone(), (family_repr.clone(), Some(tag_index)));
                        } else {
                            nb_intra_tag_spacers += 1;
                        }
                    } else {
                        nb_intra_tag_spacers += 1;
                    }
                    if nb_intra_tag_spacers > definition {
                        break;
                    }
                }
                if !is_on_tag {
                    tag_numbers.insert(gene_i.clone(), (family_repr, None));
                }
            }
        } else {
            spacer_index += 1;
            family_repr = format!("spacer{}", spacer_index);
            tag_numbers.insert(gene_i.clone(), (family_repr, None));
        }
    }
    tag_numbers
}
