package org.checkerframework.framework.util.element;

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

/**
 * Applies annotations to variable declaration (providing they are not the use of a TYPE_PARAMETER).
 */
public class VariableApplier extends TargetedElementAnnotationApplier {

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

  private static final ElementKind[] acceptedKinds = {
    ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER
  };

  /**
   * Returns true if this is a variable declaration including fields an enum constants.
   *
   * @param typeMirror ignored
   * @return true if this is a variable declaration including fields an enum constants
   */
  public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) {
    return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds)
        || element.getKind().isField();
  }

  private final Symbol.VarSymbol varSymbol;

  VariableApplier(AnnotatedTypeMirror type, Element element) {
    super(type, element);
    varSymbol = (Symbol.VarSymbol) element;

    if (type.getKind() == TypeKind.UNION && element.getKind() != ElementKind.EXCEPTION_PARAMETER) {
      throw new BugInCF(
          "Union types only allowed for exception parameters. "
              + "Type: "
              + type
              + " for element: "
              + element);
    }
    // TODO: need a way to split the union types into the right alternative
    // to use for the annotation. The exception_index is probably what we
    // need to look at, but it might not be set at this point.
  }

  /** The annotated targets. */
  private static final TargetType[] annotatedTargets =
      new TargetType[] {
        TargetType.LOCAL_VARIABLE,
        TargetType.RESOURCE_VARIABLE,
        TargetType.EXCEPTION_PARAMETER,
        TargetType.FIELD
      };

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

  /** The valid targets. */
  private static final TargetType[] validTargets =
      new TargetType[] {
        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,
        TargetType.METHOD_FORMAL_PARAMETER,
        TargetType.CLASS_EXTENDS
      };

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

  @Override
  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
    return varSymbol.getRawTypeAttributes();
  }

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

  @Override
  protected void handleTargeted(List<TypeCompound> targeted)
      throws UnexpectedAnnotationLocationException {
    ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted);
  }

  @Override
  public void extractAndApply() throws UnexpectedAnnotationLocationException {
    // Add declaration annotations to the local variable type
    ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
        type, varSymbol.getAnnotationMirrors());
    super.extractAndApply();
  }
}
