package org.checkerframework.framework.type;

import java.lang.annotation.Annotation;
import java.util.Collection;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.util.QualifierKind;
import org.checkerframework.framework.util.QualifierKindHierarchy;

/**
 * A {@link org.checkerframework.framework.type.QualifierHierarchy} where qualifiers may be
 * represented by annotations with elements, but most of the qualifiers do not have elements. In
 * contrast to {@link org.checkerframework.framework.type.ElementQualifierHierarchy}, this class
 * partially implements {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror,
 * TypeMirror)}, {@link #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror,
 * TypeMirror)}, and {@link #greatestLowerBoundShallow(AnnotationMirror, TypeMirror,
 * AnnotationMirror, TypeMirror)} and calls *WithElements when the result cannot be computed from
 * the meta-annotations {@link org.checkerframework.framework.qual.SubtypeOf}.
 *
 * <p>Subclasses must implement the following methods when annotations have elements:
 *
 * <ul>
 *   <li>{@link #isSubtypeWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
 *       QualifierKind)}
 *   <li>{@link #leastUpperBoundWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
 *       QualifierKind,QualifierKind)}
 *   <li>{@link #greatestLowerBoundWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
 *       QualifierKind,QualifierKind)}
 * </ul>
 *
 * <p>MostlyNoElementQualifierHierarchy uses a {@link QualifierKindHierarchy} to model the
 * relationships between qualifiers. Subclasses can override {@link
 * #createQualifierKindHierarchy(Collection)} to return a subclass of QualifierKindHierarchy.
 */
@AnnotatedFor("nullness")
public abstract class MostlyNoElementQualifierHierarchy extends ElementQualifierHierarchy {

  /**
   * Creates a MostlyNoElementQualifierHierarchy from the given classes.
   *
   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
   * @param elements element utils
   * @param atypeFactory the associated type factory
   */
  protected MostlyNoElementQualifierHierarchy(
      Collection<Class<? extends Annotation>> qualifierClasses,
      Elements elements,
      GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
    super(qualifierClasses, elements, atypeFactory);
  }

  @Override
  public final boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) {
    QualifierKind subKind = getQualifierKind(subAnno);
    QualifierKind superKind = getQualifierKind(superAnno);
    if (subKind.isSubtypeOf(superKind)) {
      if (superKind.hasElements() && subKind.hasElements()) {
        return isSubtypeWithElements(subAnno, subKind, superAnno, superKind);
      } else {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and
   * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of
   * {@code superKind}.
   *
   * @param subAnno possible subtype annotation; has elements
   * @param subKind the QualifierKind of {@code subAnno}
   * @param superAnno possible super annotation; has elements
   * @param superKind the QualifierKind of {@code superAnno}
   * @return true if {@code subAnno} is a subtype of {@code superAnno}
   */
  protected abstract boolean isSubtypeWithElements(
      AnnotationMirror subAnno,
      QualifierKind subKind,
      AnnotationMirror superAnno,
      QualifierKind superKind);

  @Override
  public final @Nullable AnnotationMirror leastUpperBoundQualifiers(
      AnnotationMirror a1, AnnotationMirror a2) {
    QualifierKind qual1 = getQualifierKind(a1);
    QualifierKind qual2 = getQualifierKind(a2);
    QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2);
    if (lub == null) {
      // Qualifiers are not in the same hierarchy.
      return null;
    }
    if (lub.hasElements()) {
      return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub);
    }
    return kindToElementlessQualifier.get(lub);
  }

  /**
   * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code
   * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub of
   * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link
   * #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns
   * the correct {@code AnnotationMirror} without calling this method.
   *
   * @param a1 first annotation
   * @param qualifierKind1 QualifierKind for {@code a1}
   * @param a2 second annotation
   * @param qualifierKind2 QualifierKind for {@code a2}
   * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2}
   * @return the least upper bound of {@code a1} and {@code a2}
   */
  protected abstract AnnotationMirror leastUpperBoundWithElements(
      AnnotationMirror a1,
      QualifierKind qualifierKind1,
      AnnotationMirror a2,
      QualifierKind qualifierKind2,
      QualifierKind lubKind);

  @Override
  public final @Nullable AnnotationMirror greatestLowerBoundQualifiers(
      AnnotationMirror a1, AnnotationMirror a2) {
    QualifierKind qual1 = getQualifierKind(a1);
    QualifierKind qual2 = getQualifierKind(a2);
    QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2);
    if (glb == null) {
      // Qualifiers are not in the same hierarchy.
      return null;
    }
    if (glb.hasElements()) {
      return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb);
    }
    return kindToElementlessQualifier.get(glb);
  }

  /**
   * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of {@code
   * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the glb of
   * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link
   * #greatestLowerBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns
   * the correct {@code AnnotationMirror} without calling this method.
   *
   * @param a1 first annotation
   * @param qualifierKind1 QualifierKind for {@code a1}
   * @param a2 second annotation
   * @param qualifierKind2 QualifierKind for {@code a2}
   * @return the greatest lower bound between {@code a1} and {@code a2}
   */
  protected abstract AnnotationMirror greatestLowerBoundWithElements(
      AnnotationMirror a1,
      QualifierKind qualifierKind1,
      AnnotationMirror a2,
      QualifierKind qualifierKind2,
      QualifierKind glbKind);
}
