package org.checkerframework.common.util.count;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.TypeCastTree;
import java.util.List;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.source.SourceVisitor;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;

/**
 * An annotation processor for counting the size of Java code:
 *
 * <ul>
 *   <li>The number of type parameter declarations and uses.
 *   <li>The number of array accesses and dimensions in array creations.
 *   <li>The number of type casts.
 * </ul>
 *
 * <p>To invoke it, use
 *
 * <pre>
 * javac -proc:only -processor org.checkerframework.common.util.count.JavaCodeStatistics <em>MyFile.java ...</em>
 * </pre>
 *
 * @see AnnotationStatistics
 */
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class JavaCodeStatistics extends SourceChecker {

    /** The number of type parameter declarations and uses. */
    int generics = 0;
    /** The number of array accesses and dimensions in array creations. */
    int arrayAccesses = 0;
    /** The number of type casts. */
    int typecasts = 0;

    String[] warningKeys = {
        "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound"
    };
    /**
     * The number of warning suppressions with at least one key that matches one of the Index
     * Checker subcheckers.
     */
    int numberOfIndexWarningSuppressions = 0;

    @Override
    public void typeProcessingOver() {
        System.out.printf("Found %d generic type uses.%n", generics);
        System.out.printf("Found %d array accesses and creations.%n", arrayAccesses);
        System.out.printf("Found %d typecasts.%n", typecasts);
        System.out.printf(
                "Found %d warning suppression annotations for the Index Checker.%n",
                numberOfIndexWarningSuppressions);
    }

    @Override
    protected SourceVisitor<?, ?> createSourceVisitor() {
        return new Visitor(this);
    }

    class Visitor extends SourceVisitor<Void, Void> {

        public Visitor(JavaCodeStatistics l) {
            super(l);
        }

        @Override
        public Void visitAnnotation(AnnotationTree node, Void aVoid) {
            AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(node);
            if (AnnotationUtils.annotationName(annotationMirror)
                    .equals(SuppressWarnings.class.getCanonicalName())) {
                List<String> keys =
                        AnnotationUtils.getElementValueArray(
                                annotationMirror, "value", String.class, true);
                for (String foundKey : keys) {
                    for (String indexKey : warningKeys) {
                        if (foundKey.startsWith(indexKey)) {
                            numberOfIndexWarningSuppressions++;
                            return super.visitAnnotation(node, aVoid);
                        }
                    }
                }
            }
            return super.visitAnnotation(node, aVoid);
        }

        @Override
        public Void visitAssert(AssertTree tree, Void aVoid) {
            ExpressionTree detail = tree.getDetail();
            if (detail != null) {
                String msg = detail.toString();
                for (String indexKey : warningKeys) {
                    String key = "@AssumeAssertion(" + indexKey;
                    if (msg.contains(key)) {
                        numberOfIndexWarningSuppressions++;
                        return super.visitAssert(tree, aVoid);
                    }
                }
            }
            return super.visitAssert(tree, aVoid);
        }

        @Override
        public Void visitClass(ClassTree tree, Void p) {
            if (shouldSkipDefs(tree)) {
                // Not "return super.visitClass(classTree, p);" because that would
                // recursively call visitors on subtrees; we want to skip the
                // class entirely.
                return null;
            }
            generics += tree.getTypeParameters().size();
            return super.visitClass(tree, p);
        }

        @Override
        public Void visitNewArray(NewArrayTree node, Void aVoid) {
            arrayAccesses += node.getDimensions().size();

            return super.visitNewArray(node, aVoid);
        }

        @Override
        public Void visitNewClass(NewClassTree node, Void aVoid) {
            if (TreeUtils.isDiamondTree(node)) {
                generics++;
            }
            generics += node.getTypeArguments().size();
            return super.visitNewClass(node, aVoid);
        }

        @Override
        public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) {
            generics += node.getTypeArguments().size();
            return super.visitMethodInvocation(node, aVoid);
        }

        @Override
        public Void visitMethod(MethodTree node, Void aVoid) {
            generics += node.getTypeParameters().size();
            return super.visitMethod(node, aVoid);
        }

        @Override
        public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) {
            generics += tree.getTypeArguments().size();
            return super.visitParameterizedType(tree, p);
        }

        @Override
        public Void visitArrayAccess(ArrayAccessTree node, Void aVoid) {
            arrayAccesses++;
            return super.visitArrayAccess(node, aVoid);
        }

        @Override
        public Void visitTypeCast(TypeCastTree node, Void aVoid) {
            typecasts++;
            return super.visitTypeCast(node, aVoid);
        }
    }

    @Override
    public AnnotationProvider getAnnotationProvider() {
        throw new UnsupportedOperationException(
                "getAnnotationProvider is not implemented for this class.");
    }
}
