// Copyright 2000-2020 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.codeInsight.documentation.render;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.ex.util.EditorScrollingPositionKeeper;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.util.*;

@Service
public final class DocRenderItemUpdater implements Runnable {
  private static final long MAX_UPDATE_DURATION_MS = 50;
  private final Map<Inlay<DocRenderer>, Boolean> myQueue = new HashMap<>();

  static DocRenderItemUpdater getInstance() {
    return ApplicationManager.getApplication().getService(DocRenderItemUpdater.class);
  }

  void updateInlays(@NotNull Collection<Inlay<DocRenderer>> inlays, boolean recreateContent) {
    if (inlays.isEmpty()) return;
    boolean wasEmpty = myQueue.isEmpty();
    for (Inlay<DocRenderer> inlay : inlays) {
      myQueue.merge(inlay, recreateContent, (oldValue, newValue) -> newValue | oldValue);
    }
    if (wasEmpty) processChunk();
  }

  @Override
  public void run() {
    processChunk();
  }

  private void processChunk() {
    long deadline = System.currentTimeMillis() + MAX_UPDATE_DURATION_MS;
    Map<Editor, EditorScrollingPositionKeeper> keepers = new HashMap<>();
    // This is a heuristic to lessen visual 'jumping' on editor opening. We'd like inlays visible at target opening location to be updated
    // first, and all the rest - later. We're not specifically optimizing for the case when multiple editors are opened simultaneously now,
    // opening several editors in succession should work fine with this logic though (by the time a new editor is opened, 'high-priority'
    // inlays from the previous editor are likely to have been processed already).
    List<Inlay<DocRenderer>> toProcess = new ArrayList<>(myQueue.keySet());
    toProcess.sort(Comparator.comparingInt(i -> -Math.abs(i.getOffset() - i.getEditor().getCaretModel().getOffset())));
    do {
      Inlay<DocRenderer> inlay = toProcess.remove(toProcess.size() - 1);
      boolean updateContent = myQueue.remove(inlay);
      if (inlay.isValid()) {
        Editor editor = inlay.getEditor();
        keepers.computeIfAbsent(editor, e -> {
          EditorScrollingPositionKeeper keeper = new EditorScrollingPositionKeeper(editor);
          keeper.savePosition();
          return keeper;
        });
        inlay.getRenderer().update(true, updateContent);
      }
    }
    while (!toProcess.isEmpty() && System.currentTimeMillis() < deadline);
    keepers.values().forEach(k -> k.restorePosition(false));
    if (!myQueue.isEmpty()) SwingUtilities.invokeLater(this);
  }
}

@@ -422,45 +422,208 @@
 tor.
-ex.util.EditorScrollingPositionKeeper
+VisualPosition;%0Aimport com.intellij.openapi.editor.ex.util.EditorScrollingPositionKeeper;%0Aimport it.unimi.dsi.fastutil.objects.Object2IntMap;%0Aimport it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
 ;%0Aim
@@ -701,14 +701,56 @@
 ava.
-util.*
+awt.*;%0Aimport java.util.*;%0Aimport java.util.List
 ;%0A%0A@
@@ -2253,24 +2253,91 @@
 .keySet());%0A
+    Object2IntMap%3CEditor%3E memoMap = new Object2IntOpenHashMap%3C%3E();%0A
     toProces
@@ -2402,48 +2402,47 @@
 ) - 
-i.getEditor().getCaretModel().getOffset(
+getVisibleOffset(i.getEditor(), memoMap
 ))))
@@ -3062,24 +3062,24 @@
 on(false));%0A
-
     if (!myQ
@@ -3131,11 +3131,564 @@
 is);%0A  %7D
+%0A%0A  private static int getVisibleOffset(Editor editor, Object2IntMap%3CEditor%3E memoMap) %7B%0A    return memoMap.computeIntIfAbsent(editor, e -%3E %7B%0A      Rectangle visibleArea = e.getScrollingModel().getVisibleAreaOnScrollingFinished();%0A      if (editor.isDisposed() %7C%7C visibleArea.height %3C= 0) %7B%0A        return e.getCaretModel().getOffset();%0A      %7D%0A      else %7B%0A        int y = visibleArea.y + visibleArea.height / 2;%0A        int visualLine = e.yToVisualLine(y);%0A        return e.visualPositionToOffset(new VisualPosition(visualLine, 0));%0A      %7D%0A    %7D);%0A  %7D
 %0A%7D%0A

change prioritization logic for inlay update queue