@SuppressWarnings({"AssignmentToForLoopParameter"})
  private int doStore(@NotNull TextChange change) {
    int newChangeStart = change.getStart();
    int newChangeEnd = change.getEnd();
    int insertionIndex = getChangeIndex(change.getStart());
    int clientShift = 0; // 'Client text' shift before the given change to store. I.e. this value can be subtracted from the
                         // given change's start/end offsets in order to get original document range affected by the given change.
    int changeDiff = change.getText().length() - (change.getEnd() - change.getStart());

    if (insertionIndex < 0) {
      insertionIndex = -insertionIndex - 1;
      if (insertionIndex >= myChanges.size()) {
        if (!myChanges.isEmpty()) {
          ChangeEntry changeEntry = myChanges.get(myChanges.size() - 1);
          clientShift = changeEntry.clientStartOffset - changeEntry.change.getStart() + changeEntry.change.getDiff();
        }
        myChanges.add(new ChangeEntry(
          new TextChangeImpl(change.getText(), change.getStart() - clientShift, change.getEnd() - clientShift),
          change.getStart()
        ));
        return insertionIndex;
      }
      else if (insertionIndex > 0) {
        ChangeEntry changeEntry = myChanges.get(insertionIndex - 1);
        clientShift = changeEntry.clientStartOffset - changeEntry.change.getStart() + changeEntry.change.getDiff();
      }
    }
    else {
      ChangeEntry changeEntry = myChanges.get(insertionIndex);
      clientShift = changeEntry.clientStartOffset - changeEntry.change.getStart();
    }

    boolean updateClientOffsetOnly = false;
    for (int i = insertionIndex; i < myChanges.size(); i++) {
      ChangeEntry changeEntry = myChanges.get(i);
      int storedClientStart = changeEntry.change.getStart() + clientShift;
      CharSequence storedText = changeEntry.change.getText();
      int storedClientEnd = storedClientStart + storedText.length();

      // Stored change lays after the new one.
      if (!updateClientOffsetOnly && storedClientStart > newChangeEnd) {
        if (changeDiff != 0) {
          updateClientOffsetOnly = true;
        }
        else {
          break;
        }
      }

      if (updateClientOffsetOnly) {
        changeEntry.clientStartOffset += changeDiff;
        continue;
      }
      
      // Stored change lays before the new one.
      if (storedClientEnd <= newChangeStart) {
        clientShift += changeEntry.change.getDiff();
        insertionIndex = i + 1;
        continue;
      }
      
      // Check if given change targets sub-range of the stored one.
      if (storedClientStart <= newChangeStart && storedClientEnd >= newChangeEnd) {
        StringBuilder adjustedText = new StringBuilder();
        if (storedClientStart < newChangeStart) {
          adjustedText.append(storedText.subSequence(0, newChangeStart - storedClientStart));
        }
        adjustedText.append(change.getText());
        if (storedClientEnd > newChangeEnd) {
          adjustedText.append(storedText.subSequence(newChangeEnd - storedClientStart, storedText.length()));
        }

        if (adjustedText.length() == 0 && changeEntry.change.getStart() == changeEntry.change.getEnd()) {
          myChanges.remove(i);
          insertionIndex = -1;
          updateClientOffsetOnly = true;
          i--; // Assuming that 'i' is incremented at the 'for' loop.
          continue;
        }

        changeEntry.change = new TextChangeImpl(adjustedText, changeEntry.change.getStart(), changeEntry.change.getEnd());
        insertionIndex = -1;
        updateClientOffsetOnly = true;
        continue;
      }

      // Check if given change completely contains stored change range.
      if (newChangeStart <= storedClientStart && newChangeEnd >= storedClientEnd) {
        myChanges.remove(i);
        insertionIndex = i;
        newChangeEnd -= changeEntry.change.getDiff();
        i--; // Assuming that 'i' is incremented at the 'for' loop.
        continue;
      }

      // Check if given change intersects stored change range from the left.
      if (newChangeStart <= storedClientStart && newChangeEnd > storedClientStart) {
        int numberOfStoredChangeSymbolsToRemove = newChangeEnd - storedClientStart;
        CharSequence adjustedText = storedText.subSequence(numberOfStoredChangeSymbolsToRemove, storedText.length());
        changeEntry.change = new TextChangeImpl(adjustedText, changeEntry.change.getStart(), changeEntry.change.getEnd());
        changeEntry.clientStartOffset += changeDiff + numberOfStoredChangeSymbolsToRemove;
        newChangeEnd -= numberOfStoredChangeSymbolsToRemove;
        insertionIndex = i;
        continue;
      }

      // Check if given change intersects stored change range from the right.
      if (newChangeEnd >= storedClientEnd) {
        CharSequence adjustedText = storedText.subSequence(0, newChangeStart - storedClientStart);
        TextChangeImpl adjusted = new TextChangeImpl(adjustedText, changeEntry.change.getStart(), changeEntry.change.getEnd());
        changeEntry.change = adjusted;
        clientShift += adjusted.getDiff();
        newChangeEnd -= storedClientEnd - newChangeStart;
        insertionIndex = i + 1;
        continue;
      }

      // Check if given change is left-adjacent to the stored change.
      changeEntry.clientStartOffset += changeDiff;
    }

    if (insertionIndex >= 0) {
      myChanges.add(insertionIndex, new ChangeEntry(
        new TextChangeImpl(change.getText(), newChangeStart - clientShift, newChangeEnd - clientShift),
        change.getStart()
      ));
    }
    
    return insertionIndex;
  }
@SuppressWarnings({"AssignmentToForLoopParameter"})
  private int doStore(@NotNull TextChange change)