// 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.psi.impl.cache.impl;

import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.cache.CacheManager;
import com.intellij.psi.impl.search.PsiSearchHelperImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Processor;
import com.intellij.util.Processors;
import com.intellij.util.indexing.DumbModeAccessType;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class IndexCacheManagerImpl implements CacheManager {
  @NotNull
  private final Project myProject;

  public IndexCacheManagerImpl(@NotNull Project project) {
    myProject = project;
  }

  @Override
  public PsiFile @NotNull [] getFilesWithWord(@NotNull String word,
                                              short occurenceMask,
                                              @NotNull GlobalSearchScope scope,
                                              boolean caseSensitively) {
    if (myProject.isDefault()) {
      return PsiFile.EMPTY_ARRAY;
    }
    List<PsiFile> result = new ArrayList<>();
    Processor<PsiFile> processor = Processors.cancelableCollectProcessor(result);

    processFilesWithWord(processor, word, occurenceMask, scope, caseSensitively);
    return result.isEmpty() ? PsiFile.EMPTY_ARRAY : result.toArray(PsiFile.EMPTY_ARRAY);
  }

  @Override
  public VirtualFile @NotNull [] getVirtualFilesWithWord(@NotNull String word,
                                                         short occurenceMask,
                                                         @NotNull GlobalSearchScope scope,
                                                         boolean caseSensitively) {
    if (myProject.isDefault()) {
      return VirtualFile.EMPTY_ARRAY;
    }

    List<VirtualFile> result = new ArrayList<>(5);
    Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(result);
    FileBasedIndex.getInstance().ignoreDumbMode(() -> {
      collectVirtualFilesWithWord(word, occurenceMask, scope, caseSensitively, processor);
    }, DumbModeAccessType.RAW_INDEX_DATA_ACCEPTABLE);
    return result.isEmpty() ? VirtualFile.EMPTY_ARRAY : result.toArray(VirtualFile.EMPTY_ARRAY);
  }

  @Override
  public boolean processVirtualFilesWithAllWords(@NotNull Collection<String> words,
                                                 short occurenceMask,
                                                 @NotNull GlobalSearchScope scope,
                                                 boolean caseSensitively,
                                                 @NotNull Processor<? super VirtualFile> processor) {
    PsiSearchHelperImpl.TextIndexQuery query = PsiSearchHelperImpl.TextIndexQuery.fromWords(words, caseSensitively, false, occurenceMask);
    return FileBasedIndex.getInstance().processFilesContainingAllKeys(query.toFileBasedIndexQueries(), scope, processor);
  }

  // IMPORTANT!!!
  // Since implementation of virtualFileProcessor.process() may call indices directly or indirectly,
  // we cannot call it inside FileBasedIndex.processValues() method except in collecting form
  // If we do, deadlocks are possible (IDEADEV-42137). Process the files without not holding indices' read lock.
  private boolean collectVirtualFilesWithWord(@NotNull String word,
                                              short occurrenceMask,
                                              @NotNull GlobalSearchScope scope,
                                              boolean caseSensitively,
                                              @NotNull Processor<? super VirtualFile> fileProcessor) {
    if (myProject.isDefault()) {
      return true;
    }
    PsiSearchHelperImpl.TextIndexQuery query = PsiSearchHelperImpl.TextIndexQuery.fromWord(word, caseSensitively, occurrenceMask);

    try {
      return ReadAction.compute(() -> FileBasedIndex
        .getInstance()
        .processFilesContainingAllKeys(query.toFileBasedIndexQueries(), scope, fileProcessor)
      );
    }
    catch (IndexNotReadyException e) {
      throw new ProcessCanceledException();
    }
  }

  @Override
  public boolean processFilesWithWord(@NotNull Processor<? super PsiFile> psiFileProcessor, @NotNull String word, short occurrenceMask, @NotNull GlobalSearchScope scope, boolean caseSensitively) {
    List<VirtualFile> result = new ArrayList<>(5);
    Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(result);
    collectVirtualFilesWithWord(word, occurrenceMask, scope, caseSensitively, processor);
    if (result.isEmpty()) return true;

    PsiManager psiManager = PsiManager.getInstance(myProject);
    Processor<VirtualFile> virtualFileProcessor = new ReadActionProcessor<VirtualFile>() {
      @Override
      public boolean processInReadAction(VirtualFile virtualFile) {
        if (virtualFile.isValid()) {
          PsiFile psiFile = psiManager.findFile(virtualFile);
          return psiFile == null || psiFileProcessor.process(psiFile);
        }
        return true;
      }
    };


    // IMPORTANT!!!
    // Since implementation of virtualFileProcessor.process() may call indices directly or indirectly,
    // we cannot call it inside FileBasedIndex.processValues() method
    // If we do, deadlocks are possible (IDEADEV-42137). So first we obtain files with the word specified,
    // and then process them not holding indices' read lock.
    for (VirtualFile vFile : result) {
      ProgressIndicatorProvider.checkCanceled();
      if (!virtualFileProcessor.process(vFile)) {
        return false;
      }
    }
    return true;
  }
}

@@ -182,60 +182,8 @@
 l;%0A%0A
-import com.intellij.openapi.application.ReadAction;%0A
 impo
@@ -1014,24 +1014,54 @@
 Collection;%0A
+import java.util.Collections;%0A
 import java.
@@ -3230,24 +3230,82 @@
 rocessor) %7B%0A
+    if (myProject.isDefault()) %7B%0A      return true;%0A    %7D%0A
     PsiSearc
@@ -3431,16 +3431,29 @@
 eMask);%0A
+%0A    try %7B%0A  
     retu
@@ -3566,16 +3566,111 @@
 essor);%0A
+    %7D%0A    catch (IndexNotReadyException e) %7B%0A      throw new ProcessCanceledException();%0A    %7D%0A
   %7D%0A%0A  /
@@ -4002,23 +4002,20 @@
 private 
-boolean
+void
  collect
@@ -4049,35 +4049,32 @@
 ll String word,%0A
-   
                 
@@ -4157,35 +4157,32 @@
                 
-   
 @NotNull GlobalS
@@ -4191,35 +4191,32 @@
 rchScope scope,%0A
-   
                 
@@ -4302,35 +4302,32 @@
                 
-   
 @NotNull Process
@@ -4375,477 +4375,124 @@
     
-if (myProject.isDefault()) %7B%0A      return true;%0A    %7D%0A    PsiSearchHelperImpl.TextIndexQuery query = PsiSearchHelperImpl.TextIndexQuery.fromWord(word, caseSensitively, occurrenceMask);%0A%0A    try %7B%0A      return ReadAction.compute(() -%3E FileBasedIndex%0A        .getInstance()%0A        .processFilesContainingAllKeys(query.toFileBasedIndexQueries(), scope, fileProcessor)%0A      );%0A    %7D%0A    catch (IndexNotReadyException e) %7B%0A      throw new ProcessCanceledException();%0A    %7D
+processVirtualFilesWithAllWords(Collections.singleton(word), occurrenceMask, scope, caseSensitively, fileProcessor);
 %0A  %7D

simplify IndexCacheManagerImpl (delegate methods to a single one)