/*
 * Created on 14.07.2004
 */
package net.sourceforge.pmd.dfa.variableaccess;

import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.ast.SimpleNode;
import net.sourceforge.pmd.dfa.IDataFlowNode;
import net.sourceforge.pmd.dfa.StartOrEndDataFlowNode;
import net.sourceforge.pmd.symboltable.NameOccurrence;
import net.sourceforge.pmd.symboltable.VariableNameDeclaration;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author raik
 *         <p/>
 *         Searches for special nodes and computes based on the sequence, the type of
 *         access of a variable.
 */
public class VariableAccessVisitor extends JavaParserVisitorAdapter {

    public void compute(ASTMethodDeclaration node) {
        if (node.jjtGetParent() instanceof ASTClassOrInterfaceBodyDeclaration) {
            this.computeNow(node);
        }
    }

    public void compute(ASTConstructorDeclaration node) {
        this.computeNow(node);
    }

    private void computeNow(SimpleNode node) {
        IDataFlowNode inode = node.getDataFlowNode();

        List undefinitions = markUsages(inode);

        // is this necessary?  Why does the first node need undefs?
        IDataFlowNode firstINode = (IDataFlowNode) inode.getFlow().get(0);
        firstINode.setVariableAccess(undefinitions);

        IDataFlowNode lastINode = (IDataFlowNode) inode.getFlow().get(inode.getFlow().size() - 1);
        lastINode.setVariableAccess(undefinitions);
    }

    private List markUsages(IDataFlowNode inode) {
        // undefinitions was once a field... seems like it works fine as a local
        List undefinitions = new ArrayList();
        Set variableDeclarations = collectDeclarations(inode);
        for (Iterator i = variableDeclarations.iterator(); i.hasNext();) {
            Map declarations = (Map) i.next();
            for (Iterator j = declarations.entrySet().iterator(); j.hasNext();) {
                Map.Entry entry = (Map.Entry) j.next();
                VariableNameDeclaration vnd = (VariableNameDeclaration) entry.getKey();
                addVariableAccess(vnd.getNode().getBeginLine(), new VariableAccess(VariableAccess.DEFINITION, vnd.getImage()), inode.getFlow());
                undefinitions.add(new VariableAccess(VariableAccess.UNDEFINITION, vnd.getImage()));
                for (Iterator k = ((List) entry.getValue()).iterator(); k.hasNext();) {
                    addAccess(k, inode);
                }
            }
        }
        return undefinitions;
    }

    private Set collectDeclarations(IDataFlowNode inode) {
        Set decls = new HashSet();
        Map varDecls;
        for (int i = 0; i < inode.getFlow().size(); i++) {
            IDataFlowNode n = (IDataFlowNode) inode.getFlow().get(i);
            if (n instanceof StartOrEndDataFlowNode) {
                continue;
            }
            varDecls = n.getSimpleNode().getScope().getVariableDeclarations();
            if (!decls.contains(varDecls)) {
                decls.add(varDecls);
            }
        }
        return decls;
    }

    private void addAccess(Iterator k, IDataFlowNode inode) {
        NameOccurrence occurrence = (NameOccurrence) k.next();
        if (occurrence.isOnLeftHandSide()) {
            this.addVariableAccess(occurrence.getLocation().getBeginLine(), new VariableAccess(VariableAccess.DEFINITION, occurrence.getImage()), inode.getFlow());
        } else if (occurrence.isOnRightHandSide() || (!occurrence.isOnLeftHandSide() && !occurrence.isOnRightHandSide())) {
            this.addVariableAccess(occurrence.getLocation().getBeginLine(), new VariableAccess(VariableAccess.REFERENCING, occurrence.getImage()), inode.getFlow());
        }
    }

    private void addVariableAccess(int line, VariableAccess va, List flow) {
        for (int i = 1; i < flow.size(); i++) {
            IDataFlowNode inode = (IDataFlowNode) flow.get(i);
            if (line == inode.getLine()) {
                List v = new ArrayList();
                v.add(va);
                inode.setVariableAccess(v);
            }
        }
    }

}
