#![cfg(feature = "graphmap")]
extern crate petgraph;

use core::fmt;
use std::collections::HashSet;

use petgraph::prelude::*;
use petgraph::visit::Walker;

use petgraph::algo::dijkstra;

use petgraph::dot::{Config, Dot};

#[test]
fn simple() {
    //let root = TypedArena::<Node<_>>::new();
    let mut gr = UnGraphMap::<_, _>::new();
    //let node = |&: name: &'static str| Ptr(root.alloc(Node(name.to_string())));
    let a = gr.add_node("A");
    let b = gr.add_node("B");
    let c = gr.add_node("C");
    let d = gr.add_node("D");
    let e = gr.add_node("E");
    let f = gr.add_node("F");
    gr.add_edge(a, b, 7);
    gr.add_edge(a, c, 9);
    gr.add_edge(a, d, 14);
    gr.add_edge(b, c, 10);
    gr.add_edge(c, d, 2);
    gr.add_edge(d, e, 9);
    gr.add_edge(b, f, 15);
    gr.add_edge(c, f, 11);

    assert!(gr.add_edge(e, f, 5).is_none());

    // duplicate edges
    assert_eq!(gr.add_edge(f, b, 16), Some(15));
    assert_eq!(gr.add_edge(f, e, 6), Some(5));
    println!("{gr:?}");
    println!("{}", Dot::with_config(&gr, &[]));

    assert_eq!(gr.node_count(), 6);
    assert_eq!(gr.edge_count(), 9);

    // check updated edge weight
    assert_eq!(gr.edge_weight(e, f), Some(&6));
    let scores = dijkstra(&gr, a, None, |e| *e.weight());
    let mut scores: Vec<_> = scores.into_iter().collect();
    scores.sort();
    assert_eq!(
        scores,
        vec![
            ("A", 0),
            ("B", 7),
            ("C", 9),
            ("D", 11),
            ("E", 20),
            ("F", 20)
        ]
    );
}

#[test]
fn edges_directed() {
    let mut gr = DiGraphMap::<_, _>::new();
    let a = gr.add_node("A");
    let b = gr.add_node("B");
    let c = gr.add_node("C");
    let d = gr.add_node("D");
    let e = gr.add_node("E");
    let f = gr.add_node("F");
    gr.add_edge(a, b, 7);
    gr.add_edge(a, c, 9);
    gr.add_edge(a, d, 14);
    gr.add_edge(b, c, 10);
    gr.add_edge(c, d, 2);
    gr.add_edge(d, e, 9);
    gr.add_edge(b, f, 15);
    gr.add_edge(c, f, 11);

    let mut edges_out = gr.edges_directed(c, Direction::Outgoing);
    assert_eq!(edges_out.next(), Some((c, d, &2)));
    assert_eq!(edges_out.next(), Some((c, f, &11)));
    assert_eq!(edges_out.next(), None);

    let mut edges_in = gr.edges_directed(c, Direction::Incoming);
    assert_eq!(edges_in.next(), Some((a, c, &9)));
    assert_eq!(edges_in.next(), Some((b, c, &10)));
    assert_eq!(edges_in.next(), None);
}

#[test]
fn remov() {
    let mut g = UnGraphMap::<_, _>::new();
    g.add_node(1);
    g.add_node(2);
    g.add_edge(1, 2, -1);

    assert_eq!(g.edge_weight(1, 2), Some(&-1));
    assert_eq!(g.edge_weight(2, 1), Some(&-1));
    assert_eq!(g.neighbors(1).count(), 1);

    let noexist = g.remove_edge(2, 3);
    assert_eq!(noexist, None);

    let exist = g.remove_edge(2, 1);
    assert_eq!(exist, Some(-1));
    assert_eq!(g.edge_count(), 0);
    assert_eq!(g.edge_weight(1, 2), None);
    assert_eq!(g.edge_weight(2, 1), None);
    assert_eq!(g.neighbors(1).count(), 0);
}

#[test]
fn remove_node() {
    // From #431
    let mut graph = petgraph::graphmap::DiGraphMap::<u32, ()>::new();
    graph.add_edge(1, 2, ());
    graph.remove_node(2);

    let neighbors: Vec<u32> = graph.neighbors(1).collect();
    assert_eq!(neighbors, []);

    let edges: Vec<(u32, u32, _)> = graph.all_edges().collect();
    assert_eq!(edges, []);
}

#[test]
fn remove_directed() {
    let mut g = GraphMap::<_, _, Directed>::with_capacity(0, 0);
    g.add_edge(1, 2, -1);
    println!("{g:?}");

    assert_eq!(g.edge_weight(1, 2), Some(&-1));
    assert_eq!(g.edge_weight(2, 1), None);
    assert_eq!(g.neighbors(1).count(), 1);

    let noexist = g.remove_edge(2, 3);
    assert_eq!(noexist, None);

    let exist = g.remove_edge(2, 1);
    assert_eq!(exist, None);
    let exist = g.remove_edge(1, 2);
    assert_eq!(exist, Some(-1));
    println!("{g:?}");
    assert_eq!(g.edge_count(), 0);
    assert_eq!(g.edge_weight(1, 2), None);
    assert_eq!(g.edge_weight(2, 1), None);
    assert_eq!(g.neighbors(1).count(), 0);
}

