/*
 * 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 org.jetbrains.plugins.groovy.findUsages;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightMemberReference;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.util.Processor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;

/**
 * @author Maxim.Medvedev
 *         Date: May 2, 2009 3:48:53 PM
 */
public class GroovyConstructorUsagesSearchHelper {
  private GroovyConstructorUsagesSearchHelper() {
  }

  public static boolean execute(final PsiMethod constructor, final SearchScope searchScope, final Processor<PsiReference> consumer) {
    if (!constructor.isConstructor()) return true;

    final PsiClass clazz = constructor.getContainingClass();
    if (clazz == null) return true;


    ReferencesSearch.search(clazz, searchScope, true).forEach(new Processor<PsiReference>() {
      public boolean process(PsiReference ref) {
        final PsiElement element = ref.getElement();
        if (element instanceof GrCodeReferenceElement && element.getParent() instanceof GrNewExpression) {
          final GrNewExpression newExpression = (GrNewExpression)element.getParent();
          final PsiMethod resolvedConstructor = newExpression.resolveConstructor();
          final PsiManager manager = constructor.getManager();
          if (manager.areElementsEquivalent(resolvedConstructor, constructor) && !consumer.process(ref)) return false;
        }
        return true;
      }
    });

    //this()
    if (clazz instanceof GrTypeDefinition) {
      if (!ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
        public Boolean compute() {
          return processClassConstructors(clazz, constructor, consumer, true);
        }
      }).booleanValue()) {
        return false;
      }
    }
    //super  : does not work now, need to invent a way for it to work without repository
    if (!DirectClassInheritorsSearch.search(clazz, searchScope).forEach(new Processor<PsiClass>() {
      public boolean process(PsiClass inheritor) {
        if (inheritor instanceof GrTypeDefinition) {
          if (!processClassConstructors(inheritor, constructor, consumer, false)) return false;
        }
        return true;
      }
    })) {
      return false;
    }

    return true;
  }

  private static boolean processClassConstructors(PsiClass clazz,
                                                  PsiMethod searchedConstructor,
                                                  Processor<PsiReference> consumer,
                                                  boolean processThisRefs) {
    final PsiMethod[] constructors = clazz.getConstructors();
    if (constructors.length == 0) {
      processImplicitConstructorCall(clazz, consumer, searchedConstructor);
    }
    for (PsiMethod constructor : constructors) {
      final GrOpenBlock block = ((GrMethod)constructor).getBlock();
      if (block != null) {
        final GrStatement[] statements = block.getStatements();
        if (statements.length > 0 && statements[0] instanceof GrConstructorInvocation) {
          final GrConstructorInvocation invocation = (GrConstructorInvocation)statements[0];
          if (invocation.isThisCall() == processThisRefs &&
              invocation.getManager().areElementsEquivalent(invocation.resolveConstructor(), searchedConstructor) &&
              !consumer.process(invocation)) {
            return false;
          }
        }
        else {
          processImplicitConstructorCall(constructor, consumer, searchedConstructor);
        }
      }
    }
    return true;
  }

  private static void processImplicitConstructorCall(final PsiMember usage,
                                                     final Processor<PsiReference> processor,
                                                     final PsiMethod constructor) {
    if (constructor instanceof GrMethod) {
      GrParameter[] grParameters = (GrParameter[])constructor.getParameterList().getParameters();
      if (grParameters.length > 0 && !grParameters[0].isOptional()) return;
    }
    else if (constructor.getParameterList().getParameters().length > 0) return;


    PsiManager manager = constructor.getManager();
    if (manager.areElementsEquivalent(usage, constructor) || manager.areElementsEquivalent(constructor.getContainingClass(), usage.getContainingClass())) return;
    processor.process(new LightMemberReference(manager, usage, PsiSubstitutor.EMPTY) {
      public PsiElement getElement() {
        return usage;
      }

      public TextRange getRangeInElement() {
        if (usage instanceof PsiClass) {
          PsiIdentifier identifier = ((PsiClass)usage).getNameIdentifier();
          if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
        }
        else if (usage instanceof PsiMethod) {
          PsiIdentifier identifier = ((PsiMethod)usage).getNameIdentifier();
          if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
        }
        return super.getRangeInElement();
      }
    });

  }
}

@@ -2994,148 +2994,13 @@
 f (!
-ApplicationManager.getApplication().runReadAction(new Computable%3CBoolean%3E() %7B%0A        public Boolean compute() %7B%0A          return processCla
+proce
 ssCo
@@ -3010,23 +3010,16 @@
 ructors(
-clazz, 
 construc
@@ -3037,47 +3037,19 @@
 er, 
-true);%0A        %7D%0A      %7D).booleanVal
+clazz, tr
 ue
-(
 )) %7B
@@ -3393,37 +3393,32 @@
    if (!processC
-lassC
 onstructors(inhe
@@ -3417,19 +3417,8 @@
 ors(
-inheritor, 
 cons
@@ -3435,16 +3435,27 @@
 onsumer,
+ inheritor,
  false))
@@ -3559,32 +3559,477 @@
 turn true;%0A  %7D%0A%0A
+  private static boolean processConstructors(final PsiMethod constructor, final Processor%3CPsiReference%3E consumer, final PsiClass clazz,%0A                                             final boolean processThisRefs) %7B%0A    return ApplicationManager.getApplication().runReadAction(new Computable%3CBoolean%3E() %7B%0A      public Boolean compute() %7B%0A        return processClassConstructors(clazz, constructor, consumer, processThisRefs);%0A      %7D%0A    %7D);%0A  %7D%0A%0A
   private static

read access in groovy constructor usage searcher