#![cfg(feature = "quickcheck")]

extern crate alloc;

#[macro_use]
extern crate quickcheck;
extern crate petgraph;
extern crate rand;
#[macro_use]
extern crate defmac;

extern crate itertools;
extern crate odds;

mod maximal_cliques;
mod utils;

use odds::prelude::*;
use utils::{Small, Tournament};

use alloc::collections::BTreeSet;
use core::hash::Hash;

use hashbrown::{HashMap, HashSet};
use itertools::assert_equal;
use itertools::cloned;
use quickcheck::{Arbitrary, Gen};
use rand::Rng;

#[cfg(feature = "stable_graph")]
use petgraph::algo::steiner_tree;
use petgraph::algo::{
    astar, bellman_ford, bidirectional_dijkstra, bridges, condensation, connected_components,
    dijkstra, dsatur_coloring, find_negative_cycle, floyd_warshall, ford_fulkerson,
    greedy_feedback_arc_set, greedy_matching, is_cyclic_directed, is_cyclic_undirected,
    is_isomorphic, is_isomorphic_matching, johnson, k_shortest_path, kosaraju_scc,
    maximal_cliques as maximal_cliques_algo, maximum_matching, min_spanning_tree, page_rank, spfa,
    tarjan_scc, toposort, Matching,
};
use petgraph::data::FromElements;
use petgraph::dot::{Config, Dot};
use petgraph::graph::{edge_index, node_index, IndexType};
use petgraph::graphmap::NodeTrait;
use petgraph::operator::complement;
use petgraph::prelude::*;
use petgraph::visit::{
    EdgeFiltered, EdgeIndexable, IntoEdgeReferences, IntoEdges, IntoNeighbors, IntoNodeIdentifiers,
    IntoNodeReferences, NodeCount, NodeIndexable, Reversed, Topo, VisitMap, Visitable,
};
use petgraph::EdgeType;

#[cfg(feature = "rayon")]
use petgraph::algo::parallel_johnson;

fn mst_graph<N, E, Ty, Ix>(g: &Graph<N, E, Ty, Ix>) -> Graph<N, E, Undirected, Ix>
where
    Ty: EdgeType,
    Ix: IndexType,
    N: Clone,
    E: Clone + PartialOrd,
{
    Graph::from_elements(min_spanning_tree(&g))
}

use core::fmt;
use petgraph::algo::articulation_points::articulation_points;

quickcheck! {
    fn mst_directed(g: Small<Graph<(), u32>>) -> bool {
        // filter out isolated nodes
        let no_singles = g.filter_map(
            |nx, w| g.neighbors_undirected(nx).next().map(|_| w),
            |_, w| Some(w));
        for i in no_singles.node_indices() {
            assert!(no_singles.neighbors_undirected(i).count() > 0);
        }
        assert_eq!(no_singles.edge_count(), g.edge_count());
        let mst = mst_graph(&no_singles);
        assert!(!is_cyclic_undirected(&mst));
        true
    }
}

quickcheck! {
    fn mst_undirected(g: Graph<(), u32, Undirected>) -> bool {
        // filter out isolated nodes
        let no_singles = g.filter_map(
            |nx, w| g.neighbors_undirected(nx).next().map(|_| w),
            |_, w| Some(w));
        for i in no_singles.node_indices() {
            assert!(no_singles.neighbors_undirected(i).count() > 0);
        }
        assert_eq!(no_singles.edge_count(), g.edge_count());
        let mst = mst_graph(&no_singles);
        assert!(!is_cyclic_undirected(&mst));
        true
    }
}

quickcheck! {
    fn reverse_undirected(g: Small<UnGraph<(), ()>>) -> bool {
        let mut h = (*g).clone();
        h.reverse();
        is_isomorphic(&*g, &h)
    }
}

fn assert_graph_consistent<N, E, Ty, Ix>(g: &Graph<N, E, Ty, Ix>)
where
    Ty: EdgeType,
    Ix: IndexType,
{
    assert_eq!(g.node_count(), g.node_indices().count());
    assert_eq!(g.edge_count(), g.edge_indices().count());
    for edge in g.raw_edges() {
        assert!(
            g.find_edge(edge.source(), edge.target()).is_some(),
            "Edge not in graph! {:?} to {:?}",
            edge.source(),
            edge.target()
        );
    }
}

#[test]
fn reverse_directed() {
    fn prop<Ty: EdgeType>(mut g: Graph<(), (), Ty>) -> bool {
        let node_outdegrees = g
            .node_indices()
            .map(|i| g.neighbors_directed(i, Outgoing).count())
            .collect::<Vec<_>>();
        let node_indegrees = g
            .node_indices()
            .map(|i| g.neighbors_directed(i, Incoming).count())
            .collect::<Vec<_>>();

        g.reverse();
        let new_outdegrees = g
            .node_indices()
            .map(|i| g.neighbors_directed(i, Outgoing).count())
            .collect::<Vec<_>>();
        let new_indegrees = g
            .node_indices()
            .map(|i| g.neighbors_directed(i, Incoming).count())
            .collect::<Vec<_>>();
        assert_eq!(node_outdegrees, new_indegrees);
        assert_eq!(node_indegrees, new_outdegrees);
        assert_graph_consistent(&g);
        true
    }
    quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool);
}

#[test]
fn graph_retain_nodes() {
    fn prop<Ty: EdgeType>(mut g: Graph<i32, i32, Ty>) -> bool {
        // Remove all negative nodes, these should be randomly spread
        let og = g.clone();
        let nodes = g.node_count();
        let num_negs = g.raw_nodes().iter().filter(|n| n.weight < 0).count();
        let mut removed = 0;
        g.retain_nodes(|g, i| {
            let keep = g[i] >= 0;
            if !keep {
                removed += 1;
            }
            keep
        });
        let num_negs_post = g.raw_nodes().iter().filter(|n| n.weight < 0).count();
        let num_pos_post = g.raw_nodes().iter().filter(|n| n.weight >= 0).count();
        assert_eq!(num_negs_post, 0);
        assert_eq!(removed, num_negs);
        assert_eq!(num_negs + g.node_count(), nodes);
        assert_eq!(num_pos_post, g.node_count());

        // check against filter_map
        let filtered = og.filter_map(
            |_, w| if *w >= 0 { Some(*w) } else { None },
            |_, w| Some(*w),
        );
        assert_eq!(g.node_count(), filtered.node_count());
        /*
        println!("Iso of graph with nodes={}, edges={}",
                 g.node_count(), g.edge_count());
                 */
        assert!(is_isomorphic_matching(
            &filtered,
            &g,
            PartialEq::eq,
            PartialEq::eq
        ));

        true
    }
    quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool);
    quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>) -> bool);
}

#[test]
fn graph_retain_edges() {
    fn prop<Ty: EdgeType>(mut g: Graph<(), i32, Ty>) -> bool {
        // Remove all negative edges, these should be randomly spread
        let og = g.clone();
        let edges = g.edge_count();
        let num_negs = g.raw_edges().iter().filter(|n| n.weight < 0).count();
        let mut removed = 0;
        g.retain_edges(|g, i| {
            let keep = g[i] >= 0;
            if !keep {
                removed += 1;
            }
            keep
        });
        let num_negs_post = g.raw_edges().iter().filter(|n| n.weight < 0).count();
        let num_pos_post = g.raw_edges().iter().filter(|n| n.weight >= 0).count();
        assert_eq!(num_negs_post, 0);
        assert_eq!(removed, num_negs);
        assert_eq!(num_negs + g.edge_count(), edges);
        assert_eq!(num_pos_post, g.edge_count());
        if og.edge_count() < 30 {
            // check against filter_map
            let filtered = og.filter_map(
                |_, _| Some(()),
                |_, w| if *w >= 0 { Some(*w) } else { None },
            );
            assert_eq!(g.node_count(), filtered.node_count());
            assert!(is_isomorphic(&filtered, &g));
        }
        true
    }
    quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool);
    quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>) -> bool);
}

