/** Finds shortcuts, does not change the underlying graph. */
  void findShortcuts(ShortcutHandler sch) {
    long tmpDegreeCounter = 0;
    EdgeIterator incomingEdges = vehicleInExplorer.setBaseNode(sch.getNode());
    // collect outgoing nodes (goal-nodes) only once
    while (incomingEdges.next()) {
      int u_fromNode = incomingEdges.getAdjNode();
      // accept only uncontracted nodes
      if (g.getLevel(u_fromNode) != 0) continue;

      double v_u_weight = incomingEdges.getDistance();
      int skippedEdge1 = incomingEdges.getEdge();
      int incomingEdgeOrigCount = getOrigEdgeCount(skippedEdge1);
      // collect outgoing nodes (goal-nodes) only once
      EdgeIterator outgoingEdges = vehicleOutExplorer.setBaseNode(sch.getNode());
      // force fresh maps etc as this cannot be determined by from node alone (e.g. same from node
      // but different avoidNode)
      algo.clear();
      tmpDegreeCounter++;
      while (outgoingEdges.next()) {
        int w_toNode = outgoingEdges.getAdjNode();
        // add only uncontracted nodes
        if (g.getLevel(w_toNode) != 0 || u_fromNode == w_toNode) {
          continue;
        }

        // Limit weight as ferries or forbidden edges can increase local search too much.
        // If we decrease the correct weight we only explore less and introduce more shortcuts.
        // I.e. no change to accuracy is made.
        double existingDirectWeight = v_u_weight + outgoingEdges.getDistance();
        algo.setLimitWeight(existingDirectWeight)
            .setLimitVisitedNodes((int) meanDegree * 100)
            .setEdgeFilter(levelEdgeFilter.setAvoidNode(sch.getNode()));

        dijkstraSW.start();
        dijkstraCount++;
        int endNode = algo.findEndNode(u_fromNode, w_toNode);
        dijkstraSW.stop();

        // compare end node as the limit could force dijkstra to finish earlier
        if (endNode == w_toNode && algo.getWeight(endNode) <= existingDirectWeight)
          // FOUND witness path, so do not add shortcut
          continue;

        sch.foundShortcut(
            u_fromNode,
            w_toNode,
            existingDirectWeight,
            outgoingEdges,
            skippedEdge1,
            incomingEdgeOrigCount);
      }
    }
    if (sch instanceof AddShortcutHandler) {
      // sliding mean value when using "*2" => slower changes
      meanDegree = (meanDegree * 2 + tmpDegreeCounter) / 3;
      // meanDegree = (meanDegree + tmpDegreeCounter) / 2;
    }
  }
  /** Introduces the necessary shortcuts for endNode v in the graph. */
  int addShortcuts(int v) {
    shortcuts.clear();
    findShortcuts(addScHandler.setNode(v));
    int tmpNewShortcuts = 0;
    for (Shortcut sc : shortcuts.keySet()) {
      boolean updatedInGraph = false;
      // check if we need to update some existing shortcut in the graph
      EdgeSkipIterator iter = vehicleOutExplorer.setBaseNode(sc.from);
      while (iter.next()) {
        if (iter.isShortcut()
            && iter.getAdjNode() == sc.to
            && prepareEncoder.canBeOverwritten(iter.getFlags(), sc.flags)
            && iter.getDistance() > sc.weight) {
          iter.setFlags(sc.flags);
          iter.setSkippedEdges(sc.skippedEdge1, sc.skippedEdge2);
          iter.setDistance(sc.weight);
          setOrigEdgeCount(iter.getEdge(), sc.originalEdges);
          updatedInGraph = true;
          break;
        }
      }

      if (!updatedInGraph) {
        EdgeSkipIterState edgeState = g.shortcut(sc.from, sc.to);
        edgeState.setDistance(sc.weight).setFlags(sc.flags);
        edgeState.setSkippedEdges(sc.skippedEdge1, sc.skippedEdge2);
        setOrigEdgeCount(edgeState.getEdge(), sc.originalEdges);
        tmpNewShortcuts++;
      }
    }
    return tmpNewShortcuts;
  }
  /**
   * Calculates the priority of endNode v without changing the graph. Warning: the calculated
   * priority must NOT depend on priority(v) and therefor findShortcuts should also not depend on
   * the priority(v). Otherwise updating the priority before contracting in contractNodes() could
   * lead to a slowishor even endless loop.
   */
  int calculatePriority(int v) {
    // set of shortcuts that would be added if endNode v would be contracted next.
    findShortcuts(calcScHandler.setNode(v));

    //        System.out.println(v + "\t " + tmpShortcuts);
    // # huge influence: the bigger the less shortcuts gets created and the faster is the
    // preparation
    //
    // every endNode has an 'original edge' number associated. initially it is r=1
    // when a new shortcut is introduced then r of the associated edges is summed up:
    // r(u,w)=r(u,v)+r(v,w) now we can define
    // originalEdgesCount = σ(v) := sum_{ (u,w) ∈ shortcuts(v) } of r(u, w)
    int originalEdgesCount = calcScHandler.originalEdgesCount;
    //        for (Shortcut sc : tmpShortcuts) {
    //            originalEdgesCount += sc.originalEdges;
    //        }

    // # lowest influence on preparation speed or shortcut creation count
    // (but according to paper should speed up queries)
    //
    // number of already contracted neighbors of v
    int contractedNeighbors = 0;
    int degree = 0;
    EdgeSkipIterator iter = calcPrioAllExplorer.setBaseNode(v);
    while (iter.next()) {
      degree++;
      if (iter.isShortcut()) contractedNeighbors++;
    }

    // from shortcuts we can compute the edgeDifference
    // # low influence: with it the shortcut creation is slightly faster
    //
    // |shortcuts(v)| − |{(u, v) | v uncontracted}| − |{(v, w) | v uncontracted}|
    // meanDegree is used instead of outDegree+inDegree as if one endNode is in both directions
    // only one bucket memory is used. Additionally one shortcut could also stand for two
    // directions.
    int edgeDifference = calcScHandler.shortcuts - degree;

    // according to the paper do a simple linear combination of the properties to get the priority.
    // this is the current optimum for unterfranken:
    return 10 * edgeDifference + originalEdgesCount + contractedNeighbors;
  }
  void contractNodes() {
    meanDegree = g.getAllEdges().getMaxId() / g.getNodes();
    int level = 1;
    counter = 0;
    int initSize = sortedNodes.getSize();
    int logSize =
        (int) Math.round(Math.max(10, sortedNodes.getSize() / 100 * logMessagesPercentage));
    if (logMessagesPercentage == 0) logSize = Integer.MAX_VALUE;

    // preparation takes longer but queries are slightly faster with preparation
    // => enable it but call not so often
    boolean periodicUpdate = true;
    StopWatch periodSW = new StopWatch();
    int updateCounter = 0;
    int periodicUpdatesCount =
        Math.max(10, sortedNodes.getSize() / 100 * periodicUpdatesPercentage);
    if (periodicUpdatesPercentage == 0) periodicUpdate = false;

    // disable as preparation is slower and query time does not benefit
    int lastNodesLazyUpdates =
        lastNodesLazyUpdatePercentage == 0
            ? 0
            : sortedNodes.getSize() / 100 * lastNodesLazyUpdatePercentage;
    StopWatch lazySW = new StopWatch();

    // Recompute priority of uncontracted neighbors.
    // Without neighborupdates preparation is faster but we need them
    // to slightly improve query time. Also if not applied too often it decreases the shortcut
    // number.
    boolean neighborUpdate = true;
    if (neighborUpdatePercentage == 0) neighborUpdate = false;

    StopWatch neighborSW = new StopWatch();
    LevelGraphStorage lg = ((LevelGraphStorage) g);
    while (!sortedNodes.isEmpty()) {
      // periodically update priorities of ALL nodes
      if (periodicUpdate && counter > 0 && counter % periodicUpdatesCount == 0) {
        periodSW.start();
        sortedNodes.clear();
        int len = g.getNodes();
        for (int node = 0; node < len; node++) {
          if (g.getLevel(node) != 0) continue;

          int priority = oldPriorities[node] = calculatePriority(node);
          sortedNodes.insert(node, priority);
        }
        periodSW.stop();
        updateCounter++;
      }

      if (counter % logSize == 0) {
        // TODO necessary?
        System.gc();
        logger.info(
            Helper.nf(counter)
                + ", updates:"
                + updateCounter
                + ", nodes: "
                + Helper.nf(sortedNodes.getSize())
                + ", shortcuts:"
                + Helper.nf(newShortcuts)
                + ", dijkstras:"
                + Helper.nf(dijkstraCount)
                + ", t(dijk):"
                + (int) dijkstraSW.getSeconds()
                + ", t(period):"
                + (int) periodSW.getSeconds()
                + ", t(lazy):"
                + (int) lazySW.getSeconds()
                + ", t(neighbor):"
                + (int) neighborSW.getSeconds()
                + ", meanDegree:"
                + (long) meanDegree
                + ", algo:"
                + algo.getMemoryUsageAsString()
                + ", "
                + Helper.getMemInfo());
        dijkstraSW = new StopWatch();
        periodSW = new StopWatch();
        lazySW = new StopWatch();
        neighborSW = new StopWatch();
      }

      counter++;
      int polledNode = sortedNodes.pollKey();
      if (sortedNodes.getSize() < lastNodesLazyUpdates) {
        lazySW.start();
        int priority = oldPriorities[polledNode] = calculatePriority(polledNode);
        if (!sortedNodes.isEmpty() && priority > sortedNodes.peekValue()) {
          // current node got more important => insert as new value and contract it later
          sortedNodes.insert(polledNode, priority);
          lazySW.stop();
          continue;
        }
        lazySW.stop();
      }

      // contract!
      newShortcuts += addShortcuts(polledNode);
      g.setLevel(polledNode, level);
      level++;

      EdgeSkipIterator iter = vehicleAllExplorer.setBaseNode(polledNode);
      while (iter.next()) {
        int nn = iter.getAdjNode();
        if (g.getLevel(nn) != 0)
          // already contracted no update necessary
          continue;

        if (neighborUpdate && rand.nextInt(100) < neighborUpdatePercentage) {
          neighborSW.start();
          int oldPrio = oldPriorities[nn];
          int priority = oldPriorities[nn] = calculatePriority(nn);
          if (priority != oldPrio) sortedNodes.update(nn, oldPrio, priority);

          neighborSW.stop();
        }

        if (removesHigher2LowerEdges) lg.disconnect(vehicleAllTmpExplorer, iter);
      }
    }

    // Preparation works only once so we can release temporary data.
    // The preparation object itself has to be intact to create the algorithm.
    close();
    logger.info(
        "took:"
            + (int) allSW.stop().getSeconds()
            + ", new shortcuts: "
            + newShortcuts
            + ", "
            + prepareWeighting
            + ", "
            + prepareEncoder
            + ", removeHigher2LowerEdges:"
            + removesHigher2LowerEdges
            + ", dijkstras:"
            + dijkstraCount
            + ", t(dijk):"
            + (int) dijkstraSW.getSeconds()
            + ", t(period):"
            + (int) periodSW.getSeconds()
            + ", t(lazy):"
            + (int) lazySW.getSeconds()
            + ", t(neighbor):"
            + (int) neighborSW.getSeconds()
            + ", meanDegree:"
            + (long) meanDegree
            + ", initSize:"
            + initSize
            + ", periodic:"
            + periodicUpdatesPercentage
            + ", lazy:"
            + lastNodesLazyUpdatePercentage
            + ", neighbor:"
            + neighborUpdatePercentage);
  }