@NotNull
  private static FairDiffIterable correctChangesSecondStep(@NotNull final List<? extends Line> lines1,
                                                           @NotNull final List<? extends Line> lines2,
                                                           @NotNull final FairDiffIterable changes) {
    /*
     * We want to fix invalid matching here:
     *
     * .{        ..{
     * ..{   vs  ...{
     * ...{
     *
     * first step will return matching (0,2)-(0,2). And we should adjust it to (1,3)-(0,2)
     *
     *
     * From the other hand, we don't want to reduce number of IW-matched lines.
     *
     * .{         ...{
     * ..{    vs  ..{
     * ...{       .{
     *
     * first step will return (0,3)-(0,3) and 'correcting' it to (0,1)-(2,3) is wrong (and it will break ByWord highlighting).
     *
     *
     * Idea:
     * 1. lines are matched at first step and equal -> match them
     * 2. lines are not matched at first step -> do not match them
     * 3. lines are matched at first step and not equal ->
     *   a. find all IW-equal lines in the same unmatched block
     *   b. find a maximum matching between them, maximising amount of equal pairs in it
     *   c. match equal lines using result of the previous step
     */

    final ExpandChangeBuilder builder = new ExpandChangeBuilder(lines1, lines2);
    new Object() {
      private CharSequence sample = null;
      private int last1 = 0;
      private int last2 = 0;

      public void run() {
        for (Range range : changes.iterateUnchanged()) {
          int count = range.end1 - range.start1;
          for (int i = 0; i < count; i++) {
            int index1 = range.start1 + i;
            int index2 = range.start2 + i;
            Line line1 = lines1.get(index1);
            Line line2 = lines2.get(index2);

            if (!ComparisonUtil.isEquals(sample, line1.getContent(), IGNORE_WHITESPACES)) {
              if (line1.equals(line2)) {
                flush(index1, index2);
                builder.markEqual(index1, index2);
              }
              else {
                flush(index1, index2);
                sample = line1.getContent();
              }
            }
          }
        }
        flush(changes.getLength1(), changes.getLength2());
      }

      private void flush(int line1, int line2) {
        if (sample == null) return;

        int start1 = Math.max(last1, builder.getIndex1());
        int start2 = Math.max(last2, builder.getIndex2());

        IntList subLines1=new IntArrayList();
        IntList subLines2=new IntArrayList();
        for (int i = start1; i < line1; i++) {
          if (ComparisonUtil.isEquals(sample, lines1.get(i).getContent(), IGNORE_WHITESPACES)) {
            subLines1.add(i);
            last1 = i + 1;
          }
        }
        for (int i = start2; i < line2; i++) {
          if (ComparisonUtil.isEquals(sample, lines2.get(i).getContent(), IGNORE_WHITESPACES)) {
            subLines2.add(i);
            last2 = i + 1;
          }
        }

        assert subLines1.size() > 0 && subLines2.size() > 0;
        alignExactMatching(subLines1, subLines2);

        sample = null;
      }

      private void alignExactMatching(IntList subLines1, IntList subLines2) {
        int n = Math.max(subLines1.size(), subLines2.size());
        boolean skipAligning = n > 10 || // we use brute-force algorithm (C_n_k). This will limit search space by ~250 cases.
                               subLines1.size() == subLines2.size(); // nothing to do

        if (skipAligning) {
          int count = Math.min(subLines1.size(), subLines2.size());
          for (int i = 0; i < count; i++) {
            int index1 = subLines1.getInt(i);
            int index2 = subLines2.getInt(i);
            if (lines1.get(index1).equals(lines2.get(index2))) {
              builder.markEqual(index1, index2);
            }
          }
          return;
        }

        if (subLines1.size() < subLines2.size()) {
          int[] matching = getBestMatchingAlignment(subLines1, subLines2, lines1, lines2);
          for (int i = 0; i < subLines1.size(); i++) {
            int index1 = subLines1.getInt(i);
            int index2 = subLines2.getInt(matching[i]);
            if (lines1.get(index1).equals(lines2.get(index2))) {
              builder.markEqual(index1, index2);
            }
          }
        }
        else {
          int[] matching = getBestMatchingAlignment(subLines2, subLines1, lines2, lines1);
          for (int i = 0; i < subLines2.size(); i++) {
            int index1 = subLines1.getInt(matching[i]);
            int index2 = subLines2.getInt(i);
            if (lines1.get(index1).equals(lines2.get(index2))) {
              builder.markEqual(index1, index2);
            }
          }
        }
      }
    }.run();

    return fair(builder.finish());
  }
@NotNull
  private static FairDiffIterable correctChangesSecondStep(@NotNull final List<? extends Line> lines1,
                                                           @NotNull final List<? extends Line> lines2,
                                                           @NotNull final FairDiffIterable changes)