#[test]
fn stable_graph_retain_edges() {
    fn prop<Ty: EdgeType>(mut g: StableGraph<(), i32, Ty>) -> bool {
        // Remove all negative edges, these should be randomly spread
        let og = g.clone();
        let edges = g.edge_count();
        let num_negs = g.edge_references().filter(|n| *n.weight() < 0).count();
        let mut removed = 0;
        g.retain_edges(|g, i| {
            let keep = g[i] >= 0;
            if !keep {
                removed += 1;
            }
            keep
        });
        let num_negs_post = g.edge_references().filter(|n| *n.weight() < 0).count();
        let num_pos_post = g.edge_references().filter(|n| *n.weight() >= 0).count();
        assert_eq!(num_negs_post, 0);
        assert_eq!(removed, num_negs);
        assert_eq!(num_negs + g.edge_count(), edges);
        assert_eq!(num_pos_post, g.edge_count());
        if og.edge_count() < 30 {
            // check against filter_map
            let filtered = og.filter_map(
                |_, _| Some(()),
                |_, w| if *w >= 0 { Some(*w) } else { None },
            );
            assert_eq!(g.node_count(), filtered.node_count());
        }
        true
    }
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>) -> bool);
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>) -> bool);
}

#[test]
fn isomorphism_1() {
    // using small weights so that duplicates are likely
    fn prop<Ty: EdgeType>(g: Small<Graph<i8, i8, Ty>>) -> bool {
        let mut rng = rand::thread_rng();
        // several trials of different isomorphisms of the same graph
        // mapping of node indices
        let mut map = g.node_indices().collect::<Vec<_>>();
        let mut ng = Graph::<_, _, Ty>::with_capacity(g.node_count(), g.edge_count());
        for _ in 0..1 {
            rng.shuffle(&mut map);
            ng.clear();

            for _ in g.node_indices() {
                ng.add_node(0);
            }
            // Assign node weights
            for i in g.node_indices() {
                ng[map[i.index()]] = g[i];
            }
            // Add edges
            for i in g.edge_indices() {
                let (s, t) = g.edge_endpoints(i).unwrap();
                ng.add_edge(map[s.index()], map[t.index()], g[i]);
            }
            if g.node_count() < 20 && g.edge_count() < 50 {
                assert!(is_isomorphic(&*g, &ng));
            }
            assert!(is_isomorphic_matching(
                &*g,
                &ng,
                PartialEq::eq,
                PartialEq::eq
            ));
        }
        true
    }
    quickcheck::quickcheck(prop::<Undirected> as fn(_) -> bool);
    quickcheck::quickcheck(prop::<Directed> as fn(_) -> bool);
}

#[test]
fn isomorphism_modify() {
    // using small weights so that duplicates are likely
    fn prop<Ty: EdgeType>(g: Small<Graph<i16, i8, Ty>>, node: u8, edge: u8) -> bool {
        println!("graph {g:#?}");
        let mut ng = (*g).clone();
        let i = node_index(node as usize);
        let j = edge_index(edge as usize);
        if i.index() < g.node_count() {
            ng[i] = (g[i] == 0) as i16;
        }
        if j.index() < g.edge_count() {
            ng[j] = (g[j] == 0) as i8;
        }
        if i.index() < g.node_count() || j.index() < g.edge_count() {
            assert!(!is_isomorphic_matching(
                &*g,
                &ng,
                PartialEq::eq,
                PartialEq::eq
            ));
        } else {
            assert!(is_isomorphic_matching(
                &*g,
                &ng,
                PartialEq::eq,
                PartialEq::eq
            ));
        }
        true
    }
    quickcheck::quickcheck(prop::<Undirected> as fn(_, _, _) -> bool);
    quickcheck::quickcheck(prop::<Directed> as fn(_, _, _) -> bool);
}

#[test]
fn graph_remove_edge() {
    fn prop<Ty: EdgeType>(mut g: Graph<(), (), Ty>, a: u8, b: u8) -> bool {
        let a = node_index(a as usize);
        let b = node_index(b as usize);
        let edge = g.find_edge(a, b);
        if !g.is_directed() {
            assert_eq!(edge.is_some(), g.find_edge(b, a).is_some());
        }
        if let Some(ex) = edge {
            assert!(g.remove_edge(ex).is_some());
        }
        assert_graph_consistent(&g);
        assert!(g.find_edge(a, b).is_none());
        assert!(!g.neighbors(a).any(|x| x == b));
        if !g.is_directed() {
            assert!(!g.neighbors(b).any(|x| x == a));
        }
        true
    }
    quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>, _, _) -> bool);
    quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>, _, _) -> bool);
}

#[cfg(feature = "stable_graph")]
#[test]
fn stable_graph_remove_edge() {
    fn prop<Ty: EdgeType>(mut g: StableGraph<(), (), Ty>, a: u8, b: u8) -> bool {
        let a = node_index(a as usize);
        let b = node_index(b as usize);
        let edge = g.find_edge(a, b);
        if !g.is_directed() {
            assert_eq!(edge.is_some(), g.find_edge(b, a).is_some());
        }
        if let Some(ex) = edge {
            assert!(g.remove_edge(ex).is_some());
        }
        //assert_graph_consistent(&g);
        assert!(g.find_edge(a, b).is_none());
        assert!(!g.neighbors(a).any(|x| x == b));
        if !g.is_directed() {
            assert!(g.find_edge(b, a).is_none());
            assert!(!g.neighbors(b).any(|x| x == a));
        }
        true
    }
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>, _, _) -> bool);
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>, _, _) -> bool);
}

#[cfg(feature = "stable_graph")]
#[test]
fn stable_graph_add_remove_edges() {
    fn prop<Ty: EdgeType>(mut g: StableGraph<(), (), Ty>, edges: Vec<(u8, u8)>) -> bool {
        for &(a, b) in &edges {
            let a = node_index(a as usize);
            let b = node_index(b as usize);
            let edge = g.find_edge(a, b);

            if edge.is_none() && g.contains_node(a) && g.contains_node(b) {
                let _index = g.add_edge(a, b, ());
                continue;
            }

            if !g.is_directed() {
                assert_eq!(edge.is_some(), g.find_edge(b, a).is_some());
            }
            if let Some(ex) = edge {
                assert!(g.remove_edge(ex).is_some());
            }
            //assert_graph_consistent(&g);
            assert!(
                g.find_edge(a, b).is_none(),
                "failed to remove edge {:?} from graph {:?}",
                (a, b),
                g
            );
            assert!(!g.neighbors(a).any(|x| x == b));
            if !g.is_directed() {
                assert!(g.find_edge(b, a).is_none());
                assert!(!g.neighbors(b).any(|x| x == a));
            }
        }
        true
    }
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>, _) -> bool);
    quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>, _) -> bool);
}

