/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.psi.search;

import com.intellij.JavaTestUtil;
import com.intellij.find.FindModel;
import com.intellij.find.impl.FindInProjectUtil;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.testFramework.PsiTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.FindUsagesProcessPresentation;
import com.intellij.util.CommonProcessors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class SearchInLibsTest extends PsiTestCase {
  @Override
  protected void setUp() throws Exception {
    super.setUp();
    String root = JavaTestUtil.getJavaTestDataPath() + "/psi/search/searchInLibs";
    VirtualFile rootFile = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete, false);

    final VirtualFile projectRoot = rootFile.findChild("project");
    assertNotNull(projectRoot);

    final VirtualFile innerSourceRoot = projectRoot.findChild("src2");
    assertNotNull(innerSourceRoot);

    VirtualFile libRoot = rootFile.findChild("lib");
    final VirtualFile libClassesRoot = libRoot.findChild("classes");
    final VirtualFile libSrcRoot = libRoot.findChild("src");
    final VirtualFile libSrc2Root = libRoot.findChild("src2");
    assertNotNull(libRoot);

    PsiTestUtil.removeAllRoots(myModule, null);
    PsiTestUtil.addSourceRoot(myModule, projectRoot);
    PsiTestUtil.addSourceRoot(myModule, innerSourceRoot);
    List<String> sourceRoots = Arrays.asList(libSrcRoot.getUrl(), libSrc2Root.getUrl());
    List<String> classesRoots = Collections.singletonList(libClassesRoot.getUrl());
    ModuleRootModificationUtil.addModuleLibrary(myModule, "lib", classesRoots, sourceRoots);
  }

  public void testFindUsagesInProject() throws Exception {
    doTest("ProjectClass", new String[]{"ProjectClass.java"}, GlobalSearchScope.projectScope(myProject));
  }
  public void testFindUsagesInProject1() throws Exception {
    doTest("LibraryClass1", new String[]{"ProjectClass.java"}, GlobalSearchScope.projectScope(myProject));
  }
  public void testFindUsagesInProject2() throws Exception {
    doTest("LibraryClass2", new String[]{}, GlobalSearchScope.projectScope(myProject));
  }

  public void testFindUsagesInLibs() throws Exception {
    doTest("ProjectClass", new String[]{"ProjectClass.java"}, GlobalSearchScope.allScope(myProject));
  }
  public void testFindUsagesInLibs1() throws Exception {
    doTest("LibraryClass1", new String[]{"LibraryClass2.java", "ProjectClass.java"}, GlobalSearchScope.allScope(myProject));
  }
  public void testFindUsagesInLibs2() throws Exception {
    doTest("LibraryClass2", new String[]{"LibraryClass1.java"}, GlobalSearchScope.allScope(myProject));
  }

  public void testFindInPathInLibraryDirActuallySearchesInTheirSourcesToo() throws Exception {
    FindModel model = new FindModel();
    final PsiClass aClass = myJavaFacade.findClass("LibraryClass1");
    assertNotNull(aClass);
    model.setDirectoryName(aClass.getContainingFile().getContainingDirectory().getVirtualFile().getPath());
    model.setCaseSensitive(true);
    model.setCustomScope(false);
    model.setStringToFind("LibraryClass1");
    model.setProjectScope(false);

    List<UsageInfo> usages = new ArrayList<>();
    CommonProcessors.CollectProcessor<UsageInfo> consumer = new CommonProcessors.CollectProcessor<>(usages);
    FindUsagesProcessPresentation presentation = FindInProjectUtil.setupProcessPresentation(getProject(), false, FindInProjectUtil.setupViewPresentation(false, model));
    FindInProjectUtil.findUsages(model, getProject(), consumer, presentation);

    assertSize(2, usages);
  }

  public void testFindInPathInLibrariesIsNotBrokenAgain() throws Exception {
    FindModel model = new FindModel();
    final PsiClass aClass = myJavaFacade.findClass("LibraryClass1");
    assertNotNull(aClass);
    model.setDirectoryName(aClass.getContainingFile().getContainingDirectory().getVirtualFile().getPath());
    model.setCaseSensitive(true);
    model.setCustomScope(false);
    model.setStringToFind(/*LibraryClas*/"s1"); // to defeat trigram index
    model.setProjectScope(false);

    List<UsageInfo> usages = new ArrayList<>();
    CommonProcessors.CollectProcessor<UsageInfo> consumer = new CommonProcessors.CollectProcessor<>(usages);
    FindUsagesProcessPresentation presentation = FindInProjectUtil.setupProcessPresentation(getProject(), false, FindInProjectUtil.setupViewPresentation(false, model));
    FindInProjectUtil.findUsages(model, getProject(), consumer, presentation);

    assertEquals(3, usages.size());
  }

  public void testFindInPathInLibrarySourceDirShouldSearchJustInThisDirectoryOnly() throws Exception {
    FindModel model = new FindModel();
    final PsiClass aClass = myJavaFacade.findClass("x.X");
    assertNotNull(aClass);
    String classDirPath = aClass.getContainingFile().getContainingDirectory().getVirtualFile().getPath();
    String sourceDirPath = ((PsiFile)aClass.getContainingFile().getNavigationElement()).getContainingDirectory().getVirtualFile().getPath();
    assertFalse(classDirPath.equals(sourceDirPath));
    model.setDirectoryName(sourceDirPath);
    model.setCaseSensitive(true);
    model.setCustomScope(false);
    model.setStringToFind("xxx");
    model.setProjectScope(false);

    List<UsageInfo> usages = new ArrayList<>();
    CommonProcessors.CollectProcessor<UsageInfo> consumer = new CommonProcessors.CollectProcessor<>(usages);
    FindUsagesProcessPresentation presentation = FindInProjectUtil.setupProcessPresentation(getProject(), false, FindInProjectUtil.setupViewPresentation(false, model));
    FindInProjectUtil.findUsages(model, getProject(), consumer, presentation);

    UsageInfo info = assertOneElement(usages);
    assertEquals("X.java", info.getFile().getName());
  }

  public void testInnerSourceRoot() throws Exception {
    doTest("ProjectClass2", new String[]{"ProjectClass2.java"}, GlobalSearchScope.projectScope(myProject));
  }

  private void doTest(String classNameToSearch, String[] expectedFileNames, SearchScope scope) throws Exception {
    final PsiClass aClass = myJavaFacade.findClass(classNameToSearch);
    assertNotNull(aClass);

    PsiReference[] refs = ReferencesSearch.search(aClass, scope, false).toArray(PsiReference.EMPTY_ARRAY);

    ArrayList<PsiFile> files = new ArrayList<>();
    for (PsiReference ref : refs) {
      PsiFile file = ref.getElement().getContainingFile();
      if (!files.contains(file)) {
        files.add(file);
      }
    }

    assertEquals("files count", expectedFileNames.length, files.size());

    Collections.sort(files, (o1, o2) -> o1.getName().compareTo(o2.getName()));
    Arrays.sort(expectedFileNames);

    for (int i = 0; i < expectedFileNames.length; i++) {
      String name = expectedFileNames[i];
      PsiFile file = files.get(i);
      assertEquals(name, file.getName());
    }
  }
}

@@ -4116,32 +4116,61 @@
 eInfo%3E usages = 
+Collections.synchronizedList(
 new ArrayList%3C%3E(
@@ -4162,32 +4162,33 @@
 ew ArrayList%3C%3E()
+)
 ;%0A    CommonProc
@@ -5081,32 +5081,61 @@
 eInfo%3E usages = 
+Collections.synchronizedList(
 new ArrayList%3C%3E(
@@ -5127,32 +5127,33 @@
 ew ArrayList%3C%3E()
+)
 ;%0A    CommonProc

make SearchInLibsTest tolerate multithreaded search