/**
   * Build paths for two partial maps if they have the following properties 1. No overlap (passing
   * trimOverlap) 2. passing PathBuilderFilter
   *
   * @param groupedMap
   * @param pbFilter
   * @param vmProcessor
   * @param maxTrim
   * @param ear
   * @return
   */
  private List<ClusterPathNode> buildPath(
      List<OptMapResultNode> groupedMap,
      PathBuilderFilter pbFilter,
      VirtualMapProcessor vmProcessor) {
    // resulting path will not form loop (i.e. is acyclic), no A->B B->C C->A occurs at any time.
    // But should handle null start and null stop properly

    Collections.sort(groupedMap, OptMapResultNode.subfragstartstopcomparator);
    List<ClusterPathNode> clusterPathList = new ArrayList<ClusterPathNode>();
    OptMapResultNode[][] trim1Results = new OptMapResultNode[groupedMap.size()][maxTrim + 1];
    OptMapResultNode[][] trim2Results = new OptMapResultNode[groupedMap.size()][maxTrim + 1];
    for (int i = 0; i < groupedMap.size(); i++) {
      trim1Results[i][0] = groupedMap.get(i);
      trim2Results[i][0] = groupedMap.get(i);
    }

    // head of the path
    for (OptMapResultNode map : groupedMap)
      clusterPathList.add(new ClusterPathNode(null, map, 0, 0, null, map));

    // start building paths for map to map
    for (int i = 0; i < groupedMap.size(); i++) {
      int j = i - 1;
      while (j >= 0) {
        if (pbFilter.checkPass(groupedMap.get(j), groupedMap.get(i))) {
          TrimResult trimResult =
              trimOverlap(optrefmap, trim1Results[j], trim2Results[i], pbFilter, vmProcessor);
          if (trimResult.successful) {
            OptMapResultNode map1 = trimResult.result1;
            OptMapResultNode map2 = trimResult.result2;
            int trim1 = groupedMap.get(j).cigar.getMatch() - map1.cigar.getMatch();
            int trim2 = groupedMap.get(i).cigar.getMatch() - map2.cigar.getMatch();
            ClusterPathNode cp =
                new ClusterPathNode(groupedMap.get(j), groupedMap.get(i), trim1, trim2, map1, map2);
            clusterPathList.add(cp);
          }
        }
        j--;
      }
    }
    // tail of the path
    for (OptMapResultNode map : groupedMap)
      clusterPathList.add(new ClusterPathNode(map, null, 0, 0, map, null));

    for (ClusterPathNode path : clusterPathList) path.updateScore(vmProcessor);
    return clusterPathList;
  }
  /**
   * Check if two partial maps can be connected. Trimming is done on overlapping partial maps
   *
   * <p>It is essential to use trimming. Yet, there are two drawbacks using trimming 1. Trimming is
   * expensive. 2. Trimming may lead to inappropriate results (This is done by validation using
   * trimear)
   *
   * @param optrefmap
   * @param maxTrim
   * @param result1
   * @param result2
   * @param vmProcessor
   * @param ear
   * @return
   */
  private TrimResult trimOverlap(
      LinkedHashMap<String, DataNode> optrefmap,
      OptMapResultNode[] trim1Result,
      OptMapResultNode[] trim2Result,
      PathBuilderFilter pbFilter,
      VirtualMapProcessor vmProcessor) {
    int penalty = vmProcessor.calcBasicPenalty(trim1Result[0], trim2Result[0]);
    double bestscore = Double.NEGATIVE_INFINITY;
    int bestTrim1 = -1;
    int bestTrim2 = -1;
    boolean[][] trimPair = new boolean[maxTrim + 1][maxTrim + 1];
    // temp increasing maxTrim greatly:
    // 1. if the two score addition is already lower than any one of the score, discard
    // 2. maxTrim
    for (int trimmed = 0; trimmed <= maxTrim; trimmed++) {
      for (int trim1 = 0; trim1 <= trimmed; trim1++) {
        int trim2 = trimmed - trim1;
        if (trimPair[trim1][trim2]) // this trim pair should no longer be done
        continue;
        if ((trim1Result[0].cigar.getMatch() - trim1 >= minMatch)
            && (trim2Result[0].cigar.getMatch() - trim2 >= minMatch)) {
          OptMapResultNode tresult1 = trim1Result[trim1];
          OptMapResultNode tresult2 = trim2Result[trim2];
          // trim1Result[trim1 - 1] must exist
          if (tresult1 == null) {

            tresult1 = new OptMapResultNode(trim1Result[trim1 - 1]);
            tresult1.trimResult(-1 * trim1Result[trim1 - 1].mappedstrand, optrefmap);
            tresult1.updateScore(optrefmap, match, fpp, fnp);
            trim1Result[trim1] = tresult1;
          }
          if (tresult2 == null) {
            tresult2 = new OptMapResultNode(trim2Result[trim2 - 1]);
            tresult2.trimResult(1 * trim2Result[trim2 - 1].mappedstrand, optrefmap);
            tresult2.updateScore(optrefmap, match, fpp, fnp);
            trim2Result[trim2] = tresult2;
          }

          boolean withinTrimear = true;
          withinTrimear =
              Math.abs(tresult1.getMapScale() - 1) < trimear
                  && Math.abs(tresult2.getMapScale() - 1) < trimear;
          boolean removeLaterTrim = false;

          if (pbFilter.checkPass(tresult1, tresult2)) {
            if (withinTrimear && !tresult1.overlap(tresult2)) {
              if (tresult1.mappedscore
                      + tresult2.mappedscore
                      - vmProcessor.calcPenalty(tresult1, tresult2)
                  > bestscore) {
                bestscore =
                    tresult1.mappedscore
                        + tresult2.mappedscore
                        - vmProcessor.calcPenalty(tresult1, tresult2);
                bestTrim1 = trim1;
                bestTrim2 = trim2;
              }
              removeLaterTrim = true;
            } else if (withinTrimear
                && tresult1.mappedscore + tresult2.mappedscore - penalty
                    < bestscore) // We could not calcPenalty directly when
                                 // tresult1.overlap(tresult2)
            removeLaterTrim = true;
          } else removeLaterTrim = true;

          if (removeLaterTrim)
            for (int i = trim1; i <= maxTrim; i++)
              for (int j = trim2; j <= maxTrim; j++) trimPair[i][j] = true;
        }
      }
    }

    if (bestTrim1 == -1) return TrimResult.FailedTrimResult();
    else return TrimResult.SuccessfulTrimResult(trim1Result[bestTrim1], trim2Result[bestTrim2]);
  }