package org.checkerframework.framework.util.element;

import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.TargetType;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
import org.checkerframework.javacutil.BugInCF;

/** Applies the annotations present for a method type parameter onto an AnnotatedTypeVariable. */
public class MethodTypeParamApplier extends TypeParamElementAnnotationApplier {

  /** Apply annotations from {@code element} to {@code type}. */
  public static void apply(
      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory)
      throws UnexpectedAnnotationLocationException {
    new MethodTypeParamApplier(type, element, typeFactory).extractAndApply();
  }

  /**
   * Returns true if element represents a type parameter for a method.
   *
   * @param type ignored
   * @param element the element that might be a type parameter for a method
   * @return true if element represents a type parameter for a method
   */
  public static boolean accepts(AnnotatedTypeMirror type, Element element) {
    return element.getKind() == ElementKind.TYPE_PARAMETER
        && element.getEnclosingElement() instanceof Symbol.MethodSymbol;
  }

  private final Symbol.MethodSymbol enclosingMethod;

  MethodTypeParamApplier(
      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory) {
    super(type, element, typeFactory);

    if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) {
      throw new BugInCF(
          "TypeParameter not enclosed by method?  Type( "
              + type
              + " ) "
              + "Element ( "
              + element
              + " ) ");
    }

    enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement();
  }

  /**
   * Returns TargetType.METHOD_TYPE_PARAMETER.
   *
   * @return TargetType.METHOD_TYPE_PARAMETER
   */
  @Override
  protected TargetType lowerBoundTarget() {
    return TargetType.METHOD_TYPE_PARAMETER;
  }

  /**
   * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND.
   *
   * @return TargetType.METHOD_TYPE_PARAMETER_BOUND
   */
  @Override
  protected TargetType upperBoundTarget() {
    return TargetType.METHOD_TYPE_PARAMETER_BOUND;
  }

  /**
   * Returns the index of element in the type parameter list of its enclosing method.
   *
   * @return the index of element in the type parameter list of its enclosing method
   */
  @Override
  public int getElementIndex() {
    return enclosingMethod.getTypeParameters().indexOf(element);
  }

  /** The valid targets. */
  private static final TargetType[] validTargets =
      new TargetType[] {
        TargetType.METHOD_RETURN,
        TargetType.METHOD_FORMAL_PARAMETER,
        TargetType.METHOD_RECEIVER,
        TargetType.THROWS,
        TargetType.LOCAL_VARIABLE,
        TargetType.RESOURCE_VARIABLE,
        TargetType.EXCEPTION_PARAMETER,
        TargetType.NEW,
        TargetType.CAST,
        TargetType.INSTANCEOF,
        TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
        TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
        TargetType.METHOD_REFERENCE,
        TargetType.CONSTRUCTOR_REFERENCE,
        TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
        TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
        // TODO: from generic anonymous classes; remove when
        // we can depend on only seeing classfiles that were
        // generated by a javac that contains a fix for:
        // https://bugs.openjdk.org/browse/JDK-8198945
        TargetType.CLASS_EXTENDS,
        // TODO: Test case from Issue 3277 produces invalid position.
        // Ignore until this javac bug is fixed:
        // https://bugs.openjdk.org/browse/JDK-8233945
        TargetType.UNKNOWN
      };

  @Override
  protected TargetType[] validTargets() {
    return validTargets;
  }

  /**
   * Returns the TypeCompounds (annotations) of the declaring element.
   *
   * @return the TypeCompounds (annotations) of the declaring element
   */
  @Override
  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
    return enclosingMethod.getRawTypeAttributes();
  }

  @Override
  protected boolean isAccepted() {
    return accepts(type, element);
  }
}
