/*
 * Copyright 2000-2017 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.codeInspection.ui;

import com.intellij.codeInspection.reference.RefElement;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.events.*;
import com.intellij.util.Alarm;
import com.intellij.util.ObjectUtils;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.vfs.AsyncVfsEventsListener;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Set;

class InspectionViewPsiTreeChangeAdapter implements AsyncVfsEventsListener {
  private final InspectionResultsView myView;
  private final Alarm myAlarm;
  private final Set<VirtualFile> myUnPresentEditedFiles = ContainerUtil.createWeakSet();

  public InspectionViewPsiTreeChangeAdapter(@NotNull InspectionResultsView view) {
    myView = view;
    myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, view);
  }

  @Override
  public void filesChanged(@NotNull List<VFileEvent> events) {
    //TODO filter out non project files
    boolean someFilesWereDeletedOrReplaced = false;
    Set<VirtualFile> filesToCheck = new THashSet<>();
    for (VFileEvent event : events) {
      if (!someFilesWereDeletedOrReplaced && (event instanceof VFileDeleteEvent || event instanceof VFileMoveEvent)) {
        someFilesWereDeletedOrReplaced = true;
      }
      if ((event instanceof VFileContentChangeEvent || event instanceof VFilePropertyChangeEvent) && !myUnPresentEditedFiles.contains(event.getFile())) {
        filesToCheck.add(event.getFile());
      }
    }
    if (filesToCheck.isEmpty() && !someFilesWereDeletedOrReplaced) return;

    boolean[] needUpdateUI = {false};
    if (someFilesWereDeletedOrReplaced) {
      //TODO combine 1
      synchronized (myView.getTreeStructureUpdateLock()) {
        InspectionTreeNode root = myView.getTree().getRoot();
        processNodesIfNeed(root, (node) -> {
          if (myView.isDisposed()) {
            return false;
          }

          if (node instanceof SuppressableInspectionTreeNode) {
            RefElement element = ObjectUtils.tryCast(((SuppressableInspectionTreeNode)node).getElement(), RefElement.class);
            if (element != null) {
              VirtualFile vFile = element.getPointer().getVirtualFile();
              if (vFile == null || !vFile.isValid()) {
                ((SuppressableInspectionTreeNode)node).dropCache();
                if (!needUpdateUI[0]) {
                  needUpdateUI[0] = true;
                }
              }
            }
          }

          return true;
        });
      }
    }

    Set<VirtualFile> unPresentFiles = new HashSet<>(filesToCheck);
    if (!filesToCheck.isEmpty()) {
      //TODO combine 2
      synchronized (myView.getTreeStructureUpdateLock()) {
        InspectionTreeNode root = myView.getTree().getRoot();
        processNodesIfNeed(root, (node) -> {
          if (myView.isDisposed()) {
            return false;
          }

          if (node instanceof SuppressableInspectionTreeNode) {
            RefElement element = ObjectUtils.tryCast(((SuppressableInspectionTreeNode)node).getElement(), RefElement.class);
            if (element != null) {
              VirtualFile vFile = element.getPointer().getVirtualFile();
              if (filesToCheck.contains(vFile)) {
                unPresentFiles.remove(vFile);
                ((SuppressableInspectionTreeNode)node).dropCache();
                if (!needUpdateUI[0]) {
                  needUpdateUI[0] = true;
                }
              }
            }
          }

          return true;
        });
      }
    }

    myUnPresentEditedFiles.addAll(unPresentFiles);
    if (needUpdateUI[0] && !myAlarm.isDisposed()) {
      myAlarm.cancelAllRequests();
      myAlarm.addRequest(() -> myView.resetTree(), 100, ModalityState.NON_MODAL);
    }
  }

  private static void processNodesIfNeed(InspectionTreeNode node, Processor<InspectionTreeNode> processor) {
    if (processor.process(node)) {
      final int count = node.getChildCount();
      for (int i = 0; i < count; i++) {
        processNodesIfNeed((InspectionTreeNode)node.getChildAt(i), processor);
      }
    }
  }
}

@@ -294,16 +294,120 @@
 yState;%0A
+import com.intellij.openapi.application.ReadAction;%0Aimport com.intellij.openapi.roots.ProjectFileIndex;%0A
 import c
@@ -1100,16 +1100,62 @@
 akSet();
+%0A  private final ProjectFileIndex myFileIndex;
 %0A%0A  publ
@@ -1250,16 +1250,83 @@
 = view;%0A
+    myFileIndex = ProjectFileIndex.getInstance(view.getProject());%0A
     myAl
@@ -1530,30 +1530,27 @@
 ereDeletedOr
-Replac
+Mov
 ed = false;%0A
@@ -1674,22 +1674,19 @@
 eletedOr
-Replac
+Mov
 ed && (e
@@ -1787,22 +1787,19 @@
 eletedOr
-Replac
+Mov
 ed = tru
@@ -1815,20 +1815,125 @@
 %7D%0A      
-if (
+VirtualFile file = event.getFile();%0A      if (file != null &&%0A          isInSourceContent(file) &&%0A          
 (event i
@@ -2014,16 +2014,26 @@
 vent) &&
+%0A         
  !myUnPr
@@ -2188,30 +2188,27 @@
 ereDeletedOr
-Replac
+Mov
 ed) return;%0A
@@ -2280,14 +2280,11 @@
 edOr
-Replac
+Mov
 ed) 
@@ -4415,16 +4415,251 @@
  %7D%0A  %7D%0A%0A
+  protected boolean isInSourceContent(VirtualFile file) %7B%0A    return ReadAction.compute(() -%3E %7B%0A      if (myView.getProject().isDisposed()) %7B%0A        return false;%0A      %7D%0A      return myFileIndex.isInSourceContent(file);%0A    %7D);%0A  %7D%0A%0A
   privat

rewrite InspectionViewPsiTreeChangeAdapter (IDEA-183325, IDEA-182804) don't process files outside sources