/*
 * Copyright 2000-2009 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.find.findUsages;

import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceService;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;

/**
 * @author peter
 * @see com.intellij.find.findUsages.FindUsagesHandlerFactory
 */
public abstract class FindUsagesHandler {
  // return this handler if you want to cancel the search
  public static final FindUsagesHandler NULL_HANDLER = new FindUsagesHandler(PsiUtilCore.NULL_PSI_ELEMENT){};

  private final PsiElement myPsiElement;

  protected FindUsagesHandler(@NotNull PsiElement psiElement) {
    myPsiElement = psiElement;
  }

  @NotNull
  public AbstractFindUsagesDialog getFindUsagesDialog(boolean isSingleFile, boolean toShowInNewTab, boolean mustOpenInNewTab) {
    return new CommonFindUsagesDialog(myPsiElement, getProject(), getFindUsagesOptions(DataManager.getInstance().getDataContext()),
                                      toShowInNewTab, mustOpenInNewTab, isSingleFile, this);
  }

  @NotNull
  public final PsiElement getPsiElement() {
    return myPsiElement;
  }

  @NotNull
  public final Project getProject() {
    return myPsiElement.getProject();
  }

  @NotNull
  public PsiElement[] getPrimaryElements() {
    return new PsiElement[]{myPsiElement};
  }

  @NotNull
  public PsiElement[] getSecondaryElements() {
    return PsiElement.EMPTY_ARRAY;
  }

  public static FindUsagesOptions createFindUsagesOptions(final Project project, @Nullable final DataContext dataContext) {
    FindUsagesOptions findUsagesOptions = new FindUsagesOptions(project, dataContext);
    findUsagesOptions.isUsages = true;
    findUsagesOptions.isSearchForTextOccurrences = true;
    return findUsagesOptions;
  }

  @NotNull
  public FindUsagesOptions getFindUsagesOptions() {
    return getFindUsagesOptions(null);
  }
  @NotNull
  public FindUsagesOptions getFindUsagesOptions(@Nullable final DataContext dataContext) {
    FindUsagesOptions options = createFindUsagesOptions(getProject(), dataContext);
    options.isSearchForTextOccurrences &= isSearchForTextOccurencesAvailable(getPsiElement(), false);
    return options;
  }

  public void processElementUsages(@NotNull final PsiElement element, @NotNull final Processor<UsageInfo> processor, @NotNull final FindUsagesOptions options) {
    final ReadActionProcessor<PsiReference> refProcessor = new ReadActionProcessor<PsiReference>() {
      @Override
      public boolean processInReadAction(final PsiReference ref) {
        TextRange rangeInElement = ref.getRangeInElement();
        return processor.process(new UsageInfo(ref.getElement(), rangeInElement.getStartOffset(), rangeInElement.getEndOffset(), false));
      }
    };

    final SearchScope scope = options.searchScope;

    final boolean searchText = options.isSearchForTextOccurrences && scope instanceof GlobalSearchScope;

    if (options.isUsages) {
      ReferencesSearch.search(new ReferencesSearch.SearchParameters(element, scope, false, options.fastTrack)).forEach(refProcessor);
    }

    if (searchText) {
      if (options.fastTrack != null) {
        options.fastTrack.searchCustom(new Processor<Processor<PsiReference>>() {
          @Override
          public boolean process(Processor<PsiReference> consumer) {
            processUsagesInText(element, processor, (GlobalSearchScope)scope);
            return true;
          }
        });
      } else {
        processUsagesInText(element, processor, (GlobalSearchScope)scope);
      }
    }
  }

  public void processUsagesInText(@NotNull final PsiElement element,
                                  @NotNull Processor<UsageInfo> processor,
                                  @NotNull GlobalSearchScope searchScope) {
    Collection<String> stringToSearch = getStringsToSearch(element);
    if (stringToSearch == null) return;
    final TextRange elementTextRange = ApplicationManager.getApplication().runReadAction(new Computable<TextRange>() {
      @Override
      public TextRange compute() {
        if (!element.isValid()) return null;
        return element.getTextRange();
      }
    });
    TextOccurrencesUtil.UsageInfoFactory factory = new TextOccurrencesUtil.UsageInfoFactory() {
      @Override
      public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) {
        if (elementTextRange != null
            && usage.getContainingFile() == element.getContainingFile()
            && elementTextRange.contains(startOffset)
            && elementTextRange.contains(endOffset)) {
          return null;
        }

        PsiReference someReference = usage.findReferenceAt(startOffset);
        if (someReference != null) {
          PsiElement refElement = someReference.getElement();
          for (PsiReference ref : PsiReferenceService.getService().getReferences(refElement, new PsiReferenceService.Hints(element, null))) {
            if (element.getManager().areElementsEquivalent(ref.resolve(), element)) {
              TextRange range = ref.getRangeInElement().shiftRight(refElement.getTextRange().getStartOffset() - usage.getTextRange().getStartOffset());
              return new UsageInfo(usage, range.getStartOffset(), range.getEndOffset(), true);
            }
          }

        }

        return new UsageInfo(usage, startOffset, endOffset, true);
      }
    };
    for (String s : stringToSearch) {
      TextOccurrencesUtil.processTextOccurences(element, s, searchScope, processor, factory);
    }
  }

  @Nullable
  protected Collection<String> getStringsToSearch(final PsiElement element) {
    if (element instanceof PsiNamedElement) {
      final AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
      try {
        return ContainerUtil.createMaybeSingletonList(((PsiNamedElement)element).getName());
      }
      finally {
        accessToken.finish();
      }
    }

    return Collections.singleton(element.getText());
  }

  protected boolean isSearchForTextOccurencesAvailable(PsiElement psiElement, boolean isSingleFile) {
    return false;
  }

  public Collection<PsiReference> findReferencesToHighlight(PsiElement target, SearchScope searchScope) {
    return ReferencesSearch.search(target, searchScope, false).findAll();
  }
}

@@ -976,16 +976,24 @@
 pi.util.
+Nullable
 Computab
@@ -5289,16 +5289,188 @@
 Search =
+ ApplicationManager.getApplication().runReadAction(new NullableComputable%3CCollection%3CString%3E%3E() %7B%0A      @Override%0A      public Collection%3CString%3E compute() %7B%0A        return
  getStri
@@ -5487,24 +5487,40 @@
 h(element);%0A
+      %7D%0A    %7D);%0A
     if (stri
@@ -5640,16 +5640,24 @@
 ion(new 
+Nullable
 Computab

add read action (EA-37255)