/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.lang.java.rule;

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

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;

/**
 * Base class for rules, that should ignore classes/fields that are annotated
 * with Lombok annotations.
 *
 * @author Andreas Dangel
 */
public class AbstractLombokAwareRule extends AbstractJavaRule {

    private boolean lombokImported = false;
    private boolean classHasLombokAnnotation = false;
    private static final String LOMBOK_PACKAGE = "lombok";
    private static final Set<String> LOMBOK_ANNOTATIONS = new HashSet<>();

    static {
        LOMBOK_ANNOTATIONS.add("Data");
        LOMBOK_ANNOTATIONS.add("Getter");
        LOMBOK_ANNOTATIONS.add("Setter");
        LOMBOK_ANNOTATIONS.add("Value");
        LOMBOK_ANNOTATIONS.add("RequiredArgsConstructor");
        LOMBOK_ANNOTATIONS.add("AllArgsConstructor");
        LOMBOK_ANNOTATIONS.add("Builder");
    }

    @Override
    public Object visit(ASTCompilationUnit node, Object data) {
        lombokImported = false;
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTImportDeclaration node, Object data) {
        ASTName name = node.getFirstChildOfType(ASTName.class);
        if (!lombokImported && name != null && name.getImage() != null & name.getImage().startsWith(LOMBOK_PACKAGE)) {
            lombokImported = true;
        }
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
        classHasLombokAnnotation = hasLombokAnnotation(node);
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTEnumDeclaration node, Object data) {
        classHasLombokAnnotation = hasLombokAnnotation(node);
        return super.visit(node, data);
    }

    /**
     * Returns whether there have been class level Lombok annotations found.
     * Note: this can only be queried after the class declaration node has been
     * processed.
     * 
     * @return <code>true</code> if a lombok annotation at the class level has
     *         been found
     */
    protected boolean hasClassLombokAnnotation() {
        return classHasLombokAnnotation;
    }

    /**
     * Checks whether the given node is annotated with any lombok annotation.
     * The node can be any node, e.g. class declaration or field declaration.
     * 
     * @param node
     *            the node to check
     * @return <code>true</code> if a lombok annotation has been found
     */
    protected boolean hasLombokAnnotation(Node node) {
        boolean result = false;
        Node parent = node.jjtGetParent();
        List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
        for (ASTAnnotation annotation : annotations) {
            ASTName name = annotation.getFirstDescendantOfType(ASTName.class);
            if (name != null) {
                String annotationName = name.getImage();
                if (lombokImported) {
                    if (LOMBOK_ANNOTATIONS.contains(annotationName)) {
                        result = true;
                    }
                } else {
                    if (annotationName.startsWith(LOMBOK_PACKAGE + ".")) {
                        String shortName = annotationName.substring(LOMBOK_PACKAGE.length() + 1);
                        if (LOMBOK_ANNOTATIONS.contains(shortName)) {
                            result = true;
                        }
                    }
                }
            }
        }
        return result;
    }
}
