extern crate test;

use super::*;
use crate::graph::tests::TestGraph;

#[derive(Copy, Clone, Debug)]
struct MaxReached(usize);
type UsizeSccs = Sccs<usize, usize, ()>;
type MaxReachedSccs = Sccs<usize, usize, MaxReached>;

impl Annotation for MaxReached {
    fn merge_scc(self, other: Self) -> Self {
        Self(std::cmp::max(other.0, self.0))
    }

    fn merge_reached(self, other: Self) -> Self {
        self.merge_scc(other)
    }
}

impl PartialEq<usize> for MaxReached {
    fn eq(&self, other: &usize) -> bool {
        &self.0 == other
    }
}

impl MaxReached {
    fn from_usize(nr: usize) -> Self {
        Self(nr)
    }
}

#[derive(Copy, Clone, Debug)]
struct MinMaxIn {
    min: usize,
    max: usize,
}

impl Annotation for MinMaxIn {
    fn merge_scc(self, other: Self) -> Self {
        Self { min: std::cmp::min(self.min, other.min), max: std::cmp::max(self.max, other.max) }
    }

    fn merge_reached(self, _other: Self) -> Self {
        self
    }
}

#[test]
fn diamond() {
    let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), 4);
    assert_eq!(sccs.num_sccs(), 4);
}

#[test]
fn test_big_scc() {
    // The order in which things will be visited is important to this
    // test.
    //
    // We will visit:
    //
    // 0 -> 1 -> 2 -> 0
    //
    // and at this point detect a cycle. 2 will return back to 1 which
    // will visit 3. 3 will visit 2 before the cycle is complete, and
    // hence it too will return a cycle.

    /*
    +-> 0
    |   |
    |   v
    |   1 -> 3
    |   |    |
    |   v    |
    +-- 2 <--+
         */
    let graph = TestGraph::new(0, &[(0, 1), (1, 2), (1, 3), (2, 0), (3, 2)]);
    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), 1);
}

#[test]
fn test_three_sccs() {
    /*
        0
        |
        v
    +-> 1    3
    |   |    |
    |   v    |
    +-- 2 <--+
         */
    let graph = TestGraph::new(0, &[(0, 1), (1, 2), (2, 1), (3, 2)]);
    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), 3);
    assert_eq!(sccs.scc(0), 1);
    assert_eq!(sccs.scc(1), 0);
    assert_eq!(sccs.scc(2), 0);
    assert_eq!(sccs.scc(3), 2);
    assert_eq!(sccs.successors(0), &[] as &[usize]);
    assert_eq!(sccs.successors(1), &[0]);
    assert_eq!(sccs.successors(2), &[0]);
}

#[test]
fn test_find_state_2() {
    // The order in which things will be visited is important to this
    // test. It tests part of the `find_state` behavior. Here is the
    // graph:
    //
    //
    //       /----+
    //     0 <--+ |
    //     |    | |
    //     v    | |
    // +-> 1 -> 3 4
    // |   |      |
    // |   v      |
    // +-- 2 <----+

    let graph = TestGraph::new(0, &[(0, 1), (0, 4), (1, 2), (1, 3), (2, 1), (3, 0), (4, 2)]);

    // For this graph, we will start in our DFS by visiting:
    //
    // 0 -> 1 -> 2 -> 1
    //
    // and at this point detect a cycle. The state of 2 will thus be
    // `InCycleWith { 1 }`. We will then visit the 1 -> 3 edge, which
    // will attempt to visit 0 as well, thus going to the state
    // `InCycleWith { 0 }`. Finally, node 1 will complete; the lowest
    // depth of any successor was 3 which had depth 0, and thus it
    // will be in the state `InCycleWith { 3 }`.
    //
    // When we finally traverse the `0 -> 4` edge and then visit node 2,
    // the states of the nodes are:
    //
    // 0 BeingVisited { 0 }
    // 1 InCycleWith { 3 }
    // 2 InCycleWith { 1 }
    // 3 InCycleWith { 0 }
    //
    // and hence 4 will traverse the links, finding an ultimate depth of 0.
    // If will also collapse the states to the following:
    //
    // 0 BeingVisited { 0 }
    // 1 InCycleWith { 3 }
    // 2 InCycleWith { 1 }
    // 3 InCycleWith { 0 }

    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), 1);
    assert_eq!(sccs.scc(0), 0);
    assert_eq!(sccs.scc(1), 0);
    assert_eq!(sccs.scc(2), 0);
    assert_eq!(sccs.scc(3), 0);
    assert_eq!(sccs.scc(4), 0);
    assert_eq!(sccs.successors(0), &[] as &[usize]);
}

