use std::collections::HashSet;

use crate::{
    dag::{Dag, DagNode},
    id::ID,
    version::Frontiers,
};

fn get_all_points<T: DagNode, D: Dag<Node = T>>(graph: &D, points: &mut HashSet<ID>, current: &ID) {
    points.insert(*current);
    for to_id in graph.get(*current).unwrap().deps().iter() {
        get_all_points(graph, points, &to_id);
    }
}

pub fn get_end_list<T: DagNode, D: Dag<Node = T>>(graph: &D, start_list: &Frontiers) -> Frontiers {
    let mut end_set: HashSet<ID> = HashSet::new();
    for start_id in start_list.iter() {
        end_dfs(graph, &start_id, &mut end_set);
    }
    end_set.into_iter().collect()
}

fn end_dfs<T: DagNode, D: Dag<Node = T>>(graph: &D, current: &ID, end_set: &mut HashSet<ID>) {
    let binding = graph.get(*current).unwrap();
    let deps = binding.deps();
    if deps.is_empty() {
        end_set.insert(*current);
    }
    for to_id in deps.iter() {
        end_dfs(graph, &to_id, end_set);
    }
}

pub fn calc_critical_version_dfs<T: DagNode, D: Dag<Node = T>>(
    graph: &D,
    start_list: &Frontiers,
    end_list: &Frontiers,
) -> Vec<ID> {
    let mut result: Vec<ID> = vec![];
    let mut points: HashSet<ID> = HashSet::new();
    let start_list_set: HashSet<ID> = HashSet::from_iter(start_list.iter());
    let end_list_set: HashSet<ID> = HashSet::from_iter(end_list.iter());
    for start_id in start_list.iter() {
        get_all_points(graph, &mut points, &start_id);
    }
    for escape in points {
        let mut flag = false;
        for start_id in start_list.iter() {
            if dfs(graph, &start_id, &escape, &end_list_set) {
                flag = true;
                break;
            }
        }
        if flag {
            continue;
        }
        if !end_list_set.contains(&escape) && !start_list_set.contains(&escape) {
            result.push(escape);
        }
    }
    result
}

fn dfs<T: DagNode, D: Dag<Node = T>>(
    graph: &D,
    current: &ID,
    escape: &ID,
    end_list_set: &HashSet<ID>,
) -> bool {
    if current == escape {
        return false;
    }
    if end_list_set.contains(current) {
        return true;
    }
    for to_id in graph.get(*current).unwrap().deps().iter() {
        if dfs(graph, &to_id, escape, end_list_set) {
            return true;
        }
    }
    false
}
