// Copyright 2000-2018 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.codeInspection;

import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiParameter;
import com.siyeh.HardcodedMethodConstants;
import com.siyeh.ig.psiutils.MethodUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;

//TODO it is not intuitive that for Kotlin properties user should use @get:NonNls

/**
 * @see org.jetbrains.annotations.NonNls
 */
public final class NonNlsUastUtil {
  //TODO caching? (maybe at least for packages)

  /**
   * @return <code>true</code> if passed element is annotated with {@link org.jetbrains.annotations.NonNls}; <code>false</code> otherwise.
   */
  public static boolean isNonNlsAnnotated(@Nullable UElement element) {
    return element instanceof UAnnotated && ((UAnnotated)element).findAnnotation(AnnotationUtil.NON_NLS) != null;
  }

  /**
   * @return <code>true</code> if expression qualifier (part before <code>::</code>) should be considered non-localizable;
   * <code>false</code> otherwise.
   */
  public static boolean isCallableReferenceExpressionWithNonNlsQualifier(@Nullable UCallableReferenceExpression expression) {
    if (expression == null) return false;
    return false; //TODO implement
  }

  /**
   * @return <code>true</code> if expression receiver should be considered non-localizable; <code>false</code> otherwise.
   */
  public static boolean isCallExpressionWithNonNlsReceiver(@Nullable UCallExpression expression) {
    if (expression == null) return false;
    UExpression receiver = expression.getReceiver();
    if (receiver == null) return false;

    if (receiver instanceof ULiteralExpression) {
      return isNonNlsStringLiteral((ULiteralExpression)receiver);
    }
    if (receiver instanceof USimpleNameReferenceExpression) { // reference to variable/field/parameter
      return isReferenceToNonNlsElement((USimpleNameReferenceExpression)receiver);
    }
    if (receiver instanceof UCallExpression || receiver instanceof UQualifiedReferenceExpression) { // call chain
      PsiElement resolved = ((UResolvable)receiver).resolve();
      return resolved != null && isNonNlsAnnotatedPsi(resolved);
    }

    UElement expressionParent = expression.getUastParent();
    if (expressionParent instanceof UVariable && isNonNlsAnnotated(expressionParent)) { // initialized field/variable
      return true;
    }
    //TODO other cases?
    return false;
  }

  /**
   * @return <code>true</code> if passed string literal should be considered non-localizable; <code>false</code> otherwise.
   */
  public static boolean isNonNlsStringLiteral(@Nullable ULiteralExpression expression) {
    if (expression == null) return false;
    if (!UastLiteralUtils.isStringLiteral(expression)) return false;
    if (isPlacedInNonNlsClass(expression) || isPlacedInNonNlsPackage(expression)) return true;

    UElement parent = UastUtils.getParentOfType(expression,
                                                true,
                                                UExpressionList.class,
                                                UVariable.class,
                                                UReturnExpression.class,
                                                UCallExpression.class,
                                                UBinaryExpression.class);

    if (parent instanceof UField || parent instanceof ULocalVariable || parent instanceof UParameter) {
      return isNonNlsAnnotated(parent);
    }
    if (parent instanceof UReturnExpression) {
      return isReturnExpressionInNonNlsMethod((UReturnExpression)parent);
    }
    if (parent instanceof UCallExpression) {
      return isNonNlsArgument(expression, (UCallExpression)parent) ||
             isCallExpressionWithNonNlsReceiver((UCallExpression)parent);
    }

    while (parent instanceof UBinaryExpression) {
      if (!isAssignmentExpression((UBinaryExpression)parent)) {
        // go upper to find assignment expression (or field/variable declaration)
        parent = UastUtils.getParentOfType(parent, true, UBinaryExpression.class, UVariable.class);
        if (parent instanceof UVariable) {
          return isNonNlsAnnotated(parent);
        }
        continue;
      }

      UExpression leftOperand = ((UBinaryExpression)parent).getLeftOperand();
      //TODO probably this doesn't cover all cases
      return leftOperand instanceof USimpleNameReferenceExpression &&
             isReferenceToNonNlsElement((USimpleNameReferenceExpression)leftOperand);
    }

    //TODO UExpressionList?

    return false;
  }


