package test;

import java.util.Comparator;
import java.util.List;
import org.testng.Assert;
import org.testng.TestNGException;
import org.testng.annotations.Test;
import org.testng.internal.Graph;
import org.testng.internal.Graph.Node;
import org.testng.internal.Tarjan;

public class GraphTest {

  private static final Comparator<Node<String>> COMPARATOR = Comparator.comparing(Node::getObject);

  @Test
  public void sort() {
    Graph<String> g = new Graph<>(COMPARATOR);
    g.addNode("3");
    g.addNode("1");
    g.addNode("2.2");
    g.addNode("independent");
    g.addNode("2.1");
    g.addNode("2");

    g.addPredecessor("3", "2");
    g.addPredecessor("3", "2.1");
    g.addPredecessor("3", "2.2");
    g.addPredecessor("2", "1");
    g.addPredecessor("2.1", "1");
    g.addPredecessor("2.2", "1");

    g.topologicalSort();
    List<String> l = g.getStrictlySortedNodes();
    int i = 0;
    Assert.assertEquals("1", l.get(i));
    i++;
    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
    i++;
    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
    i++;
    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
    i++;
    Assert.assertEquals("3", l.get(i));

    Assert.assertEquals(1, g.getIndependentNodes().size());
  }

  @Test(expectedExceptions = org.testng.TestNGException.class)
  public void cycleShouldFail() {
    Graph<String> g = createCyclicGraph();
    g.topologicalSort();
  }

  @Test
  public void cycleShouldBeCorrect() {
    Graph<String> g = null;
    try {
      g = createCyclicGraph();
      g.topologicalSort();
    } catch (TestNGException ex) {
      Tarjan<String> t = new Tarjan<>(g, "1");
      Assert.assertEquals(t.getCycle().size(), 3);
    }
  }

  private Graph<String> createCyclicGraph() {
    Graph<String> g = new Graph<>(COMPARATOR);
    g.addNode("3");
    g.addNode("2");
    g.addNode("1");

    g.addPredecessor("3", "2");
    g.addPredecessor("2", "1");
    g.addPredecessor("1", "3");

    return g;
  }

  @Test
  public void findPredecessors() {
    Graph<String> g = new Graph<>(COMPARATOR);
    g.addNode("3");
    g.addNode("1");
    g.addNode("2.2");
    g.addNode("independent");
    g.addNode("2.1");
    g.addNode("2");

    // 1 ->  2.1, 2.2, 2.3 --> 3
    g.addPredecessor("3", "2");
    g.addPredecessor("3", "2.1");
    g.addPredecessor("3", "2.2");
    g.addPredecessor("2", "1");
    g.addPredecessor("2.1", "1");
    g.addPredecessor("2.2", "1");

    // Invoke sort to make sure there is no side effect
    g.topologicalSort();

    //
    // Test findPredecessors
    //
    {
      List<String> predecessors = g.findPredecessors("2");
      Assert.assertEquals(1, predecessors.size());
      Assert.assertEquals("1", predecessors.get(0));
    }

    {
      List<String> predecessors = g.findPredecessors("3");

      Assert.assertEquals(4, predecessors.size());
      Assert.assertEquals("1", predecessors.get(0));
      Assert.assertTrue(
          predecessors.get(1).equals("2.1")
              || predecessors.get(1).equals("2.2")
              || predecessors.get(1).equals("2"));
      Assert.assertTrue(
          predecessors.get(2).equals("2.1")
              || predecessors.get(2).equals("2.2")
              || predecessors.get(2).equals("2"));
      Assert.assertTrue(
          predecessors.get(3).equals("2.1")
              || predecessors.get(3).equals("2.2")
              || predecessors.get(3).equals("2"));
    }
  }

  // Using an earlier implementation of Graph.findPrecessors, finding
  // predecessors in certain kinds of graphs where many nodes have the
  // same predecessors could be very slow, since the old implementation
  // would explore the same nodes repeatedly.  This situation could
  // happen when test methods are organized in several groups, with
  // dependsOnGroups annotations so that each method in one group depends
  // on all of the methods in another group.  If there were several
  // such groups depending on each other in a chain, the combinatorics
  // of the old method became excessive.  This test builds a 72-node graph that
  // emulates this situation, then call Graph.findPredecessors.  The old
  // implementation run this in 70+ seconds on my computer, the new implementation
  // takes a few milliseconds.  In practice, with larger graphs, the former
  // slowness could get very extreme, taking hours or more to complete
  // in some real user situations.
  //
  @Test(timeOut = 5000) // If this takes more than 5 seconds we've definitely regressed.
  public void findPredecessorsTiming() {
    Graph<String> g = new Graph<>(COMPARATOR);

    final String rootNode = "myroot";
    final String independentNode = "independent";
    g.addNode(rootNode);
    g.addNode(independentNode);

    final int maxDepth = 7;
    final int nodesPerDepth = 10; // must be < 100
    //
    // Add maxDepth groups of new nodes, where each group contains nodesPerDepth
    // nodes, and each node in a group a depth N has each node in the group
    // at depth (N-1) as a predecessor.
    //
    for (int depth = 1; depth <= maxDepth; depth++) {
      for (int i = 0; i < nodesPerDepth; i++) {
        String newNode = String.valueOf(i + (100 * depth));
        g.addNode(newNode);
        if (depth == 1) {
          continue;
        }
        for (int j = 0; j < nodesPerDepth; j++) {
          String prevNode = String.valueOf(j + (100 * (depth - 1)));
          g.addPredecessor(newNode, prevNode);
        }
      }
    }

    // Finally, make all of the nodes in the group at depth maxDepth
    // be predecessors of rootNode.
    //
    for (int i = 0; i < nodesPerDepth; i++) {
      String node = String.valueOf(i + (100 * maxDepth));
      g.addPredecessor(rootNode, node);
    }

    // Now we're done building the graph, which has (maxDepth * nodesPerDepth) + 2
    // nodes.  rootNode has all of the other nodes except independentNode
    // as predecessors.

    //
    // Test findPredecessors
    //
    {
      List<String> predecessors = g.findPredecessors(rootNode);
      Assert.assertEquals(predecessors.size(), (maxDepth * nodesPerDepth));
    }
  }
}