#[test]
fn test_find_state_3() {
    /*
          /----+
        0 <--+ |
        |    | |
        v    | |
    +-> 1 -> 3 4 5
    |   |      | |
    |   v      | |
    +-- 2 <----+-+
         */
    let graph =
        TestGraph::new(0, &[(0, 1), (0, 4), (1, 2), (1, 3), (2, 1), (3, 0), (4, 2), (5, 2)]);
    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), 2);
    assert_eq!(sccs.scc(0), 0);
    assert_eq!(sccs.scc(1), 0);
    assert_eq!(sccs.scc(2), 0);
    assert_eq!(sccs.scc(3), 0);
    assert_eq!(sccs.scc(4), 0);
    assert_eq!(sccs.scc(5), 1);
    assert_eq!(sccs.successors(0), &[] as &[usize]);
    assert_eq!(sccs.successors(1), &[0]);
}

#[test]
fn test_deep_linear() {
    /*
    0
    |
    v
    1
    |
    v
    2
    |
    v
    …
     */
    #[cfg(not(miri))]
    const NR_NODES: usize = 1 << 14;
    #[cfg(miri)]
    const NR_NODES: usize = 1 << 3;
    let mut nodes = vec![];
    for i in 1..NR_NODES {
        nodes.push((i - 1, i));
    }
    let graph = TestGraph::new(0, nodes.as_slice());
    let sccs: UsizeSccs = Sccs::new(&graph);
    assert_eq!(sccs.num_sccs(), NR_NODES);
    assert_eq!(sccs.scc(0), NR_NODES - 1);
    assert_eq!(sccs.scc(NR_NODES - 1), 0);
}

#[bench]
fn bench_sccc(b: &mut test::Bencher) {
    // Like `test_three_sccs` but each state is replaced by a group of
    // three or four to have some amount of test data.
    /*
       0-3
        |
        v
    +->4-6 11-14
    |   |    |
    |   v    |
    +--7-10<-+
         */
    fn make_3_clique(slice: &mut [(usize, usize)], base: usize) {
        slice[0] = (base + 0, base + 1);
        slice[1] = (base + 1, base + 2);
        slice[2] = (base + 2, base + 0);
    }
    // Not actually a clique but strongly connected.
    fn make_4_clique(slice: &mut [(usize, usize)], base: usize) {
        slice[0] = (base + 0, base + 1);
        slice[1] = (base + 1, base + 2);
        slice[2] = (base + 2, base + 3);
        slice[3] = (base + 3, base + 0);
        slice[4] = (base + 1, base + 3);
        slice[5] = (base + 2, base + 1);
    }

    let mut graph = [(0, 0); 6 + 3 + 6 + 3 + 4];
    make_4_clique(&mut graph[0..6], 0);
    make_3_clique(&mut graph[6..9], 4);
    make_4_clique(&mut graph[9..15], 7);
    make_3_clique(&mut graph[15..18], 11);
    graph[18] = (0, 4);
    graph[19] = (5, 7);
    graph[20] = (11, 10);
    graph[21] = (7, 4);
    let graph = TestGraph::new(0, &graph[..]);
    b.iter(|| {
        let sccs: UsizeSccs = Sccs::new(&graph);
        assert_eq!(sccs.num_sccs(), 3);
    });
}

#[test]
fn test_max_self_loop() {
    let graph = TestGraph::new(0, &[(0, 0)]);
    let sccs: MaxReachedSccs =
        Sccs::new_with_annotation(&graph, |n| if n == 0 { MaxReached(17) } else { MaxReached(0) });
    assert_eq!(sccs.annotation(0), 17);
}

#[test]
fn test_max_branch() {
    let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 4)]);
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize);
    assert_eq!(sccs.annotation(sccs.scc(0)), 4);
    assert_eq!(sccs.annotation(sccs.scc(1)), 3);
    assert_eq!(sccs.annotation(sccs.scc(2)), 4);
}
#[test]
fn test_single_cycle_max() {
    let graph = TestGraph::new(0, &[(0, 2), (2, 3), (2, 4), (4, 1), (1, 2)]);
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize);
    assert_eq!(sccs.annotation(sccs.scc(2)), 4);
    assert_eq!(sccs.annotation(sccs.scc(0)), 4);
}

#[test]
fn test_simple_cycle_max() {
    let graph = TestGraph::new(0, &[(0, 1), (1, 2), (2, 0)]);
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize);
    assert_eq!(sccs.num_sccs(), 1);
}