fn assert_graphmap_consistent<N, E, Ty>(g: &GraphMap<N, E, Ty>)
where
    Ty: EdgeType,
    N: NodeTrait + fmt::Debug,
{
    for (a, b, _weight) in g.all_edges() {
        assert!(g.contains_edge(a, b), "Edge not in graph! {a:?} to {b:?}");
        assert!(
            g.neighbors(a).any(|x| x == b),
            "Edge {:?} not in neighbor list for {:?}",
            (a, b),
            a
        );
        if !g.is_directed() {
            assert!(
                g.neighbors(b).any(|x| x == a),
                "Edge {:?} not in neighbor list for {:?}",
                (b, a),
                b
            );
        }
    }
}

#[test]
fn graphmap_remove() {
    fn prop<Ty: EdgeType>(mut g: GraphMap<i8, (), Ty>, a: i8, b: i8) -> bool {
        //if g.edge_count() > 20 { return true; }
        assert_graphmap_consistent(&g);
        let contains = g.contains_edge(a, b);
        if !g.is_directed() {
            assert_eq!(contains, g.contains_edge(b, a));
        }
        assert_eq!(g.remove_edge(a, b).is_some(), contains);
        assert!(!g.contains_edge(a, b) && !g.neighbors(a).any(|x| x == b));
        //(g.is_directed() || g.neighbors(b).find(|x| *x == a).is_none()));
        assert!(g.remove_edge(a, b).is_none());
        assert_graphmap_consistent(&g);
        true
    }
    quickcheck::quickcheck(prop as fn(DiGraphMap<_, _>, _, _) -> bool);
    quickcheck::quickcheck(prop as fn(UnGraphMap<_, _>, _, _) -> bool);
}

#[test]
fn graphmap_add_remove() {
    fn prop(mut g: UnGraphMap<i8, ()>, a: i8, b: i8) -> bool {
        assert_eq!(g.contains_edge(a, b), g.add_edge(a, b, ()).is_some());
        g.remove_edge(a, b);
        !g.contains_edge(a, b) && !g.neighbors(a).any(|x| x == b) && !g.neighbors(b).any(|x| x == a)
    }
    quickcheck::quickcheck(prop as fn(_, _, _) -> bool);
}

fn sort_sccs<T: Ord>(v: &mut [Vec<T>]) {
    for scc in &mut *v {
        scc.sort();
    }
    v.sort();
}

quickcheck! {
    fn graph_sccs(g: Graph<(), ()>) -> bool {
        let mut sccs = kosaraju_scc(&g);
        let mut tsccs = tarjan_scc(&g);
        sort_sccs(&mut sccs);
        sort_sccs(&mut tsccs);
        if sccs != tsccs {
            println!("{:?}",
                     Dot::with_config(&g, &[Config::EdgeNoLabel,
                                      Config::NodeIndexLabel]));
            println!("Sccs {sccs:?}");
            println!("Sccs (Tarjan) {tsccs:?}");
            return false;
        }
        true
    }
}

quickcheck! {
    fn kosaraju_scc_is_topo_sort(g: Graph<(), ()>) -> bool {
        let tsccs = kosaraju_scc(&g);
        let firsts = tsccs.iter().rev().map(|v| v[0]).collect::<Vec<_>>();
        subset_is_topo_order(&g, &firsts)
    }
}

quickcheck! {
    fn tarjan_scc_is_topo_sort(g: Graph<(), ()>) -> bool {
        let tsccs = tarjan_scc(&g);
        let firsts = tsccs.iter().rev().map(|v| v[0]).collect::<Vec<_>>();
        subset_is_topo_order(&g, &firsts)
    }
}

quickcheck! {
    // Reversed edges gives the same sccs (when sorted)
    fn graph_reverse_sccs(g: Graph<(), ()>) -> bool {
        let mut sccs = kosaraju_scc(&g);
        let mut tsccs = kosaraju_scc(Reversed(&g));
        sort_sccs(&mut sccs);
        sort_sccs(&mut tsccs);
        if sccs != tsccs {
            println!("{:?}",
                     Dot::with_config(&g, &[Config::EdgeNoLabel,
                                      Config::NodeIndexLabel]));
            println!("Sccs {sccs:?}");
            println!("Sccs (Reversed) {tsccs:?}");
            return false;
        }
        true
    }
}

quickcheck! {
    // Reversed edges gives the same sccs (when sorted)
    fn graphmap_reverse_sccs(g: DiGraphMap<u16, ()>) -> bool {
        let mut sccs = kosaraju_scc(&g);
        let mut tsccs = kosaraju_scc(Reversed(&g));
        sort_sccs(&mut sccs);
        sort_sccs(&mut tsccs);
        if sccs != tsccs {
            println!("{:?}",
                     Dot::with_config(&g, &[Config::EdgeNoLabel,
                                      Config::NodeIndexLabel]));
            println!("Sccs {sccs:?}");
            println!("Sccs (Reversed) {tsccs:?}");
            return false;
        }
        true
    }
}

#[test]
fn graph_condensation_acyclic() {
    fn prop(g: Graph<(), ()>) -> bool {
        !is_cyclic_directed(&condensation(g, /* make_acyclic */ true))
    }
    quickcheck::quickcheck(prop as fn(_) -> bool);
}

#[derive(Debug, Clone)]
struct Dag<N: Default + Clone + Send + 'static>(Graph<N, ()>);

impl<N: Default + Clone + Send + 'static> Arbitrary for Dag<N> {
    fn arbitrary<G: Gen>(g: &mut G) -> Self {
        let nodes = usize::arbitrary(g);
        if nodes == 0 {
            return Dag(Graph::with_capacity(0, 0));
        }
        let split = g.gen_range(0., 1.);
        let max_width = f64::sqrt(nodes as f64) as usize;
        let tall = (max_width as f64 * split) as usize;
        let fat = max_width - tall;

        let edge_prob = 1. - (1. - g.gen_range(0., 1.)) * (1. - g.gen_range(0., 1.));
        let edges = ((nodes as f64).powi(2) * edge_prob) as usize;
        let mut gr = Graph::with_capacity(nodes, edges);
        let mut nodes = 0;
        for _ in 0..tall {
            let cur_nodes = g.gen_range(0, fat);
            for _ in 0..cur_nodes {
                gr.add_node(N::default());
            }
            for j in 0..nodes {
                for k in 0..cur_nodes {
                    if g.gen_range(0., 1.) < edge_prob {
                        gr.add_edge(NodeIndex::new(j), NodeIndex::new(k + nodes), ());
                    }
                }
            }
            nodes += cur_nodes;
        }
        Dag(gr)
    }

    // shrink the graph by splitting it in two by a very
    // simple algorithm, just even and odd node indices
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let self_ = self.clone();
        Box::new((0..2).filter_map(move |x| {
            let gr = self_.0.filter_map(
                |i, w| {
                    if i.index() % 2 == x {
                        Some(w.clone())
                    } else {
                        None
                    }
                },
                |_, _| Some(()),
            );
            // make sure we shrink
            if gr.node_count() < self_.0.node_count() {
                Some(Dag(gr))
            } else {
                None
            }
        }))
    }
}

fn is_topo_order<N>(gr: &Graph<N, (), Directed>, order: &[NodeIndex]) -> bool {
    if gr.node_count() != order.len() {
        println!(
            "Graph ({}) and count ({}) had different amount of nodes.",
            gr.node_count(),
            order.len()
        );
        return false;
    }
    // check all the edges of the graph
    for edge in gr.raw_edges() {
        let a = edge.source();
        let b = edge.target();
        let ai = order.find(&a).unwrap();
        let bi = order.find(&b).unwrap();
        if ai >= bi {
            println!("{a:?} > {b:?} ");
            return false;
        }
    }
    true
}

