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

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

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTResultType;
import net.sourceforge.pmd.lang.java.rule.AbstractInefficientZeroCheck;
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.util.CollectionUtil;

/**
 * Detect structures like "foo.size() == 0" and suggest replacing them with
 * foo.isEmpty(). Will also find != 0 (replaceable with !isEmpty()).
 * 
 * @author Jason Bennett
 */
public class UseCollectionIsEmptyRule extends AbstractInefficientZeroCheck {
    
    public boolean appliesToClassName(String name){
        return CollectionUtil.isCollectionType(name, true);
    }
    
    /**
     * Determine if we're dealing with .size method
     * 
     * @param occ
     *            The name occurrence
     * @return true if it's .size, else false
     */
    public boolean isTargetMethod(JavaNameOccurrence occ) {
        if (occ.getNameForWhichThisIsAQualifier() != null) {
            if (occ.getLocation().getImage().endsWith(".size")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Map<String, List<String>> getComparisonTargets() {
        Map<String, List<String>> rules = new HashMap<>();
        rules.put("<", Arrays.asList("0", "1"));
        rules.put(">", Arrays.asList("0"));
        rules.put("==", Arrays.asList("0"));
        rules.put("!=", Arrays.asList("0"));
        rules.put(">=", Arrays.asList("0", "1"));
        rules.put("<=", Arrays.asList("0"));
        return rules;
    }

    @Override
    public Object visit(ASTPrimarySuffix node, Object data) {
        if (node.getImage() != null && node.getImage().endsWith("size")) {

            ASTClassOrInterfaceType type = getTypeOfPrimaryPrefix(node);
            if (type == null) {
                type = getTypeOfMethodCall(node);
            }

            if (type != null && CollectionUtil.isCollectionType(type.getType(), true)) {
                Node expr = node.jjtGetParent().jjtGetParent();
                checkNodeAndReport(data, node, expr);
            }
        }
        return data;
    }

    private ASTClassOrInterfaceType getTypeOfMethodCall(ASTPrimarySuffix node) {
        ASTClassOrInterfaceType type = null;
        ASTName methodName = node.jjtGetParent()
                .getFirstChildOfType(ASTPrimaryPrefix.class)
                .getFirstChildOfType(ASTName.class);
        if (methodName != null) {
            ClassScope classScope = node.getScope().getEnclosingScope(ClassScope.class);
            Map<MethodNameDeclaration, List<NameOccurrence>> methods = classScope.getMethodDeclarations();
            for (Map.Entry<MethodNameDeclaration, List<NameOccurrence>> e : methods.entrySet()) {
                if (e.getKey().getName().equals(methodName.getImage())) {
                    type = e.getKey().getNode()
                        .getFirstParentOfType(ASTMethodDeclaration.class)
                        .getFirstChildOfType(ASTResultType.class)
                        .getFirstDescendantOfType(ASTClassOrInterfaceType.class);
                    break;
                }
            }
        }
        return type;
    }

    private ASTClassOrInterfaceType getTypeOfPrimaryPrefix(ASTPrimarySuffix node) {
        return node.jjtGetParent()
                .getFirstChildOfType(ASTPrimaryPrefix.class)
                .getFirstDescendantOfType(ASTClassOrInterfaceType.class);
    }
}
