// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.ex.util;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.Collections;
import java.util.List;


/**
 * Allows to keep vertical scrolling position in editor for operations that affect editor contents (changing document, collapsing/expanding
 * folding regions, etc). If caret is in view, then (optionally) its vertical position is preserved, otherwise, the code displayed in top
 * left corner of editor viewport. {@link #savePosition()} method should be called before the operation, to save scrolling position, and
 * {@link #restorePosition(boolean)} method - after the operation, to restore the position.
 */
public class EditorScrollingPositionKeeper implements Disposable {
  private final Editor myEditor;
  private final boolean myIgnoreCaret;
  private int myViewportShift;
  private RangeMarker myTopLeftCornerMarker;

  public EditorScrollingPositionKeeper(@NotNull Editor editor) {
    this(editor, false);
  }

  /**
   * @param ignoreCaret if {@code true}, caret position will be ignored, and top-left corner of editor will be used as an anchor
   */
  public EditorScrollingPositionKeeper(@NotNull Editor editor, boolean ignoreCaret) {
    myEditor = editor;
    myIgnoreCaret = ignoreCaret;
  }

  public void savePosition() {
    disposeMarker();
    Rectangle visibleArea = myEditor.getScrollingModel().getVisibleAreaOnScrollingFinished();
    int caretY = myIgnoreCaret ? 0 : myEditor.visualLineToY(myEditor.getCaretModel().getVisualPosition().line);
    if (myIgnoreCaret ||
        visibleArea.height > 0 && (caretY + myEditor.getLineHeight() <= visibleArea.y || caretY >= (visibleArea.y + visibleArea.height))) {
      int topLeftCornerOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(visibleArea.getLocation()));
      myTopLeftCornerMarker = myEditor.getDocument().createRangeMarker(topLeftCornerOffset, topLeftCornerOffset);
      myViewportShift = myEditor.offsetToXY(topLeftCornerOffset).y - visibleArea.y;
    }
    else {
      myTopLeftCornerMarker = null;
      myViewportShift = caretY - visibleArea.y;
    }
  }

  public void restorePosition(boolean stopAnimation) {
    int newY;
    if (myTopLeftCornerMarker == null) {
      newY = myEditor.visualLineToY(myEditor.getCaretModel().getVisualPosition().line);
    }
    else {
      if (!myTopLeftCornerMarker.isValid()) return;
      newY = myEditor.offsetToXY(myTopLeftCornerMarker.getStartOffset()).y;
    }
    ScrollingModel scrollingModel = myEditor.getScrollingModel();
    Rectangle targetArea = scrollingModel.getVisibleAreaOnScrollingFinished();
    // when animated scrolling is in progress, we'll not stop it immediately
    boolean disableAnimation = targetArea.equals(scrollingModel.getVisibleArea()) || stopAnimation;
    if (disableAnimation) scrollingModel.disableAnimation();
    scrollingModel.scroll(targetArea.x, newY - myViewportShift); // can't use 'scrollVertically' - it aborts horizontal scrolling
    if (disableAnimation) scrollingModel.enableAnimation();
  }


  @Override
  public void dispose() {
    disposeMarker();
  }

  private void disposeMarker() {
    if (myTopLeftCornerMarker != null) myTopLeftCornerMarker.dispose();
  }

  /**
   * Performs given operation, restoring editor scrolling position afterwards.
   */
  public static void perform(@Nullable Editor editor, boolean stopAnimation, @NotNull Runnable operation) {
    if (editor == null) {
      operation.run();
      return;
    }
    EditorScrollingPositionKeeper keeper = new EditorScrollingPositionKeeper(editor);
    keeper.savePosition();
    try {
      operation.run();
      keeper.restorePosition(stopAnimation);
    }
    finally {
      Disposer.dispose(keeper);
    }
  }

  /**
   * Performs given operation, restoring scrolling position in all document's editors afterwards.
   */
  public static void perform(@Nullable Document document, boolean stopAnimation, @NotNull Runnable runnable) {
    EditorScrollingPositionKeeper.ForDocument keeper = new EditorScrollingPositionKeeper.ForDocument(document);
    keeper.savePosition();
    try {
      runnable.run();
      keeper.restorePosition(stopAnimation);
    }
    finally {
      Disposer.dispose(keeper);
    }
  }

  /**
   * Same as {@link EditorScrollingPositionKeeper}, but tracking all editors for a given document.
   */
  public static class ForDocument implements Disposable {
    private final List<EditorScrollingPositionKeeper> myKeepers;

    public ForDocument(@Nullable Document document) {
      myKeepers = document == null ? Collections.emptyList()
                                   : ContainerUtil.map(EditorFactory.getInstance().getEditors(document),
                                                       EditorScrollingPositionKeeper::new);
    }

    public void savePosition() {
      myKeepers.forEach(EditorScrollingPositionKeeper::savePosition);
    }

    public void restorePosition(boolean stopAnimation) {
      myKeepers.forEach(k -> k.restorePosition(stopAnimation));
    }

    @Override
    public void dispose() {
      myKeepers.forEach(Disposer::dispose);
    }
  }
}

@@ -711,21 +711,8 @@
 then
- (optionally)
  its
@@ -780,19 +780,16 @@
 d in top
-%0A *
  left co
@@ -787,32 +787,35 @@
 p left corner of
+%0A *
  editor viewport
@@ -1118,47 +1118,8 @@
 or;%0A
-  private final boolean myIgnoreCaret;%0A
   pr
@@ -1264,315 +1264,25 @@
     
-this(editor, false);%0A  %7D%0A%0A  /**%0A   * @param ignoreCaret if %7B@code true%7D, caret position will be ignored, and top-left corner of editor will be used as an anchor%0A   */%0A  public EditorScrollingPositionKeeper(@NotNull Editor editor, boolean ignoreCaret) %7B%0A    myEditor = editor;%0A    myIgnoreCaret = ignoreCaret
+myEditor = editor
 ;%0A  
@@ -1450,28 +1450,8 @@
 tY =
- myIgnoreCaret ? 0 :
  myE
@@ -1534,33 +1534,8 @@
 if (
-myIgnoreCaret %7C%7C%0A        
 visi

remove unused constructor of EditorScrollingPositionKeeper