fn subset_is_topo_order<N>(gr: &Graph<N, (), Directed>, order: &[NodeIndex]) -> bool {
    if gr.node_count() < order.len() {
        println!(
            "Graph (len={}) had less nodes than order (len={})",
            gr.node_count(),
            order.len()
        );
        return false;
    }
    // check all the edges of the graph
    for edge in gr.raw_edges() {
        let a = edge.source();
        let b = edge.target();
        if a == b {
            continue;
        }
        // skip those that are not in the subset
        let ai = match order.find(&a) {
            Some(i) => i,
            None => continue,
        };
        let bi = match order.find(&b) {
            Some(i) => i,
            None => continue,
        };
        if ai >= bi {
            println!("{a:?} > {b:?} ");
            return false;
        }
    }
    true
}

#[test]
fn full_topo() {
    fn prop(Dag(gr): Dag<()>) -> bool {
        let order = toposort(&gr, None).unwrap();
        is_topo_order(&gr, &order)
    }
    quickcheck::quickcheck(prop as fn(_) -> bool);
}

#[test]
fn full_topo_generic() {
    fn prop_generic(Dag(mut gr): Dag<usize>) -> bool {
        assert!(!is_cyclic_directed(&gr));
        let mut index = 0;
        let mut topo = Topo::new(&gr);
        while let Some(nx) = topo.next(&gr) {
            gr[nx] = index;
            index += 1;
        }

        let mut order = Vec::new();
        index = 0;
        let mut topo = Topo::new(&gr);
        while let Some(nx) = topo.next(&gr) {
            order.push(nx);
            assert_eq!(gr[nx], index);
            index += 1;
        }
        if !is_topo_order(&gr, &order) {
            println!("{gr:?}");
            return false;
        }

        {
            order.clear();
            let mut topo = Topo::new(&gr);
            while let Some(nx) = topo.next(&gr) {
                order.push(nx);
            }
            if !is_topo_order(&gr, &order) {
                println!("{gr:?}");
                return false;
            }
        }

        {
            order.clear();
            let init_nodes = gr.node_identifiers().filter(|n| {
                gr.neighbors_directed(*n, Direction::Incoming)
                    .next()
                    .is_none()
            });
            let mut topo = Topo::with_initials(&gr, init_nodes);
            while let Some(nx) = topo.next(&gr) {
                order.push(nx);
            }
            if !is_topo_order(&gr, &order) {
                println!("{gr:?}");
                return false;
            }
        }

        {
            order.clear();
            let mut topo = Topo::with_initials(&gr, gr.node_identifiers());
            while let Some(nx) = topo.next(&gr) {
                order.push(nx);
            }
            if !is_topo_order(&gr, &order) {
                println!("{gr:?}");
                return false;
            }
        }
        true
    }
    quickcheck::quickcheck(prop_generic as fn(_) -> bool);
}

quickcheck! {
    // checks that the distances computed by dijkstra satisfy the triangle
    // inequality.
    fn dijkstra_triangle_ineq(g: Graph<u32, u32>, node: usize) -> bool {
        if g.node_count() == 0 {
            return true;
        }
        let v = node_index(node % g.node_count());
        let distances = dijkstra(&g, v, None, |e| *e.weight());
        for v2 in distances.keys() {
            let dv2 = distances[v2];
            // triangle inequality:
            // d(v,u) <= d(v,v2) + w(v2,u)
            for edge in g.edges(*v2) {
                let u = edge.target();
                let w = edge.weight();
                if distances.contains_key(&u) && distances[&u] > dv2 + w {
                    return false;
                }
            }
        }
        true
    }
}

#[test]
// checks that the distances computed by astar satisfy the triangle inequality.
fn astar_triangle_ineq() {
    fn prop(g: Graph<(), u32, Undirected>, start: usize, end: usize) -> bool {
        if g.node_count() == 0 {
            return true;
        }
        let start_node = node_index(start % g.node_count());
        let end_node = node_index(end % g.node_count());
        if let Some((distance, _)) = astar(
            &g,
            start_node,
            |node| node == end_node,
            |e| *e.weight(),
            |_| 0,
        ) {
            for v in g.node_indices() {
                if let Some(edge) = g.find_edge(v, end_node) {
                    let weight = g.edge_weight(edge).unwrap();
                    // triangle inequality:
                    // d(start_node, end_node) <= d(start_node, v) + w(v, end_node)
                    if let Some((distance_v, _)) =
                        astar(&g, start_node, |node| node == v, |e| *e.weight(), |_| 0)
                    {
                        if distance > distance_v + *weight {
                            return false;
                        }
                    }
                }
            }
        }

        true
    }

    quickcheck::quickcheck(prop as fn(Graph<(), u32, Undirected>, usize, usize) -> bool);
}

#[test]
// checks that the distances computed by astar is equivalent to dijkstra
fn astar_compare_with_dijkstra() {
    fn prop(g: Graph<(), u32, Undirected>, start: usize, end: usize) -> bool {
        if g.node_count() == 0 {
            return true;
        }
        let start_node = node_index(start % g.node_count());
        let end_node = node_index(end % g.node_count());
        let astar_output = astar(
            &g,
            start_node,
            |node| node == end_node,
            |e| *e.weight(),
            |_| 0,
        );

        let dijkstra_output = dijkstra(&g, start_node, Some(end_node), |e| *e.weight());

        if let Some((astar_distance, _)) = astar_output {
            if let Some(dijkstra_distance) = dijkstra_output.get(&end_node) {
                if astar_distance != *dijkstra_distance {
                    return false;
                }
            } else {
                return false;
            }
        } else if dijkstra_output.get(&end_node).is_some() {
            return false;
        }

        true
    }

    quickcheck::quickcheck(prop as fn(Graph<(), u32, Undirected>, usize, usize) -> bool);
}

quickcheck! {
    // checks that the distances computed by k'th shortest path is always greater or equal compared to their dijkstra computation
    fn k_shortest_path_(g: Graph<u32, u32>, node: usize) -> bool {
        if g.node_count() == 0 {
            return true;
        }
        let v = node_index(node % g.node_count());
        let second_best_distances = k_shortest_path(&g, v, None, 2, |e| *e.weight());
        let dijkstra_distances = dijkstra(&g, v, None, |e| *e.weight());
        for v in second_best_distances.keys() {
            if second_best_distances[v] < dijkstra_distances[v] {
                return false;
            }
        }
        true
    }
}

quickcheck! {
    fn bidirectional_dijkstra_directed(g: Graph<u32, u32, Directed>) -> bool {
        test_bidirectional_dijkstra_impl(g)
    }

    fn bidirectional_dijkstra_undirected(g: Graph<u32, u32, Undirected>) -> bool {
        test_bidirectional_dijkstra_impl(g)
    }
}

fn test_bidirectional_dijkstra_impl<Ty>(g: Graph<u32, u32, Ty>) -> bool
where
    Ty: EdgeType,
{
    if g.node_count() <= 1 || g.node_count() > 50 {
        return true;
    }

    for node1 in g.node_identifiers() {
        let dijkstra_res = dijkstra(&g, node1, None, |e| *e.weight());

        for node2 in g.node_identifiers() {
            if node1 == node2 {
                continue;
            }

            let bidirectional_dijkstra_res =
                bidirectional_dijkstra(&g, node1, node2, |e| *e.weight());

            match (dijkstra_res.get(&node2), bidirectional_dijkstra_res) {
                (None, None) => continue,
                (Some(distance), Some(bidirectional_distance)) => {
                    if *distance != bidirectional_distance {
                        return false;
                    }
                }
                // Both algorithms must find a solution for the same problem.
                (Some(_), None) | (None, Some(_)) => return false,
            }
        }
    }

    true
}

