use std::{fmt::Debug, vec};
use itertools::Itertools;
use petgraph::{
    graph::DiGraph,
    visit::{
        Data, EdgeFiltered, EdgeRef, GraphBase, IntoEdgeReferences, IntoEdges, IntoEdgesDirected,
        IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeFiltered,
        Visitable,
    },
    Direction,
};
pub type CFGraph = DiGraph<(), (), usize>;
#[derive(Debug, Clone)]
pub struct CFSubGraph<'a> {
    pub graph: &'a CFGraph,
    pub nodes: Vec<<CFGraph as GraphBase>::NodeId>,
    pub edges: Vec<<CFGraph as GraphBase>::EdgeId>,
}
impl<'a> CFSubGraph<'a> {
    pub fn new(
        graph: &'a CFGraph,
        nodes: impl IntoIterator<Item = <CFGraph as GraphBase>::NodeId>,
        edges: impl IntoIterator<Item = <CFGraph as GraphBase>::EdgeId>,
    ) -> Self {
        Self {
            graph,
            nodes: nodes.into_iter().collect(),
            edges: edges.into_iter().collect(),
        }
    }
    pub fn find_edge(
        &self,
        a: <CFGraph as GraphBase>::NodeId,
        b: <CFGraph as GraphBase>::NodeId,
    ) -> Option<<CFGraph as GraphBase>::EdgeId> {
        self.graph
            .find_edge(a, b)
            .filter(|it| self.edges.contains(it))
    }
}
impl<'a> GraphBase for CFSubGraph<'a> {
    type EdgeId = <CFGraph as GraphBase>::EdgeId;
    type NodeId = <CFGraph as GraphBase>::NodeId;
}
impl<'a> NodeCount for &'a CFSubGraph<'a> {
    fn node_count(&self) -> usize {
        self.nodes.len()
    }
}
impl<'a> IntoNeighbors for &'a CFSubGraph<'a> {
    type Neighbors = vec::IntoIter<Self::NodeId>;
    fn neighbors(self, a: Self::NodeId) -> Self::Neighbors {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges.neighbors(a).collect_vec().into_iter()
    }
}
impl<'a> IntoNeighborsDirected for &'a CFSubGraph<'a> {
    type NeighborsDirected = vec::IntoIter<Self::NodeId>;
    fn neighbors_directed(self, n: Self::NodeId, d: Direction) -> Self::NeighborsDirected {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges
            .neighbors_directed(n, d)
            .collect_vec()
            .into_iter()
    }
}
impl<'a> Data for &'a CFSubGraph<'a> {
    type NodeWeight = <&'a CFGraph as Data>::NodeWeight;
    type EdgeWeight = <&'a CFGraph as Data>::EdgeWeight;
}
impl<'a> IntoEdgeReferences for &'a CFSubGraph<'a> {
    type EdgeRef = <&'a CFGraph as IntoEdgeReferences>::EdgeRef;
    type EdgeReferences = vec::IntoIter<Self::EdgeRef>;
    fn edge_references(self) -> Self::EdgeReferences {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges.edge_references().collect_vec().into_iter()
    }
}
impl<'a> IntoNodeIdentifiers for &'a CFSubGraph<'a> {
    type NodeIdentifiers = vec::IntoIter<Self::NodeId>;
    fn node_identifiers(self) -> Self::NodeIdentifiers {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        filtered_nodes.node_identifiers().collect_vec().into_iter()
    }
}
impl<'a> IntoEdges for &'a CFSubGraph<'a> {
    type Edges = vec::IntoIter<Self::EdgeRef>;
    fn edges(self, a: Self::NodeId) -> Self::Edges {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges.edges(a).collect_vec().into_iter()
    }
}
impl<'a> IntoEdgesDirected for &'a CFSubGraph<'a> {
    type EdgesDirected = vec::IntoIter<Self::EdgeRef>;
    fn edges_directed(self, a: Self::NodeId, dir: Direction) -> Self::EdgesDirected {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges
            .edges_directed(a, dir)
            .collect_vec()
            .into_iter()
    }
}
impl<'a> Visitable for &'a CFSubGraph<'a> {
    type Map = <CFGraph as Visitable>::Map;
    fn visit_map(&self) -> Self::Map {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges.visit_map()
    }
    fn reset_map(&self, map: &mut Self::Map) {
        let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n));
        let filtered_edges =
            EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id()));
        filtered_edges.reset_map(map)
    }
}