/*
 * Copyright 2000-2014 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.lang.resolve;

import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.extensions.GroovyMethodInfo;
import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentReferenceProvider;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConditionalExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;

public final class GroovyMethodArgumentReferenceContributor extends PsiReferenceContributor {

  @Override
  public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
    registrar.registerReferenceProvider(GroovyPatterns.stringLiteral(), new MyProvider());
  }

  private static class MyProvider extends PsiReferenceProvider {
    @Override
    public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
      GrExpression argument = (GrExpression)element;
      PsiElement parent = element.getParent();

      if (parent instanceof GrConditionalExpression) {
        // support case: foo(a > b ? "aaa" : "bbb")

        if (((GrConditionalExpression)parent).getCondition() == parent) return PsiReference.EMPTY_ARRAY;
        argument = (GrConditionalExpression)parent;
        parent = parent.getParent();
      }

      if (parent instanceof GrListOrMap) {
        // support case: foo(["aaa", "bbb"])
        if (!((GrListOrMap)parent).isMap()) {
          argument = (GrListOrMap)parent;
          parent = parent.getParent();
        }
      }

      if (parent instanceof GrNamedArgument) {
        return createReferencesForNamedArgument(element, (GrNamedArgument)parent, context);
      }

      if (parent instanceof GrArgumentList argumentList) {
        int index = argumentList.getExpressionArgumentIndex(argument);

        PsiElement call = argumentList.getParent();
        if (!(call instanceof GrMethodCall)) return PsiReference.EMPTY_ARRAY;

        return PsiReference.EMPTY_ARRAY;
      }

      return PsiReference.EMPTY_ARRAY;
    }

    private static PsiReference[] createReferencesForNamedArgument(@NotNull PsiElement element,
                                                                   GrNamedArgument namedArgument,
                                                                   @NotNull ProcessingContext context) {
      String labelName = namedArgument.getLabelName();
      if (labelName == null) return PsiReference.EMPTY_ARRAY;

      if (!GroovyMethodInfo.getAllSupportedNamedArguments().contains(labelName)) {
        // Optimization: avoid unnecessary resolve.
        return PsiReference.EMPTY_ARRAY;
      }

      PsiElement call = PsiUtil.getCallByNamedParameter(namedArgument);

      if (!(call instanceof GrMethodCall)) return PsiReference.EMPTY_ARRAY;

      GrExpression invokedExpression = ((GrMethodCall)call).getInvokedExpression();
      if (!(invokedExpression instanceof GrReferenceExpression)) return PsiReference.EMPTY_ARRAY;

      for (GroovyResolveResult result : ((GrReferenceExpression)invokedExpression).multiResolve(false)) {
        PsiElement eMethod = result.getElement();
        if (!(eMethod instanceof PsiMethod method)) continue;

        for (GroovyMethodInfo info : GroovyMethodInfo.getInfos(method)) {
          Object referenceProvider = info.getNamedArgReferenceProvider(labelName);
          if (referenceProvider != null) {
            PsiReference[] refs;

            if (referenceProvider instanceof GroovyNamedArgumentReferenceProvider) {
              refs = ((GroovyNamedArgumentReferenceProvider)referenceProvider).createRef(element, namedArgument, result, context);
            }
            else {
              refs = ((PsiReferenceProvider)referenceProvider).getReferencesByElement(element, context);
            }

            if (refs.length > 0) {
              return refs;
            }
          }
        }
      }

      return PsiReference.EMPTY_ARRAY;
    }

    //@NotNull
    //private PsiReference[] createReferencesForArgument(@NotNull PsiElement element,
    //                                                   int index,
    //                                                   @NotNull GrMethodCall methodCall) {
    //  GrExpression invokedExpression = methodCall.getInvokedExpression();
    //  if (!(invokedExpression instanceof GrReferenceExpression)) return PsiReference.EMPTY_ARRAY;
    //
    //  String methodName = ((GrReferenceExpression)invokedExpression).getReferenceName();
    //  if (methodName == null) return PsiReference.EMPTY_ARRAY;
    //
    //  for (String key : new String[] {methodName, null}) {
    //    for (GroovyResolveResult result : ((GrReferenceExpression)invokedExpression).multiResolve(false)) {
    //      PsiElement eMethod = result.getElement();
    //      if (eMethod instanceof PsiMethod) {
    //        PsiMethod method = (PsiMethod)eMethod;
    //
    //        //noinspection ConstantConditions
    //        if (key != null && !key.equals(method.getName())) continue;
    //
    //        for (Pair<Contributor.Provider, Condition<PsiMethod>> pair : list) {
    //          if (pair.second.value(method)) {
    //            PsiReference[] res;
    //            if (attrNameOrParameterIndex instanceof Integer) {
    //              res = pair.first.createRef(element, methodCall, (Integer)attrNameOrParameterIndex, result);
    //            }
    //            else {
    //              assert namedArgument != null;
    //              res = pair.first.createRef(element, namedArgument, result);
    //            }
    //
    //            if (res.length > 0) {
    //              return res;
    //            }
    //          }
    //        }
    //      }
    //    }
    //  }
    //
    //  return PsiReference.EMPTY_ARRAY;
    //}
  }

}
