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 java.util.List;
import javax.lang.model.element.TypeElement;
import org.checkerframework.framework.type.AnnotatedTypeMirror;

/**
 * When discovering supertypes of an AnnotatedTypeMirror we want to annotate each supertype with the
 * annotations of the subtypes class declaration. This class provides static methods to do this for
 * a list of supertypes. An instance of this class handles ONE supertype.
 */
public class SuperTypeApplier extends IndexedElementAnnotationApplier {

    /**
     * Annotates each supertype with annotations from subtypeElement's extends/implements clauses.
     *
     * @param supertypes supertypes to annotate
     * @param subtypeElement element that may have annotations to apply to supertypes
     */
    public static void annotateSupers(
            List<AnnotatedTypeMirror.AnnotatedDeclaredType> supertypes,
            TypeElement subtypeElement) {
        for (int i = 0; i < supertypes.size(); i++) {
            final AnnotatedTypeMirror supertype = supertypes.get(i);
            // Offset i by -1 since typeIndex should start from -1.
            // -1 represents the (implicit) extends clause class.
            // 0 and greater represent the implements clause interfaces.
            // For details see the JSR 308 specification:
            // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends
            final int typeIndex = i - 1;
            new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply();
        }
    }

    private final Symbol.ClassSymbol subclassSymbol;

    /**
     * The type_index of the supertype being annotated.
     *
     * <p>Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as
     * follows:
     *
     * <p>If subtypeElement represents a class and not an interface:
     *
     * <p>then the first member of supertypes represents the object and the relevant type_index =
     * -1; interface indices are offset by 1.
     *
     * <p>else all members of supertypes represent interfaces and their indices == their index in
     * the supertypes list
     */
    private final int index;

    /**
     * Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers it
     * is intended to be used for annotate super types via the static annotateSuper method, hence
     * the private constructor
     */
    SuperTypeApplier(
            final AnnotatedTypeMirror supertype,
            final TypeElement subclassElement,
            final int index) {
        super(supertype, subclassElement);
        this.subclassSymbol = (Symbol.ClassSymbol) subclassElement;
        this.index = index;
    }

    /** @return the type_index that should represent supertype */
    @Override
    public int getElementIndex() {
        return index;
    }

    /** @return the type_index of anno's TypeAnnotationPosition */
    @Override
    public int getTypeCompoundIndex(Attribute.TypeCompound anno) {
        return anno.getPosition().type_index;
    }

    /** @return TargetType.CLASS_EXTENDS */
    @Override
    protected TargetType[] annotatedTargets() {
        return new TargetType[] {TargetType.CLASS_EXTENDS};
    }

    /** @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND */
    @Override
    protected TargetType[] validTargets() {
        return new TargetType[] {
            TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND
        };
    }

    /** @return the TypeCompounds (annotations) of the subclass */
    @Override
    protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
        return subclassSymbol.getRawTypeAttributes();
    }

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

    @Override
    protected boolean isAccepted() {
        return true;
    }
}