#[test]
fn dfs() {
    let mut gr = UnGraphMap::<_, _>::default();
    let h = gr.add_node("H");
    let i = gr.add_node("I");
    let j = gr.add_node("J");
    let k = gr.add_node("K");
    // Z is disconnected.
    let z = gr.add_node("Z");
    gr.add_edge(h, i, 1.);
    gr.add_edge(h, j, 3.);
    gr.add_edge(i, j, 1.);
    gr.add_edge(i, k, 2.);

    println!("{gr:?}");

    {
        let mut cnt = 0;
        let mut dfs = Dfs::new(&gr, h);
        while dfs.next(&gr).is_some() {
            cnt += 1;
        }
        assert_eq!(cnt, 4);
    }
    {
        let mut cnt = 0;
        let mut dfs = Dfs::new(&gr, z);
        while dfs.next(&gr).is_some() {
            cnt += 1;
        }
        assert_eq!(cnt, 1);
    }

    assert_eq!(Dfs::new(&gr, h).iter(&gr).count(), 4);
    assert_eq!(Dfs::new(&gr, i).iter(&gr).count(), 4);
    assert_eq!(Dfs::new(&gr, z).iter(&gr).count(), 1);
}

#[test]
fn edge_iterator() {
    let mut gr = UnGraphMap::<_, _>::new();
    let h = gr.add_node("H");
    let i = gr.add_node("I");
    let j = gr.add_node("J");
    let k = gr.add_node("K");
    gr.add_edge(h, i, 1);
    gr.add_edge(h, j, 2);
    gr.add_edge(i, j, 3);
    gr.add_edge(i, k, 4);

    let real_edges: HashSet<_> = gr.all_edges().map(|(a, b, &w)| (a, b, w)).collect();
    let expected_edges: HashSet<_> =
        vec![("H", "I", 1), ("H", "J", 2), ("I", "J", 3), ("I", "K", 4)]
            .into_iter()
            .collect();

    assert_eq!(real_edges, expected_edges);
}

#[test]
fn from_edges() {
    let gr =
        GraphMap::<_, _, Undirected>::from_edges(&[("a", "b", 1), ("a", "c", 2), ("c", "d", 3)]);
    assert_eq!(gr.node_count(), 4);
    assert_eq!(gr.edge_count(), 3);
    assert_eq!(gr[("a", "c")], 2);

    let gr = GraphMap::<_, (), Undirected>::from_edges(&[
        (0, 1),
        (0, 2),
        (0, 3),
        (1, 2),
        (1, 3),
        (2, 3),
    ]);
    assert_eq!(gr.node_count(), 4);
    assert_eq!(gr.edge_count(), 6);
    assert_eq!(gr.neighbors(0).count(), 3);
    assert_eq!(gr.neighbors(1).count(), 3);
    assert_eq!(gr.neighbors(2).count(), 3);
    assert_eq!(gr.neighbors(3).count(), 3);

    println!("{:?}", Dot::with_config(&gr, &[Config::EdgeNoLabel]));
}

#[test]
fn graphmap_directed() {
    //let root = TypedArena::<Node<_>>::new();
    let mut gr = DiGraphMap::<_, ()>::with_capacity(0, 0);
    //let node = |&: name: &'static str| Ptr(root.alloc(Node(name.to_string())));
    let a = gr.add_node("A");
    let b = gr.add_node("B");
    let c = gr.add_node("C");
    let d = gr.add_node("D");
    let e = gr.add_node("E");
    let edges = [(a, b), (a, c), (a, d), (b, c), (c, d), (d, e), (b, b)];
    gr.extend(&edges);

    // Add reverse edges -- ok!
    assert!(gr.add_edge(e, d, ()).is_none());
    // duplicate edge - no
    assert!(gr.add_edge(a, b, ()).is_some());

    // duplicate self loop - no
    assert!(gr.add_edge(b, b, ()).is_some());
    println!("{gr:#?}");
}

fn assert_sccs_eq<N>(mut res: Vec<Vec<N>>, mut answer: Vec<Vec<N>>)
where
    N: Ord + fmt::Debug,
{
    // normalize the result and compare with the answer.
    for scc in &mut res {
        scc.sort();
    }
    res.sort();
    for scc in &mut answer {
        scc.sort();
    }
    answer.sort();
    assert_eq!(res, answer);
}

#[test]
fn scc() {
    let gr: GraphMap<_, u32, Directed> = GraphMap::from_edges(&[
        (6, 0, 0),
        (0, 3, 1),
        (3, 6, 2),
        (8, 6, 3),
        (8, 2, 4),
        (2, 5, 5),
        (5, 8, 6),
        (7, 5, 7),
        (1, 7, 8),
        (7, 4, 9),
        (4, 1, 10),
    ]);

    assert_sccs_eq(
        petgraph::algo::kosaraju_scc(&gr),
        vec![vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8]],
    );
}

