package org.checkerframework.checker.index.substringindex;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.index.IndexChecker;
import org.checkerframework.checker.index.OffsetDependentTypesHelper;
import org.checkerframework.checker.index.qual.SubstringIndexBottom;
import org.checkerframework.checker.index.qual.SubstringIndexFor;
import org.checkerframework.checker.index.qual.SubstringIndexUnknown;
import org.checkerframework.checker.index.upperbound.UBQualifier;
import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.type.ElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;

/**
 * Builds types with annotations from the Substring Index checker hierarchy, which contains
 * the @{@link SubstringIndexFor} annotation.
 */
public class SubstringIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {

  /** The top qualifier of the Substring Index hierarchy. */
  public final AnnotationMirror UNKNOWN =
      AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class);

  /** The bottom qualifier of the Substring Index hierarchy. */
  public final AnnotationMirror BOTTOM =
      AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class);

  /**
   * Create a new SubstringIndexAnnotatedTypeFactory.
   *
   * @param checker the associated checker
   */
  public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) {
    super(checker);

    this.postInit();
  }

  /**
   * Returns a mutable set of annotation classes that are supported by the Substring Index Checker.
   *
   * @return mutable set containing annotation classes from the Substring Index qualifier hierarchy
   */
  @Override
  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
    return new LinkedHashSet<>(
        Arrays.asList(
            SubstringIndexUnknown.class, SubstringIndexFor.class, SubstringIndexBottom.class));
  }

  @Override
  protected QualifierHierarchy createQualifierHierarchy() {
    return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
  }

  /**
   * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the
   * Substring Index Checker annotations.
   */
  @Override
  protected DependentTypesHelper createDependentTypesHelper() {
    return new OffsetDependentTypesHelper(this);
  }

  /**
   * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link
   * UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link BOTTOM} of type {@link
   * SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the subtyping
   * relation of {@link UBQualifier}.
   */
  private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy {

    /**
     * Creates a SubstringIndexQualifierHierarchy from the given classes.
     *
     * @param qualifierClasses classes of annotations that are the qualifiers
     * @param elements element utils
     */
    public SubstringIndexQualifierHierarchy(
        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
      super(qualifierClasses, elements, SubstringIndexAnnotatedTypeFactory.this);
    }

    @Override
    public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) {
      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
        return a2;
      }
      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
        return a1;
      }
      if (AnnotationUtils.areSame(a1, BOTTOM)) {
        return a1;
      }
      if (AnnotationUtils.areSame(a2, BOTTOM)) {
        return a2;
      }
      UBQualifier ubq1 =
          UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker());
      UBQualifier ubq2 =
          UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker());
      UBQualifier glb = ubq1.glb(ubq2);
      return convertUBQualifierToAnnotation(glb);
    }

    @Override
    public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) {
      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
        return a1;
      }
      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
        return a2;
      }
      if (AnnotationUtils.areSame(a1, BOTTOM)) {
        return a2;
      }
      if (AnnotationUtils.areSame(a2, BOTTOM)) {
        return a1;
      }
      UBQualifier ubq1 =
          UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker());
      UBQualifier ubq2 =
          UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker());
      UBQualifier lub = ubq1.lub(ubq2);
      return convertUBQualifierToAnnotation(lub);
    }

    @Override
    public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) {
      if (areSameByClass(superAnno, SubstringIndexUnknown.class)) {
        return true;
      }
      if (areSameByClass(subAnno, SubstringIndexBottom.class)) {
        return true;
      }
      if (areSameByClass(subAnno, SubstringIndexUnknown.class)) {
        return false;
      }
      if (areSameByClass(superAnno, SubstringIndexBottom.class)) {
        return false;
      }

      UBQualifier subtype =
          UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker.getUltimateParentChecker());
      UBQualifier supertype =
          UBQualifier.createUBQualifier(
              superAnno, (IndexChecker) checker.getUltimateParentChecker());
      return subtype.isSubtype(supertype);
    }
  }

  /**
   * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index
   * hierarchy.
   *
   * @param qualifier the {@link UBQualifier} to be converted
   * @return an annotation from the Substring Index hierarchy, representing {@code qualifier}
   */
  public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) {
    if (qualifier.isUnknown()) {
      return UNKNOWN;
    } else if (qualifier.isBottom()) {
      return BOTTOM;
    }

    LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier;
    return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv);
  }
}
