/**
   * Constructs the online graph for the analysis. Additionally an offline graph for HCD is
   * constructed to speed up the computation of the dynamic transitive closure with it.
   *
   * @param bConstr List of all {@link BaseConstraint}s that should be considered.<br>
   *     A {@link BaseConstraint} leads to an entry in a nodes points-to set.
   * @param sConstr List of all {@link SimpleConstraint}s that should be considered.<br>
   *     A {@link SimpleConstraint} represents an edge in the graph.
   * @param cConstr List of all {@link ComplexConstraint}s that should be considered.<br>
   *     {@link ComplexConstraint}s are stored in nodes, so they can be accessed faster when
   *     computing the dynamic transitive closure.
   * @param g The resulting graph. Should be empty.
   */
  private static void buildGraph(
      Set<BaseConstraint> bConstr,
      Set<SimpleConstraint> sConstr,
      Set<ComplexConstraint> cConstr,
      DirectedGraph g) {

    // HCD offline - build offline graph
    List<List<String>> sccs = buildOfflineGraphAndFindSCCs(sConstr, cConstr);

    // build online graph
    for (BaseConstraint bc : bConstr) {

      DirectedGraph.Node n = g.getNode(bc.getSuperVar());
      n.addPointerTarget(bc.getSubVar());
    }

    for (SimpleConstraint sc : sConstr) {

      DirectedGraph.Node src = g.getNode(sc.getSubVar());
      DirectedGraph.Node dest = g.getNode(sc.getSuperVar());
      g.addEdge(src, dest);
    }

    for (ComplexConstraint cc : cConstr) {

      DirectedGraph.Node n;

      if (cc.isSubDerefed()) {

        n = g.getNode(cc.getSubVar());
        n.complexConstrMeSub.add(cc.getSuperVar());

      } else {

        n = g.getNode(cc.getSuperVar());
        n.complexConstrMeSuper.add(cc.getSubVar());
      }
    }

    // for HCD
    mergeOrMarkSCCs(g, sccs); // ... in online graph
  }
  /**
   * Merges all non-ref nodes in an SCC. For every ref-node the last remaining non-ref Node is
   * stored.
   *
   * @param g The (online) points-to graph for the analysis.
   * @param sccs List of all found SCCs in the offline version of the graph.
   */
  private static void mergeOrMarkSCCs(DirectedGraph g, List<List<String>> sccs) {

    for (List<String> scc : sccs) {

      LinkedList<DirectedGraph.Node> refNodes = new LinkedList<>();
      LinkedList<DirectedGraph.Node> normNodes = new LinkedList<>();

      for (String n : scc) {

        // translate to new node
        if (n.charAt(0) == '*') {
          refNodes.add(g.getNode(n.substring(1)));
        } else {
          normNodes.add(g.getNode(n));
        }
      }

      DirectedGraph.Node merged = g.mergeNodes(normNodes.poll(), normNodes);

      for (DirectedGraph.Node n : refNodes) {
        n.mergePts = merged;
      }
    }
  }
  /**
   * Recursive part of tarjans algorithm to find strongly connected components. Algorithm is e.g.
   * described in <a
   * href="http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm">wikipedia</a>
   *
   * @param maxdfs The current value for maxdfs.
   * @param v The current Node to process.
   * @param workset Set of all unreached Nodes.
   * @param stack Temporary datastructure for algorithm.
   * @param nodeStrMap Because the sccs are returned as a list of variables (i.e. Strings), the
   *     mapping from Node to String must be given.
   * @param sccs All found strongly connected components are added to this list.
   * @return an updated value for maxdfs.
   */
  private static int tarjan(
      int maxdfs,
      DirectedGraph.Node v,
      Set<DirectedGraph.Node> workset,
      LinkedList<DirectedGraph.Node> stack,
      Map<DirectedGraph.Node, String> nodeStrMap,
      List<List<String>> sccs) {

    v.dfs = maxdfs;
    v.lowlink = maxdfs;
    maxdfs++;
    stack.push(v);
    workset.remove(v);

    for (DirectedGraph.Node succ : v.getSuccessors()) {
      if (workset.contains(succ)) {
        maxdfs = tarjan(maxdfs, succ, workset, stack, nodeStrMap, sccs);
        v.lowlink = Math.min(v.lowlink, succ.lowlink);
      } else if (succ.dfs > 0) { // <==> stack.contains(succ)
        v.lowlink = Math.min(v.lowlink, succ.dfs);
      }
    }

    if (v.lowlink == v.dfs) {

      DirectedGraph.Node succ;
      LinkedList<String> scc = new LinkedList<>();

      do {
        succ = stack.pop();
        succ.dfs = -succ.dfs;
        scc.add(nodeStrMap.get(succ));
      } while (!succ.equals(v));

      if (scc.size() > 1) {
        sccs.add(scc);
      }
    }

    return maxdfs;
  }
  /**
   * Computes the dynamic transitive closure of the given constraint system and writes the resulting
   * points-to sets to the {@link Map} <code>ptSets</code>.
   *
   * @param bConstr {@link Set} of {@link BaseConstraint}s in the constraint system.
   * @param sConstr {@link Set} of {@link SimpleConstraint}s in the constraint system.
   * @param cConstr {@link Set} of {@link ComplexConstraint}s in the constraint system.
   * @param ptSets Writes all found points-to relations to this {@link Map}.<br>
   *     <i>Note:</i> The map is cleared, before the results are written.
   */
  private static void computeDynTransitiveClosure(
      Set<BaseConstraint> bConstr,
      Set<SimpleConstraint> sConstr,
      Set<ComplexConstraint> cConstr,
      Map<String, String[]> ptSets) {

    // build initial graph
    DirectedGraph g = new DirectedGraph();

    buildGraph(bConstr, sConstr, cConstr, g);

    HashSet<DirectedGraph.Node> workset = new HashSet<>();

    // add all nodes in graph to the initial workset
    for (Map.Entry<String, DirectedGraph.Node> entry : g.getNameMappings()) {
      workset.add(entry.getValue());
    }

    HashSet<DirectedGraph.Edge> tested = new HashSet<>();

    // dynamic transitive closure
    while (!workset.isEmpty()) {

      DirectedGraph.Node n = workset.iterator().next();
      workset.remove(n);
      if (!n.isValid()) {
        // node is invalid, if it was merged into another one
        continue;
      }

      // two lines for HCD
      if (n.mergePts != null) {
        g.mergeNodes(n.mergePts, n.getPointsToNodesSet());
      }

      for (DirectedGraph.Node v : n.getPointsToNodesSet()) {

        for (String aStr : n.complexConstrMeSub) {
          DirectedGraph.Node a = g.getNode(aStr);

          if (!v.isSuccessor(a)) {
            g.addEdge(v, a);
            workset.add(v);
          }
        }

        for (String bStr : n.complexConstrMeSuper) {
          DirectedGraph.Node b = g.getNode(bStr);

          if (!b.isSuccessor(v)) {
            g.addEdge(b, v);
            workset.add(b);
          }
        }
      } // for (String vStr : n.pointsToSet)

      for (DirectedGraph.Node z : n.getSuccessors()) {

        // LCD code
        DirectedGraph.Edge edge = new DirectedGraph.Edge(n, z);
        if (z.getPointsToSet().equals(n.getPointsToSet()) && !tested.contains(edge)) {
          tested.add(edge);
          DirectedGraph.Node merged = g.detectAndCollapseCycleContainingEdge(edge);

          if (merged != null) {
            workset.add(merged);
            break;
          }

        } else /* END LCD code */ if (n.propagatePointerTargetsTo(z)) {
          workset.add(z);
        }
      }
    } // while (!workset.isEmpty())

    // clear result map
    ptSets.clear();

    // write results to map
    for (Map.Entry<String, DirectedGraph.Node> e : g.getNameMappings()) {

      Collection<String> ptSetNode = e.getValue().getPointsToSet();
      ptSets.put(e.getKey(), ptSetNode.toArray(new String[ptSetNode.size()]));
    }
  }