package org.checkerframework.common.value;

import com.sun.source.tree.Tree;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import org.checkerframework.javacutil.TreeUtils;

/** Stores methods that have special handling in the value checker. */
class ValueMethodIdentifier {

  /** String.length() method. */
  private final ExecutableElement lengthMethod;

  /** Array.getLength() method. */
  private final ExecutableElement getLengthMethod;

  /** String.startsWith(String) method. */
  private final ExecutableElement startsWithMethod;

  /** String.endsWith(String) method. */
  private final ExecutableElement endsWithMethod;

  /** The {@code java.lang.Math#min()} methods. */
  private final List<ExecutableElement> mathMinMethods;

  /** The {@code java.lang.Math#max()} methods. */
  private final List<ExecutableElement> mathMaxMethods;

  /** Arrays.copyOf() methods. */
  private final List<ExecutableElement> copyOfMethods;

  /**
   * Initialize elements with methods that have special handling in the value checker.
   *
   * @param processingEnv the processing environment
   */
  public ValueMethodIdentifier(ProcessingEnvironment processingEnv) {
    lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv);
    getLengthMethod = TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv);
    startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv);
    endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv);
    mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv);
    mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv);
    copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv);
    copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv));
  }

  /**
   * Returns true iff the argument is an invocation of Math.min.
   *
   * @param methodTree a tree
   * @param processingEnv the processing environment
   * @return true iff the argument is an invocation of Math.min
   */
  public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) {
    return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv);
  }

  /**
   * Returns true iff the argument is an invocation of Math.max.
   *
   * @param methodTree a tree
   * @param processingEnv the processing environment
   * @return true iff the argument is an invocation of Math.max
   */
  public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) {
    return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv);
  }

  /**
   * Returns true if a tree is an invocation of the {@code String.length()} method.
   *
   * @param tree a tree
   * @param processingEnv the processing environment
   * @return true if a tree is an invocation of the {@code String.length()} method
   */
  public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) {
    return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv);
  }

  /**
   * Returns true if a tree is an invocation of the {@code Array.getLength()} method.
   *
   * @param tree tree to check
   * @param processingEnv the processing environment
   * @return true iff the argument is an invocation of {@code Array.getLength()} method
   */
  public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) {
    return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv);
  }

  /**
   * Returns true if a method is the {@code String.length()} method.
   *
   * @param method the element to check
   * @return true iff the argument methid is {@code String.length()} method
   */
  public boolean isStringLengthMethod(ExecutableElement method) {
    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
    return method.equals(lengthMethod);
  }

  /**
   * Returns true if a method is the {@code Array.getLength()} method.
   *
   * @param method the element to check
   * @return true iff the argument method is {@code Array.getLength()} method
   */
  public boolean isArrayGetLengthMethod(ExecutableElement method) {
    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
    return method.equals(getLengthMethod);
  }

  /**
   * Returns true if a method is the {@code String.startsWith(String)} method.
   *
   * @param method the element to check
   * @return true iff the argument method is {@code String.startsWith(String)} method
   */
  public boolean isStartsWithMethod(ExecutableElement method) {
    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
    return method.equals(startsWithMethod);
  }

  /**
   * Returns true if a method is the {@code String.endsWith(String)} method.
   *
   * @param method a method
   * @return true if the method is {@code String.endsWith(String)}
   */
  public boolean isEndsWithMethod(ExecutableElement method) {
    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
    return method.equals(endsWithMethod);
  }

  /**
   * Returns true if a tree is an invocation of the {@code Arrays.copyOf()} method.
   *
   * @param tree tree to check
   * @param processingEnv the processing environment
   * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method
   */
  public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) {
    return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv);
  }
}