quickcheck! {
    // checks floyd_warshall against dijkstra results
    fn floyd_warshall_(g: Graph<u32, u32>) -> bool {
        if g.node_count() == 0 {
            return true;
        }

        let fw_res = floyd_warshall(&g, |e| *e.weight()).unwrap();

        for node1 in g.node_identifiers() {
            let dijkstra_res = dijkstra(&g, node1, None, |e| *e.weight());

            for node2 in g.node_identifiers() {
                // if dijkstra found a path then the results must be same
                if let Some(distance) = dijkstra_res.get(&node2) {
                    let floyd_distance = fw_res.get(&(node1, node2)).unwrap();
                    if distance != floyd_distance {
                        return false;
                    }
                } else {
                    // if there are no path between two nodes then floyd_warshall will return maximum value possible
                    if *fw_res.get(&(node1, node2)).unwrap() != u32::MAX {
                        return false;
                    }
                }
            }
         }
        true
    }
}

quickcheck! {
    // checks that the complement of the complement is the same as the input if the input does not contain self-loops
    fn complement_(g: Graph<u32, u32>, _node: usize) -> bool {
        if g.node_count() == 0 {
            return true;
        }
        for x in g.node_indices() {
            if g.contains_edge(x, x) {
                return true;
            }
        }
        let mut complement_graph: Graph<u32, u32>  = Graph::new();
        let mut result: Graph<u32, u32> = Graph::new();
        complement(&g, &mut complement_graph, 0);
        complement(&complement_graph, &mut result, 0);

        for x in g.node_indices() {
            for y in g.node_indices() {
                if g.contains_edge(x, y) != result.contains_edge(x, y){
                    return false;
                }
            }
        }
        true
    }
}

fn set<I>(iter: I) -> HashSet<I::Item>
where
    I: IntoIterator,
    I::Item: Hash + Eq,
{
    iter.into_iter().collect()
}

quickcheck! {
    fn dfs_visit(gr: Graph<(), ()>, node: usize) -> bool {
        use petgraph::visit::{Visitable, VisitMap};
        use petgraph::visit::DfsEvent::*;
        use petgraph::visit::{Time, depth_first_search};
        if gr.node_count() == 0 {
            return true;
        }
        let start_node = node_index(node % gr.node_count());

        let invalid_time = Time(!0);
        let mut discover_time = vec![invalid_time; gr.node_count()];
        let mut finish_time = vec![invalid_time; gr.node_count()];
        let mut has_tree_edge = gr.visit_map();
        let mut edges = HashSet::new();
        depth_first_search(&gr, Some(start_node).into_iter().chain(gr.node_indices()),
                           |evt| {
            match evt {
                Discover(n, t) => discover_time[n.index()] = t,
                Finish(n, t) => finish_time[n.index()] = t,
                TreeEdge(u, v) => {
                    // v is an ancestor of u
                    assert!(has_tree_edge.visit(v), "Two tree edges to {v:?}!");
                    assert!(discover_time[v.index()] == invalid_time);
                    assert!(discover_time[u.index()] != invalid_time);
                    assert!(finish_time[u.index()] == invalid_time);
                    edges.insert((u, v));
                }
                BackEdge(u, v) => {
                    // u is an ancestor of v
                    assert!(discover_time[v.index()] != invalid_time);
                    assert!(finish_time[v.index()] == invalid_time);
                    edges.insert((u, v));
                }
                CrossForwardEdge(u, v) => {
                    edges.insert((u, v));
                }
            }
        });
        assert!(discover_time.iter().all(|x| *x != invalid_time));
        assert!(finish_time.iter().all(|x| *x != invalid_time));
        assert_eq!(edges.len(), gr.edge_count());
        assert_eq!(edges, set(gr.edge_references().map(|e| (e.source(), e.target()))));
        true
    }
}

quickcheck! {
    fn test_bellman_ford(gr: Graph<(), f32>) -> bool {
        let mut gr = gr;
        for elt in gr.edge_weights_mut() {
            *elt = elt.abs();
        }
        if gr.node_count() == 0 {
            return true;
        }
        for (i, start) in gr.node_indices().enumerate() {
            if i >= 10 { break; } // testing all is too slow
            if bellman_ford(&gr, start).is_err() {
                return false;
            }
        }
        true
    }
}

quickcheck! {
    fn test_find_negative_cycle(gr: Graph<(), f32>) -> bool {
        if gr.node_count() == 0 {
            return true;
        }
        for (i, start) in gr.node_indices().enumerate() {
            if i >= 10 { break; } // testing all is too slow
            if let Some(path) = find_negative_cycle(&gr, start) {
                assert!(!path.is_empty());
            }
        }
        true
    }
}

quickcheck! {
    fn test_bellman_ford_undir(gr: Graph<(), f32, Undirected>) -> bool {
        let mut gr = gr;
        for elt in gr.edge_weights_mut() {
            *elt = elt.abs();
        }
        if gr.node_count() == 0 {
            return true;
        }
        for (i, start) in gr.node_indices().enumerate() {
            if i >= 10 { break; } // testing all is too slow
            if bellman_ford(&gr, start).is_err() {
                return false;
            }
        }
        true
    }
}

defmac!(iter_eq a, b => a.eq(b));
defmac!(nodes_eq ref a, ref b => a.node_references().eq(b.node_references()));
defmac!(edgew_eq ref a, ref b => a.edge_references().eq(b.edge_references()));
defmac!(edges_eq ref a, ref b =>
        iter_eq!(
            a.edge_references().map(|e| (e.source(), e.target())),
            b.edge_references().map(|e| (e.source(), e.target()))));