#[test]
fn test_into_graph() {
    let gr: GraphMap<_, u32, Directed> = GraphMap::from_edges(&[
        (6, 0, 0),
        (0, 3, 1),
        (3, 6, 2),
        (8, 6, 3),
        (8, 2, 4),
        (2, 5, 5),
        (5, 8, 6),
        (7, 5, 7),
        (1, 7, 8),
        (7, 4, 9),
        (4, 1, 10),
    ]);

    let graph: Graph<_, _, _> = gr.clone().into_graph();
    println!("{}", Dot::new(&gr));
    println!("{}", Dot::new(&graph));

    // node weigths in `graph` are node identifiers in `gr`.
    for edge in graph.edge_references() {
        let a = edge.source();
        let b = edge.target();
        let aw = graph[a];
        let bw = graph[b];
        assert_eq!(&gr[(aw, bw)], edge.weight());
    }
}

#[test]
fn test_from_graph() {
    let mut gr: Graph<u32, u32, Directed> = Graph::new();
    let node_a = gr.add_node(12);
    let node_b = gr.add_node(13);
    let node_c = gr.add_node(14);
    gr.add_edge(node_a, node_b, 1000);
    gr.add_edge(node_b, node_c, 999);
    gr.add_edge(node_c, node_a, 1111);
    gr.add_node(42);
    let gr = gr;

    let graph: GraphMap<u32, u32, Directed> = GraphMap::from_graph(gr.clone());
    println!("{}", Dot::new(&gr));
    println!("{}", Dot::new(&graph));

    assert!(petgraph::algo::is_isomorphic(&gr, &graph));
    assert_eq!(graph[(12, 13)], 1000);
}

#[test]
fn test_all_edges_mut() {
    // graph with edge weights equal to in+out
    let mut graph: GraphMap<_, u32, Directed> =
        GraphMap::from_edges(&[(0, 1, 1), (1, 2, 3), (2, 0, 2)]);

    // change it so edge weight is equal to 2 * (in+out)
    for (start, end, weight) in graph.all_edges_mut() {
        *weight = (start + end) * 2;
    }

    // test it
    for (start, end, weight) in graph.all_edges() {
        assert_eq!((start + end) * 2, *weight);
    }
}

#[test]
fn neighbors_incoming_includes_self_loops() {
    let mut graph = DiGraphMap::<_, _>::new();

    graph.add_node(());
    graph.add_edge((), (), ());

    let mut neighbors = graph.neighbors_directed((), Incoming);
    assert_eq!(neighbors.next(), Some(()));
    assert_eq!(neighbors.next(), None);
}

#[test]
fn undirected_neighbors_includes_self_loops() {
    let mut graph = UnGraphMap::<_, _>::new();

    graph.add_node(());
    graph.add_edge((), (), ());

    let mut neighbors = graph.neighbors(());
    assert_eq!(neighbors.next(), Some(()));
    assert_eq!(neighbors.next(), None);
}

#[test]
fn self_loops_can_be_removed() {
    let mut graph = DiGraphMap::<_, _>::new();

    graph.add_node(());
    graph.add_edge((), (), ());

    graph.remove_edge((), ());

    assert_eq!(graph.neighbors_directed((), Outgoing).next(), None);
    assert_eq!(graph.neighbors_directed((), Incoming).next(), None);
}

#[test]
#[cfg(feature = "rayon")]
fn test_parallel_iterator() {
    use rayon::prelude::*;
    let mut gr: DiGraphMap<u32, u32> = DiGraphMap::new();

    for i in 0..1000 {
        gr.add_node(i);
    }

    let serial_sum: u32 = gr.nodes().sum();
    let parallel_sum: u32 = gr.par_nodes().sum();
    assert_eq!(serial_sum, parallel_sum);

    gr.par_nodes()
        .enumerate()
        .for_each(|(i, n)| assert_eq!(i as u32, n));

    for i in 0..1000 {
        gr.add_edge(i / 2, i, i + i / 2);
    }

    let serial_sum: u32 = gr.all_edges().map(|(.., &e)| e).sum();
    let parallel_sum: u32 = gr.par_all_edges().map(|(.., &e)| e).sum();
    assert_eq!(serial_sum, parallel_sum);

    gr.par_all_edges_mut().for_each(|(n1, n2, e)| *e -= n1 + n2);
    gr.all_edges().for_each(|(.., &e)| assert_eq!(e, 0));
}

#[test]
fn test_alternative_hasher() {
    let mut gr: GraphMap<&str, u32, Directed, fxhash::FxBuildHasher> = GraphMap::new();
    gr.add_node("abc");
    gr.add_node("def");
    gr.add_node("ghi");

    gr.add_edge("abc", "def", 1);

    assert!(gr.contains_edge("abc", "def"));
    assert!(!gr.contains_edge("abc", "ghi"));
}
