/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.lang.java.rule.design;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;

public class NonThreadSafeSingletonRule extends AbstractJavaRule {

    private Map<String, ASTFieldDeclaration> fieldDecls = new HashMap<>();

    private boolean checkNonStaticMethods = true;
    private boolean checkNonStaticFields = true;

    private static final BooleanProperty CHECK_NON_STATIC_METHODS_DESCRIPTOR = new BooleanProperty(
            "checkNonStaticMethods",
            "Check for non-static methods.  Do not set this to false and checkNonStaticFields to true.", true, 1.0f);
    private static final BooleanProperty CHECK_NON_STATIC_FIELDS_DESCRIPTOR = new BooleanProperty(
            "checkNonStaticFields",
            "Check for non-static fields.  Do not set this to true and checkNonStaticMethods to false.", false, 2.0f);

    public NonThreadSafeSingletonRule() {
        definePropertyDescriptor(CHECK_NON_STATIC_METHODS_DESCRIPTOR);
        definePropertyDescriptor(CHECK_NON_STATIC_FIELDS_DESCRIPTOR);
    }

    @Override
    public Object visit(ASTCompilationUnit node, Object data) {
        fieldDecls.clear();
        checkNonStaticMethods = getProperty(CHECK_NON_STATIC_METHODS_DESCRIPTOR);
        checkNonStaticFields = getProperty(CHECK_NON_STATIC_FIELDS_DESCRIPTOR);
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTFieldDeclaration node, Object data) {
        if (checkNonStaticFields || node.isStatic()) {
            fieldDecls.put(node.getVariableName(), node);
        }
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTMethodDeclaration node, Object data) {

        if (checkNonStaticMethods && !node.isStatic() || node.isSynchronized()) {
            return super.visit(node, data);
        }

        List<ASTIfStatement> ifStatements = node.findDescendantsOfType(ASTIfStatement.class);
        for (ASTIfStatement ifStatement : ifStatements) {
            if (ifStatement.getFirstParentOfType(ASTSynchronizedStatement.class) == null) {
                if (!ifStatement.hasDescendantOfType(ASTNullLiteral.class)) {
                    continue;
                }
                ASTName n = ifStatement.getFirstDescendantOfType(ASTName.class);
                if (n == null || !fieldDecls.containsKey(n.getImage())) {
                    continue;
                }
                List<ASTAssignmentOperator> assigmnents = ifStatement
                        .findDescendantsOfType(ASTAssignmentOperator.class);
                boolean violation = false;
                for (int ix = 0; ix < assigmnents.size(); ix++) {
                    ASTAssignmentOperator oper = assigmnents.get(ix);
                    if (!(oper.jjtGetParent() instanceof ASTStatementExpression)) {
                        continue;
                    }
                    ASTStatementExpression expr = (ASTStatementExpression) oper.jjtGetParent();
                    if (expr.jjtGetChild(0) instanceof ASTPrimaryExpression
                            && ((ASTPrimaryExpression) expr.jjtGetChild(0)).jjtGetNumChildren() == 1
                            && ((ASTPrimaryExpression) expr.jjtGetChild(0)).jjtGetChild(0) instanceof ASTPrimaryPrefix) {
                        ASTPrimaryPrefix pp = (ASTPrimaryPrefix) ((ASTPrimaryExpression) expr.jjtGetChild(0))
                                .jjtGetChild(0);
                        String name = null;
                        if (pp.usesThisModifier()) {
                            ASTPrimarySuffix priSuf = expr.getFirstDescendantOfType(ASTPrimarySuffix.class);
                            name = priSuf.getImage();
                        } else {
                            ASTName astName = (ASTName) pp.jjtGetChild(0);
                            name = astName.getImage();
                        }
                        if (fieldDecls.containsKey(name)) {
                            violation = true;
                        }
                    }
                }
                if (violation) {
                    addViolation(data, ifStatement);
                }
            }
        }
        return super.visit(node, data);
    }
}
