package test;

import org.testng.Assert;
import org.testng.TestNGException;
import org.testng.annotations.Test;
import org.testng.internal.Graph;
import org.testng.internal.Tarjan;

import java.util.List;


/**
 * This class
 * 
 * @author cbeust
 */
public class GraphTest {

  @Test
  public void sort() {
    Graph<String> g = new Graph<String>();
    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 "1".equals(l.get(i));
    i++;
    assert "2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i));
    i++;
    assert "2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i));
    i++;
    assert "2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i));
    i++;
    assert "3".equals(l.get(i));
    
    assert 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<String>(g, "1");
      Assert.assertEquals(t.getCycle().size(), 3);
    }
    
  }
  
  private Graph<String> createCyclicGraph() {
    Graph<String> g = new Graph<String>();
    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<String>();
    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 predecessors.size() == 1;
      assert predecessors.get(0).equals("1");
    }
    
    {
      List<String> predecessors = g.findPredecessors("3");

      assert predecessors.size() == 4;
      assert predecessors.get(0).equals("1");
      assert predecessors.get(1).equals("2.1") 
        || predecessors.get(1).equals("2.2") 
        || predecessors.get(1).equals("2");
      assert predecessors.get(2).equals("2.1") 
        || predecessors.get(2).equals("2.2") 
        || predecessors.get(2).equals("2");
      assert 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<String>();

    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 predecessors.size() == (maxDepth * nodesPerDepth);
    }
  }
}
