package org.checkerframework.common.aliasing;

import com.sun.source.tree.NewArrayTree;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.util.Elements;
import org.checkerframework.common.aliasing.qual.LeakedToResult;
import org.checkerframework.common.aliasing.qual.MaybeAliased;
import org.checkerframework.common.aliasing.qual.MaybeLeaked;
import org.checkerframework.common.aliasing.qual.NonLeaked;
import org.checkerframework.common.aliasing.qual.Unique;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.NoElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;

/** Annotated type factory for the Aliasing Checker. */
public class AliasingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {

  /** The @{@link MaybeAliased} annotation. */
  protected final AnnotationMirror MAYBE_ALIASED =
      AnnotationBuilder.fromClass(elements, MaybeAliased.class);

  /** The @{@link NonLeaked} annotation. */
  protected final AnnotationMirror NON_LEAKED =
      AnnotationBuilder.fromClass(elements, NonLeaked.class);

  /** The @{@link Unique} annotation. */
  protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class);

  /** The @{@link MaybeLeaked} annotation. */
  protected final AnnotationMirror MAYBE_LEAKED =
      AnnotationBuilder.fromClass(elements, MaybeLeaked.class);

  /** Create the type factory. */
  public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) {
    super(checker);
    if (this.getClass() == AliasingAnnotatedTypeFactory.class) {
      this.postInit();
    }
  }

  // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation
  // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on
  // stub files, this annotation won't be a type qualifier anymore.
  @Override
  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
    return getBundledTypeQualifiers(MaybeLeaked.class);
  }

  @Override
  public CFTransfer createFlowTransferFunction(
      CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
    CFTransfer ret = new AliasingTransfer(analysis);
    return ret;
  }

  protected class AliasingTreeAnnotator extends TreeAnnotator {

    public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) {
      super(atypeFactory);
    }

    @Override
    public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
      type.replaceAnnotation(UNIQUE);
      return super.visitNewArray(tree, type);
    }
  }

  @Override
  protected ListTreeAnnotator createTreeAnnotator() {
    return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator());
  }

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

  /** AliasingQualifierHierarchy. */
  protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy {

    /**
     * Create AliasingQualifierHierarchy.
     *
     * @param qualifierClasses classes of annotations that are the qualifiers
     * @param elements element utils
     */
    protected AliasingQualifierHierarchy(
        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
      super(qualifierClasses, elements, AliasingAnnotatedTypeFactory.this);
    }

    /**
     * Returns true is {@code anno} is annotation in the Leaked hierarchy.
     *
     * @param anno an annotation
     * @return true is {@code anno} is annotation in the Leaked hierarchy
     */
    private boolean isLeakedQualifier(AnnotationMirror anno) {
      return areSameByClass(anno, MaybeLeaked.class)
          || areSameByClass(anno, NonLeaked.class)
          || areSameByClass(anno, LeakedToResult.class);
    }

    @Override
    public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) {
      if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) {
        // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers
        // annotations.
        // Currently the stub parser does not support non-type-qualifier annotations on
        // receiver parameters (Issue 383), therefore these annotations are implemented as
        // type qualifiers but the warnings related to the hierarchy are ignored.
        return true;
      }
      return super.isSubtypeQualifiers(subAnno, superAnno);
    }
  }
}