quickcheck! {
    fn test_di_from(gr1: DiGraph<i32, i32>) -> () {
        let sgr = StableGraph::from(gr1.clone());
        let gr2 = Graph::from(sgr);

        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }
    fn test_un_from(gr1: UnGraph<i32, i32>) -> () {
        let sgr = StableGraph::from(gr1.clone());
        let gr2 = Graph::from(sgr);

        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }

    fn test_graph_from_stable_graph(gr1: StableDiGraph<usize, usize>) -> () {
        let mut gr1 = gr1;
        let gr2 = Graph::from(gr1.clone());

        // renumber the stablegraph nodes and put the new index in the
        // associated data
        let mut index = 0;
        for i in 0..gr1.node_bound() {
            let ni = node_index(i);
            if gr1.contains_node(ni) {
                gr1[ni] = index;
                index += 1;
            }
        }
        if let Some(edge_bound) = gr1.edge_references().next_back()
            .map(|ed| ed.id().index() + 1)
        {
            index = 0;
            for i in 0..edge_bound {
                let ni = edge_index(i);
                if gr1.edge_weight(ni).is_some() {
                    gr1[ni] = index;
                    index += 1;
                }
            }
        }

        assert_equal(
            // Remap the stablegraph to compact indices
            gr1.edge_references().map(|ed| (edge_index(*ed.weight()), gr1[ed.source()], gr1[ed.target()])),
            gr2.edge_references().map(|ed| (ed.id(), ed.source().index(), ed.target().index()))
        );
    }

    fn stable_di_graph_map_id(gr1: StableDiGraph<usize, usize>) -> () {
        let gr2 = gr1.map(|_, &nw| nw, |_, &ew| ew);
        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }

    fn stable_un_graph_map_id(gr1: StableUnGraph<usize, usize>) -> () {
        let gr2 = gr1.map(|_, &nw| nw, |_, &ew| ew);
        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }

    fn stable_di_graph_filter_map_id(gr1: StableDiGraph<usize, usize>) -> () {
        let gr2 = gr1.filter_map(|_, &nw| Some(nw), |_, &ew| Some(ew));
        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }

    fn test_stable_un_graph_filter_map_id(gr1: StableUnGraph<usize, usize>) -> () {
        let gr2 = gr1.filter_map(|_, &nw| Some(nw), |_, &ew| Some(ew));
        assert!(nodes_eq!(&gr1, &gr2));
        assert!(edgew_eq!(&gr1, &gr2));
        assert!(edges_eq!(&gr1, &gr2));
    }

    fn stable_di_graph_filter_map_remove(gr1: Small<StableDiGraph<i32, i32>>,
                                         nodes: Vec<usize>,
                                         edges: Vec<usize>) -> ()
    {
        let gr2 = gr1.filter_map(|ix, &nw| {
            if !nodes.contains(&ix.index()) { Some(nw) } else { None }
        },
        |ix, &ew| {
            if !edges.contains(&ix.index()) { Some(ew) } else { None }
        });
        let check_nodes = &set(gr1.node_indices()) - &set(cloned(&nodes).map(node_index));
        let mut check_edges = &set(gr1.edge_indices()) - &set(cloned(&edges).map(edge_index));
        // remove all edges with endpoint in removed nodes
        for edge in gr1.edge_references() {
            if nodes.contains(&edge.source().index()) ||
                nodes.contains(&edge.target().index()) {
                check_edges.remove(&edge.id());
            }
        }
        // assert maintained
        for i in check_nodes {
            assert_eq!(gr1[i], gr2[i]);
        }
        for i in check_edges {
            assert_eq!(gr1[i], gr2[i]);
            assert_eq!(gr1.edge_endpoints(i), gr2.edge_endpoints(i));
        }

        // assert removals
        for i in nodes {
            assert!(gr2.node_weight(node_index(i)).is_none());
        }
        for i in edges {
            assert!(gr2.edge_weight(edge_index(i)).is_none());
        }
    }
}

fn naive_closure_foreach<G, F>(g: G, mut f: F)
where
    G: Visitable + IntoNeighbors + IntoNodeIdentifiers,
    F: FnMut(G::NodeId, G::NodeId),
{
    let mut dfs = Dfs::empty(&g);
    for i in g.node_identifiers() {
        dfs.reset(&g);
        dfs.move_to(i);
        while let Some(nx) = dfs.next(&g) {
            if i != nx {
                f(i, nx);
            }
        }
    }
}

fn naive_closure<G>(g: G) -> Vec<(G::NodeId, G::NodeId)>
where
    G: Visitable + IntoNodeIdentifiers + IntoNeighbors,
{
    let mut res = Vec::new();
    naive_closure_foreach(g, |a, b| res.push((a, b)));
    res
}

fn naive_closure_edgecount<G>(g: G) -> usize
where
    G: Visitable + IntoNodeIdentifiers + IntoNeighbors,
{
    let mut res = 0;
    naive_closure_foreach(g, |_, _| res += 1);
    res
}

quickcheck! {
    fn test_tred(g: Dag<()>) -> bool {
        let acyclic = g.0;
        println!("acyclic graph {:#?}", &acyclic);
        let toposort = toposort(&acyclic, None).unwrap();
        println!("Toposort:");
        for (new, old) in toposort.iter().enumerate() {
            println!("{} -> {}", old.index(), new);
        }
        let (toposorted, revtopo): (petgraph::adj::List<(), usize>, _) =
            petgraph::algo::tred::dag_to_toposorted_adjacency_list(&acyclic, &toposort);
        println!("checking revtopo");
        for (i, ix) in toposort.iter().enumerate() {
            assert_eq!(i, revtopo[ix.index()]);
        }
        println!("toposorted adjacency list: {:#?}", &toposorted);
        let (tred, tclos) = petgraph::algo::tred::dag_transitive_reduction_closure(&toposorted);
        println!("tred: {:#?}", &tred);
        println!("tclos: {:#?}", &tclos);
        if tred.node_count() != tclos.node_count() {
            println!("Different node count");
            return false;
        }
        if acyclic.node_count() != tclos.node_count() {
            println!("Different node count from original graph");
            return false;
        }
        // check the closure
        let mut clos_edges: Vec<(_, _)> = tclos.edge_references().map(|i| (i.source(), i.target())).collect();
        clos_edges.sort();
        let mut tred_closure = naive_closure(&tred);
        tred_closure.sort();
        if tred_closure != clos_edges {
            println!("tclos is not the transitive closure of tred");
            return false
        }
        // check the transitive reduction is a transitive reduction
        for i in tred.edge_references() {
            let filtered = EdgeFiltered::from_fn(&tred, |edge| {
                edge.source() !=i.source() || edge.target() != i.target()
            });
            let new = naive_closure_edgecount(&filtered);
            if new >= clos_edges.len() {
                println!("when removing ({} -> {}) the transitive closure does not shrink",
                         i.source().index(), i.target().index());
                return false
            }
        }
        // check that the transitive reduction is included in the original graph
        for i in tred.edge_references() {
            if acyclic.find_edge(toposort[i.source().index()], toposort[i.target().index()]).is_none() {
                println!("tred is not included in the original graph");
                return false
            }
        }
        println!("ok!");
        true
    }
}

quickcheck! {
    fn greedy_fas_remaining_graph_is_acyclic(g: StableDiGraph<(), ()>) -> bool {
        let mut g = g;
        let fas: Vec<EdgeIndex> = greedy_feedback_arc_set(&g).map(|e| e.id()).collect();

        for edge_id in fas {
            g.remove_edge(edge_id);
        }

        !is_cyclic_directed(&g)
    }

    /// Assert that the size of the feedback arc set of a tournament does not exceed
    /// **|E| / 2 - |V| / 6**
    fn greedy_fas_performance_within_bound(t: Tournament<(), ()>) -> bool {
        let Tournament(g) = t;

        let expected_bound = if g.node_count() < 2 {
            0
        } else {
            ((g.edge_count() as f64) / 2.0 - (g.node_count() as f64) / 6.0) as usize
        };

        let fas_size = greedy_feedback_arc_set(&g).count();

        fas_size <= expected_bound
    }
}

fn is_valid_matching<G: NodeIndexable>(m: &Matching<G>) -> bool {
    // A set of edges is a matching if no two edges from the matching share an
    // endpoint.
    for (s1, t1) in m.edges() {
        for (s2, t2) in m.edges() {
            if s1 == s2 && t1 == t2 {
                continue;
            }

            if s1 == s2 || s1 == t2 || t1 == s2 || t1 == t2 {
                // Two edges share an endpoint.
                return false;
            }
        }
    }

    true
}

