package permissions.dispatcher;

import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import lombok.ast.Annotation;
import lombok.ast.AstVisitor;
import lombok.ast.CompilationUnit;
import lombok.ast.ForwardingAstVisitor;
import lombok.ast.Node;

public class NoCorrespondingNeedsPermissionDetector extends Detector implements Detector.JavaScanner {

    public static final Issue ISSUE = Issue.create("NoCorrespondingNeedsPermission",
            "No corresponding @NeedsPermission found",
            "The processor would ignore @OnShowRationale annotation if there is no corresponding @NeedsPermissions. For instance, if there is @OnShowRationale(Manifest.permission.CAMERA), then there must be @NeedsPermission(Manifest.permission.CAMERA) in the same class.",
            Category.CORRECTNESS,
            4,
            Severity.ERROR,
            new Implementation(NoCorrespondingNeedsPermissionDetector.class, EnumSet.of(Scope.JAVA_FILE)));

    @Override
    public AstVisitor createJavaVisitor(JavaContext context) {
        return new AnnotationChecker(context);
    }

    static class AnnotationChecker extends ForwardingAstVisitor {
        private final JavaContext context;
        private Set<String> needsPermissionName;
        private Set<String> onShowRationaleName;

        private Set<Annotation> needsPermissionAnnotations;
        private Set<Annotation> onShowRationaleAnnotations;

        private AnnotationChecker(JavaContext context) {
            this.context = context;

            needsPermissionName = new HashSet<>();
            needsPermissionName.add("NeedsPermission");
            needsPermissionName.add("permissions.dispatcher.NeedsPermission");

            onShowRationaleName = new HashSet<>();
            onShowRationaleName.add("OnShowRationale");
            onShowRationaleName.add("permissions.dispatcher.OnShowRationale");

            needsPermissionAnnotations = new HashSet<>();
            onShowRationaleAnnotations = new HashSet<>();
        }

        @Override
        public boolean visitAnnotation(Annotation node) {
            if (!context.isEnabled(ISSUE)) {
                return super.visitAnnotation(node);
            }

            // Let's store NeedsPermission and OnShowRationale
            String type = node.astAnnotationTypeReference().getTypeName();
            if (needsPermissionName.contains(type)) {
                needsPermissionAnnotations.add(node);
            } else if (onShowRationaleName.contains(type)) {
                onShowRationaleAnnotations.add(node);
            }
            return super.visitAnnotation(node);
        }

        @Override
        public void afterVisitCompilationUnit(CompilationUnit node) {
            if (onShowRationaleAnnotations.size() <= 0) {
                super.afterVisitCompilationUnit(node);
                return;
            }

            // If there is OnShowRationale, find corresponding NeedsPermission
            boolean found = false;
            for (Annotation onShowRationaleAnnotation : onShowRationaleAnnotations) {
                for (Annotation needsPermissionAnnotation : needsPermissionAnnotations) {
                    if (hasSameNodes(onShowRationaleAnnotation.getValueValues(), needsPermissionAnnotation.getValueValues())) {
                        found = true;
                    }
                }

                if (!found) {
                    context.report(ISSUE, context.getLocation(onShowRationaleAnnotation), "No corresponding @NeedsPermission found");
                }
            }

            super.afterVisitCompilationUnit(node);
        }

        private boolean hasSameNodes(List<Node> first, List<Node> second) {
            if (first.size() != second.size()) {
                return false;
            }

            for (int i = 0; i < first.size(); i++) {
                if (!first.get(i).toString().equals(second.get(i).toString())) {
                    return false;
                }
            }
            return true;
        }
    }
}

@@ -4235,13 +4235,16 @@
  = 0
-; i %3C
+, size =
  fir
@@ -4253,16 +4253,26 @@
 .size();
+ i %3C size;
  i++) %7B%0A

Make better performance loop iteration