package org.checkerframework.framework.util;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.type.ElementQualifierHierarchy;
import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
import org.checkerframework.framework.type.NoElementQualifierHierarchy;
import org.checkerframework.javacutil.TypeSystemError;

/**
 * This interface holds information about the subtyping relationships between kinds of qualifiers. A
 * "kind" of qualifier is its annotation class and is represented by the {@link QualifierKind}
 * class. If a type system has more than one hierarchy, information about all hierarchies is stored
 * in this class.
 *
 * <p>The qualifier kind subtyping relationship may be an over-approximation of the qualifier
 * subtyping relationship, for qualifiers that have elements/arguments. In other words, if a
 * qualifier kind is a subtype of another qualifier kind, then qualifiers of those kinds may or may
 * not be subtypes, depending on the values of any elements of the qualifiers. If qualifier kinds
 * are not subtypes, then qualifiers of those kinds are never subtypes.
 *
 * <p>This interface is used by {@link NoElementQualifierHierarchy} and {@link
 * ElementQualifierHierarchy} (but <em>not</em> {@link MostlyNoElementQualifierHierarchy}) to
 * implement methods that compare {@link javax.lang.model.element.AnnotationMirror}s, such as {@link
 * org.checkerframework.framework.type.QualifierHierarchy#isSubtypeShallow(AnnotationMirror,
 * TypeMirror, AnnotationMirror, TypeMirror)}.
 *
 * @see DefaultQualifierKindHierarchy
 * @see org.checkerframework.framework.util.DefaultQualifierKindHierarchy.DefaultQualifierKind
 */
@AnnotatedFor("nullness")
public interface QualifierKindHierarchy {

  /**
   * Returns the qualifier kinds that are the top qualifier in their hierarchies.
   *
   * @return the qualifier kinds that are the top qualifier in their hierarchies
   */
  Set<? extends QualifierKind> getTops();

  /**
   * Returns the qualifier kinds that are the bottom qualifier in their hierarchies.
   *
   * @return the qualifier kinds that are the bottom qualifier in their hierarchies
   */
  Set<? extends QualifierKind> getBottoms();

  /**
   * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
   * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does).
   *
   * @param q1 a qualifier kind
   * @param q2 a qualifier kind
   * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
   *     kinds are not in the same hierarchy
   */
  @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2);

  /**
   * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
   * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does).
   *
   * @param q1 a qualifier kind
   * @param q2 a qualifier kind
   * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
   *     kinds are not in the same hierarchy
   */
  @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2);

  /**
   * Returns a list of all {@link QualifierKind}s sorted in ascending order.
   *
   * @return a list of all {@link QualifierKind}s sorted in ascending order
   */
  List<? extends QualifierKind> allQualifierKinds();

  /**
   * Returns the {@link QualifierKind} for the given annotation class name. Throws an exception if
   * one does not exist.
   *
   * @param name canonical name of an annotation class
   * @return the {@link QualifierKind} for the given annotation class name
   */
  QualifierKind getQualifierKind(@CanonicalName String name);

  /**
   * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code clazz}
   * is anonymous or otherwise does not have a name.
   *
   * @param clazz annotation class
   * @return the canonical name of {@code clazz}
   */
  static @CanonicalName String annotationClassName(Class<? extends Annotation> clazz) {
    String name = clazz.getCanonicalName();
    if (name == null) {
      throw new TypeSystemError("Qualifier classes must not be anonymous.");
    }
    return name;
  }
}