#[test]
fn test_double_cycle_max() {
    let graph =
        TestGraph::new(0, &[(0, 1), (1, 2), (1, 4), (2, 3), (2, 4), (3, 5), (4, 1), (5, 4)]);
    let sccs: MaxReachedSccs =
        Sccs::new_with_annotation(&graph, |n| if n == 5 { MaxReached(2) } else { MaxReached(1) });

    assert_eq!(sccs.annotation(sccs.scc(0)).0, 2);
}

#[test]
fn test_bug_minimised() {
    let graph = TestGraph::new(0, &[(0, 3), (0, 1), (3, 2), (2, 3), (1, 4), (4, 5), (5, 4)]);
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |n| match n {
        3 => MaxReached(1),
        _ => MaxReached(0),
    });
    assert_eq!(sccs.annotation(sccs.scc(2)), 1);
    assert_eq!(sccs.annotation(sccs.scc(1)), 0);
    assert_eq!(sccs.annotation(sccs.scc(4)), 0);
}

#[test]
fn test_bug_max_leak_minimised() {
    let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (3, 0), (3, 4), (4, 3)]);
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w {
        4 => MaxReached(1),
        _ => MaxReached(0),
    });

    assert_eq!(sccs.annotation(sccs.scc(2)), 0);
    assert_eq!(sccs.annotation(sccs.scc(3)), 1);
    assert_eq!(sccs.annotation(sccs.scc(0)), 1);
}

#[test]
fn test_bug_max_leak() {
    let graph = TestGraph::new(
        8,
        &[
            (0, 0),
            (0, 18),
            (0, 19),
            (0, 1),
            (0, 2),
            (0, 7),
            (0, 8),
            (0, 23),
            (18, 0),
            (18, 12),
            (19, 0),
            (19, 25),
            (12, 18),
            (12, 3),
            (12, 5),
            (3, 12),
            (3, 21),
            (3, 22),
            (5, 13),
            (21, 3),
            (22, 3),
            (13, 5),
            (13, 4),
            (4, 13),
            (4, 0),
            (2, 11),
            (7, 6),
            (6, 20),
            (20, 6),
            (8, 17),
            (17, 9),
            (9, 16),
            (16, 26),
            (26, 15),
            (15, 10),
            (10, 14),
            (14, 27),
            (23, 24),
        ],
    );
    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w {
        22 => MaxReached(1),
        24 => MaxReached(2),
        27 => MaxReached(2),
        _ => MaxReached(0),
    });

    assert_eq!(sccs.annotation(sccs.scc(2)), 0);
    assert_eq!(sccs.annotation(sccs.scc(7)), 0);
    assert_eq!(sccs.annotation(sccs.scc(8)), 2);
    assert_eq!(sccs.annotation(sccs.scc(23)), 2);
    assert_eq!(sccs.annotation(sccs.scc(3)), 2);
    assert_eq!(sccs.annotation(sccs.scc(0)), 2);
}

#[test]
fn test_bug_max_zero_stick_shape() {
    let graph = TestGraph::new(0, &[(0, 1), (1, 2), (2, 3), (3, 2), (3, 4)]);

    let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w {
        4 => MaxReached(1),
        _ => MaxReached(0),
    });

    assert_eq!(sccs.annotation(sccs.scc(0)), 1);
    assert_eq!(sccs.annotation(sccs.scc(1)), 1);
    assert_eq!(sccs.annotation(sccs.scc(2)), 1);
    assert_eq!(sccs.annotation(sccs.scc(3)), 1);
    assert_eq!(sccs.annotation(sccs.scc(4)), 1);
}

#[test]
fn test_min_max_in() {
    let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (3, 0), (3, 4), (4, 3), (3, 5)]);
    let sccs: Sccs<usize, usize, MinMaxIn> =
        Sccs::new_with_annotation(&graph, |w| MinMaxIn { min: w, max: w });

    assert_eq!(sccs.annotation(sccs.scc(2)).min, 2);
    assert_eq!(sccs.annotation(sccs.scc(2)).max, 2);
    assert_eq!(sccs.annotation(sccs.scc(0)).min, 0);
    assert_eq!(sccs.annotation(sccs.scc(0)).max, 4);
    assert_eq!(sccs.annotation(sccs.scc(3)).min, 0);
    assert_eq!(sccs.annotation(sccs.scc(3)).max, 4);
    assert_eq!(sccs.annotation(sccs.scc(5)).min, 5);
}