fn is_maximum_matching<G: NodeIndexable + IntoEdges + IntoNodeIdentifiers + Visitable>(
    g: G,
    m: &Matching<G>,
) -> bool {
    // Berge's lemma: a matching is maximum iff there is no augmenting path (a
    // path that starts and ends in unmatched vertices, and alternates between
    // matched and unmatched edges). Thus if we find an augmenting path, the
    // matching is not maximum.
    //
    // Start with an unmatched node and traverse the graph alternating matched
    // and unmatched edges. If an unmatched node is found, then an augmenting
    // path was found.
    for unmatched in g.node_identifiers().filter(|u| !m.contains_node(*u)) {
        let visited = &mut g.visit_map();
        let mut stack = Vec::new();

        stack.push((unmatched, false));
        while let Some((u, do_matched_edges)) = stack.pop() {
            if visited.visit(u) {
                for e in g.edges(u) {
                    if e.source() == e.target() {
                        // Ignore self-loops.
                        continue;
                    }

                    let is_matched = m.contains_edge(e.source(), e.target());

                    if do_matched_edges && is_matched || !do_matched_edges && !is_matched {
                        stack.push((e.target(), !do_matched_edges));

                        // Found another free node (other than the starting one)
                        // that is unmatched - an augmenting path.
                        if !is_matched && !m.contains_node(e.target()) && e.target() != unmatched {
                            return false;
                        }
                    }
                }
            }
        }
    }

    true
}

fn is_perfect_matching<G: NodeCount + NodeIndexable>(g: G, m: &Matching<G>) -> bool {
    // By definition.
    g.node_count() % 2 == 0 && m.edges().count() == g.node_count() / 2
}

quickcheck! {
    fn matching(g: Graph<(), (), Undirected>) -> bool {
        let m1 = greedy_matching(&g);
        let m2 = maximum_matching(&g);

        assert!(is_valid_matching(&m1), "greedy_matching returned an invalid matching");
        assert!(is_valid_matching(&m2), "maximum_matching returned an invalid matching");
        assert!(is_maximum_matching(&g, &m2), "maximum_matching returned a matching that is not maximum");
        assert_eq!(m1.is_perfect(), is_perfect_matching(&g, &m1), "greedy_matching incorrectly determined whether the matching is perfect");
        assert_eq!(m2.is_perfect(), is_perfect_matching(&g, &m2), "maximum_matching incorrectly determined whether the matching is perfect");

        true
    }

    fn matching_in_stable_graph(g: StableGraph<(), (), Undirected>) -> bool {
        let m1 = greedy_matching(&g);
        let m2 = maximum_matching(&g);

        assert!(is_valid_matching(&m1), "greedy_matching returned an invalid matching");
        assert!(is_valid_matching(&m2), "maximum_matching returned an invalid matching");
        assert!(is_maximum_matching(&g, &m2), "maximum_matching returned a matching that is not maximum");
        assert_eq!(m1.is_perfect(), is_perfect_matching(&g, &m1), "greedy_matching incorrectly determined whether the matching is perfect");
        assert_eq!(m2.is_perfect(), is_perfect_matching(&g, &m2), "maximum_matching incorrectly determined whether the matching is perfect");

        true
    }
}
quickcheck! {
    fn test_bridges(g: Graph<(), (), Undirected>) -> bool {
        let num = connected_components(&g);
        let br = bridges(&g).map(|edge| edge.id()).collect::<HashSet<_>>();

        for &edge in &br {
            let mut graph = g.clone();
            graph.remove_edge(edge);
            assert_eq!(connected_components(&graph), num+1);
        }

        for e in g.edge_references() {
            if !br.contains(&e.id()) {
               let mut graph = g.clone();
               graph.remove_edge(e.id());
               assert_eq!(connected_components(&graph), num);
           }
        }

        true
    }
}

quickcheck! {
    // The ranks are probabilities,
    // as such they are positive and they should sum up to 1.
    fn test_page_rank_proba(gr: Graph<(), f32>) -> bool {
        if gr.node_count() == 0 {
            return true;
        }
        let tol = 1e-10;
        let ranks: Vec<f64> = page_rank(&gr, 0.85_f64, 5);
        let at_least_one_neg_rank = ranks.iter().any(|rank| *rank < 0.);
        let not_sumup_to_one = (ranks.iter().sum::<f64>() - 1.).abs() > tol;
        if  at_least_one_neg_rank | not_sumup_to_one{
            return false;
        }
        true
    }
}

fn sum_flows<N, F: core::iter::Sum + Copy>(
    gr: &Graph<N, F>,
    flows: &[F],
    node: NodeIndex,
    dir: Direction,
) -> F {
    gr.edges_directed(node, dir)
        .map(|edge| flows[EdgeIndexable::to_index(&gr, edge.id())])
        .sum::<F>()
}

quickcheck! {
    // 1. (Capacity)
    //    The flows should be <= capacities
    // 2. (Flow conservation)
    //    For every internal node (i.e a node different from the
    //    source node and the destination (or sink) node), the sum
    //    of incoming flows (i.e flows of incoming edges) is equal
    //    to the sum of the outgoing flows (i.e flows of outgoing edges).
    // 3. (Maximum flow)
    //    It is equal to the sum of the destination node incoming flows and
    //    also the sum of the outgoing flows of the source node.
    fn test_ford_fulkerson_flows(gr: Graph<usize, u32>) -> bool {
        if gr.node_count() <= 1 || gr.edge_count() == 0 {
            return true;
        }
        let source = NodeIndex::from(0);
        let destination = NodeIndex::from(gr.node_count() as u32 / 2);
        let (max_flow, flows) = ford_fulkerson(&gr, source, destination);
        let capacity_constraint = flows
            .iter()
            .enumerate()
            .all(|(ix, flow)| flow <= gr.edge_weight(EdgeIndexable::from_index(&gr, ix)).unwrap());
        let flow_conservation_constraint = (0..gr.node_count()).all(|ix| {
            let node = NodeIndexable::from_index(&gr, ix);
            if (node != source) && (node != destination){
            sum_flows(&gr, &flows, node, Direction::Outgoing)
                == sum_flows(&gr, &flows, node, Direction::Incoming)
            } else {true}
        });
        let max_flow_constaint = (sum_flows(&gr, &flows, source, Direction::Outgoing) == max_flow)
            && (sum_flows(&gr, &flows, destination, Direction::Incoming) == max_flow);
        return capacity_constraint && flow_conservation_constraint && max_flow_constaint;
    }
}

quickcheck! {
    fn test_dynamic_toposort(g: DiGraph<(), ()>) -> bool {
        use petgraph::acyclic::Acyclic;
        use petgraph::data::{Build, Create};
        use petgraph::algo::toposort;
        use alloc::collections::BTreeMap;
        use core::iter;

        // We will re-build `g` from scratch, adding edges one by one.
        let mut acylic_g =
            Acyclic::<DiGraph<(), ()>>::with_capacity(g.node_count(), g.edge_count());
        let mut new_g = DiGraph::<(), ()>::new();

        // This test is quite slow, so we bound the number of nodes.
        const MAX_NODES: usize = 30;
        let nodes: BTreeSet<_> = g.node_indices().take(MAX_NODES).collect();

        // Add all nodes
        let acyclic_nodes: BTreeMap<_, _> = nodes
            .iter()
            .zip(iter::repeat_with(|| acylic_g.add_node(())))
            .collect();
        let new_nodes: BTreeMap<_, _> = nodes
            .iter()
            .zip(iter::repeat_with(|| new_g.add_node(())))
            .collect();

        // Now add edges one by one
        for e in g.edge_indices() {
            let (src, dst) = g.edge_endpoints(e).unwrap();
            if !nodes.contains(&src) || !nodes.contains(&dst) {
                continue;
            }
            let new_g_backup = new_g.clone();

            // Add the edge to the new graph
            new_g.add_edge(new_nodes[&src], new_nodes[&dst], ());
            let is_dag_exp = toposort(&new_g, None).is_ok();

            // Add the edge to the acyclic graph
            let is_dag_dyn = acylic_g
                .try_add_edge(acyclic_nodes[&src], acyclic_nodes[&dst], ())
                .is_ok();

            // Check that both approaches agree on whether the graph is a DAG
            assert_eq!(is_dag_exp, is_dag_dyn);

            if !is_dag_exp {
                // Remove the edge that makes it non-acyclic
                new_g = new_g_backup;
            }
        }
        true
    }
}

