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 class type parameter onto an AnnotatedTypeVariable. See
 * {@link TypeParamElementAnnotationApplier} for details.
 */
public class ClassTypeParamApplier extends TypeParamElementAnnotationApplier {

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

    /** @return true if element represents a type parameter for a class */
    public static boolean accepts(final AnnotatedTypeMirror type, final Element element) {
        return element.getKind() == ElementKind.TYPE_PARAMETER
                && element.getEnclosingElement() instanceof Symbol.ClassSymbol;
    }

    /** The class that holds the type parameter element. */
    private final Symbol.ClassSymbol enclosingClass;

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

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

        enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement();
    }

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

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

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

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

    /** @return the raw type attributes of the enclosing class */
    @Override
    protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
        return enclosingClass.getRawTypeAttributes();
    }

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