  private static boolean isPlacedInNonNlsClass(@NotNull UElement element) {
    UClass containingClass = UastUtils.getContainingUClass(element);
    if (containingClass == null) return false;
    if (isNonNlsAnnotated(containingClass)) return true;

    //TODO potential performance issue here, cache classes?
    UClass superClass = containingClass;
    while ((superClass = superClass.getSuperClass()) != null) {
      if (isNonNlsAnnotated(superClass)) return true;
    }
    return false;
  }

  private static boolean isPlacedInNonNlsPackage(@NotNull UElement element) {
    return false; //TODO implement
  }

  private static boolean isReferenceToNonNlsElement(@NotNull USimpleNameReferenceExpression expression) {
    PsiElement resolved = expression.resolve();
    if (resolved == null) return false;
    UElement resolvedUElement = UastContextKt.toUElement(resolved);
    return isNonNlsAnnotated(resolvedUElement); //TODO does this cover all the cases?
  }

  private static boolean isReturnExpressionInNonNlsMethod(@NotNull UReturnExpression expression) {
    //TODO possible bugs (lambdas? anonymous classes in @NonNls methods?)
    UElement method = UastUtils.getParentOfType(expression, UMethod.class);
    return isNonNlsAnnotated(method);
  }

  //TODO support named parameters
  private static boolean isNonNlsArgument(@NotNull ULiteralExpression argument, @NotNull UCallExpression callExpression) {
    PsiParameter parameter = UastUtils.getParameterForArgument(callExpression, argument);
    if (parameter == null) return false;
    if (isNonNlsAnnotatedPsi(parameter)) return true;

    // special handling for equals()
    if (!HardcodedMethodConstants.EQUALS.equals(callExpression.getMethodName())) return false;
    PsiMethod method = callExpression.resolve();
    if (!MethodUtils.isEquals(method)) return false;
    return isCallExpressionWithNonNlsReceiver(callExpression);
  }

  //TODO looks dirty, is there a better way to check this?  <--  see UElementAsPsiInspection
  private static boolean isAssignmentExpression(@NotNull UBinaryExpression expression) {
    UIdentifier operatorIdentifier = expression.getOperatorIdentifier();
    if (operatorIdentifier == null) return false;
    String operatorName = operatorIdentifier.getName();
    return operatorName.equals("=") || operatorName.equals("+=");
  }

  private static boolean isNonNlsAnnotatedPsi(@NotNull PsiElement element) {
    return element instanceof PsiModifierListOwner &&
           AnnotationUtil.findAnnotation((PsiModifierListOwner)element, AnnotationUtil.NON_NLS) != null;
  }
}

@@ -4133,32 +4133,44 @@
 f (!isAssignment
+OrComparison
 Expression((UBin
@@ -6755,101 +6755,8 @@
  %7D%0A%0A
-  //TODO looks dirty, is there a better way to check this?  %3C--  see UElementAsPsiInspection%0A
   pr
@@ -6788,16 +6788,28 @@
 signment
+OrComparison
 Expressi
@@ -6860,37 +6860,40 @@
     
-UIdentifier operatorIdentifie
+final UastBinaryOperator operato
 r = 
@@ -6918,26 +6918,16 @@
 ator
-Identifier
 ();%0A    
 if (
@@ -6926,20 +6926,23 @@
     
-if (
+return 
 operator
 Iden
@@ -6941,67 +6941,56 @@
 ator
-Identifier == null) return false;%0A    String o
+ instanceof UastBinaryOperator.AssignO
 perator
-Name =
+ %7C%7C
  ope
@@ -6998,94 +6998,57 @@
 ator
-Identifier.getName();%0A    return operatorName.equals(%22=%22) %7C%7C operatorName.equals(%22+=%22)
+ instanceof UastBinaryOperator.ComparisonOperator
 ;%0A  

check for binary-operator-based comparison