fn is_proper_coloring<G>(g: G, coloring: &HashMap<G::NodeId, usize>) -> bool
where
    G: IntoNodeIdentifiers + IntoEdges,
    G::NodeId: Eq + Hash,
{
    for node in g.node_identifiers() {
        for nbor in g.neighbors(node) {
            if node != nbor && coloring[&node] == coloring[&nbor] {
                return false;
            }
        }
    }
    true
}

quickcheck! {
    fn dsatur_coloring_quickcheck(g: Graph<(), (), Undirected>) -> bool {
        let (coloring, _) = dsatur_coloring(&g);
        assert!(is_proper_coloring(&g, &coloring), "dsatur_coloring returned a non proper coloring");
        true
    }
}

quickcheck! {
    // Test that removal of articulation points will always increase the amount of connected components.
    fn test_articulation_points(g: Graph<(), u32, Undirected>) -> bool {

        let articulation_points = articulation_points(&g);
        let original_components = connected_components(&g);

        for point in articulation_points {
        let mut modified_graph = g.clone();
        modified_graph.remove_node(point);
        let new_components = connected_components(&modified_graph);
        if new_components <= original_components {
            return false;
        }
    }
        true
    }
}

#[cfg(feature = "stable_graph")]
#[test]
fn steiner_tree_spans_terminals() {
    fn prop(g: UnGraph<(), u32>) -> bool {
        if g.node_count() <= 1 {
            return true; // We naturally don't support steiner trees with zero or one node
        }

        // Run the steiner tree algorithm on connected components, to test it on both
        // connected and disconnected graphs.
        let mut connected_components = Vec::new();
        let mut visited = g.visit_map();

        for node in g.node_indices() {
            if !visited.is_visited(&node) {
                let mut component = HashSet::new();

                let mut dfs = Dfs::new(&g, node);
                while let Some(nx) = dfs.next(&g) {
                    visited.visit(nx);
                    component.insert(nx);
                }

                connected_components.push(component);
            }
        }

        for component in connected_components {
            if component.len() < 2 {
                continue; // We naturally don't support steiner trees with zero or one node
            } else {
                let g = g.filter_map(
                    |node_index, _| {
                        if component.contains(&node_index) {
                            Some(())
                        } else {
                            None
                        }
                    },
                    |edge_index, edge_weight| {
                        let edge = g.edge_endpoints(edge_index).unwrap();
                        if component.contains(&(edge.0)) && component.contains(&(edge.1)) {
                            Some(*edge_weight)
                        } else {
                            None
                        }
                    },
                );

                let terminals = g.node_indices().take(5).collect::<Vec<_>>();
                let m_steiner_tree = steiner_tree(&g, &terminals);

                let steiner_tree_nodes: Vec<NodeIndex> = m_steiner_tree.node_indices().collect();

                let spans_terminals = terminals.iter().all(|&t| steiner_tree_nodes.contains(&t));

                if !spans_terminals {
                    return false; // The steiner tree does not span all terminals
                }
            }
        }

        true
    }

    quickcheck::quickcheck(prop as fn(Graph<(), u32, Undirected>) -> bool);
}

#[test]
fn maximal_cliques_matches_ref_impl() {
    use maximal_cliques::maximal_cliques_ref;

    fn prop<Ty>(g: Graph<(), (), Ty>) -> bool
    where
        Ty: EdgeType,
    {
        // Our implementations of maximal cliques only works for undirected graphs
        // or symmetric directed graphs. So we filter out directed edges if needed.
        let g = if Ty::is_directed() {
            g.filter_map(
                |_, _| Some(()),
                |edge_index, _| {
                    let (source, target) = g.edge_endpoints(edge_index).unwrap();
                    if g.contains_edge(target, source) {
                        Some(())
                    } else {
                        None
                    }
                },
            )
        } else {
            g
        };
        if g.edge_count() <= 200 && g.node_count() <= 200 {
            let cliques = maximal_cliques_algo(&g);
            let cliques_ref = maximal_cliques_ref(&g);

            assert!(cliques.len() == cliques_ref.len(),
                "Maximal cliques algo returned different number of cliques than the reference implementation: {} != {}",
                cliques.len(),
                cliques_ref.len()
            );

            for c in &cliques_ref {
                assert!(
                    cliques.contains(c),
                    "Ref Clique {c:?} not found in the result of maximal_cliques_algo: {cliques:?}"
                );
            }
        }
        true
    }
    quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>) -> bool);
    quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool);
}

quickcheck! {
    fn test_spfa(gr: Graph<(), f32>) -> bool {
        let mut gr = gr;
        for elt in gr.edge_weights_mut() {
            *elt = elt.abs();
        }
        if gr.node_count() == 0 {
            return true;
        }
        for (i, start) in gr.node_indices().enumerate() {
            if i >= 10 { break; } // testing all is too slow
            let spfa_res = spfa(&gr, start, |edge| *edge.weight());
            let bf_res = bellman_ford(&gr, start);
            // We only compare the predecessors, since the algorithms use different actual values
            // to represent inf weights.
            if spfa_res.map(|p| p.predecessors) != bf_res.map(|p| p.predecessors) {
                return false;
            }
        }
        true
    }
}

quickcheck! {
    fn test_spfa_undir(gr: Graph<(), f32, Undirected>) -> bool {
        let mut gr = gr;
        for elt in gr.edge_weights_mut() {
            *elt = elt.abs();
        }
        if gr.node_count() == 0 {
            return true;
        }
        for (i, start) in gr.node_indices().enumerate() {
            if i >= 10 { break; } // testing all is too slow
            let spfa_res = spfa(&gr, start, |edge| *edge.weight());
            let bf_res = bellman_ford(&gr, start);
            // We only compare the predecessors, since the algorithms use different actual values
            // to represent inf weight.
            if spfa_res.map(|p| p.predecessors) != bf_res.map(|p| p.predecessors) {
                return false;
            }
        }
        true
    }
}

quickcheck! {
    // checks johnson against dijkstra results
    fn johnson_(g: Graph<u32, u32>) -> bool {
        if g.node_count() == 0 {
            return true;
        }

        let johnson_res = johnson(&g, |e| *e.weight()).unwrap();

        for node1 in g.node_identifiers() {
            let dijkstra_res = dijkstra(&g, node1, None, |e| *e.weight());

            for node2 in g.node_identifiers() {
                // The results must be same
                if johnson_res.get(&(node1, node2)) != dijkstra_res.get(&node2) {
                    return false;
                }
            }
        }

        true
    }
}

#[cfg(feature = "rayon")]
quickcheck! {
    // checks parallel_johnson against dijkstra results
    fn parallel_johnson_(g: Graph<u32, u32>) -> bool {
        if g.node_count() == 0 {
            return true;
        }

        let johnson_res = parallel_johnson(&g, |e| *e.weight()).unwrap();

        for node1 in g.node_identifiers() {
            let dijkstra_res = dijkstra(&g, node1, None, |e| *e.weight());

            for node2 in g.node_identifiers() {
                // The results must be same
                if johnson_res.get(&(node1, node2)) != dijkstra_res.get(&node2) {
                    return false;
                }
            }
        }

        true
    }
}
