package org.incava.doctorj;

import java.io.*;
import java.util.*;
import org.incava.analysis.*;
import org.incava.java.*;
import org.incava.log.Log;
import org.incava.text.SpellChecker;


/**
 * Checks the validity of Javadoc for a list of parameters.
 */
public class ParameterDocAnalyzer extends DocAnalyzer
{
    /**
     * The message for documented parameters for a function without any in the
     * code.
     */
    public final static String MSG_PARAMETERS_DOCUMENTED_BUT_NO_CODE_PARAMETERS = "Parameters documented but no code parameters";
    
    /**
     * The message for a parameter field without a name.
     */
    public final static String MSG_PARAMETER_WITHOUT_NAME = "Parameter tag without name";

    /**
     * The message for a parameter field without a description.
     */
    public final static String MSG_PARAMETER_WITHOUT_DESCRIPTION = "Parameter without description";

    /**
     * The message for parameters being misordered with regard to the code.
     */
    public final static String MSG_PARAMETER_NOT_IN_CODE_ORDER = "Parameter not in code order";

    /**
     * The message for a parameter being apparently misspelled from that in the
     * code.
     */
    public final static String MSG_PARAMETER_MISSPELLED = "Parameter misspelled";

    /**
     * The message for a parameter referring to argument type, not name.
     */
    public final static String MSG_PARAMETER_TYPE_USED = "Parameter refers to type, not name";

    /**
     * The message for an undocumented code parameter.
     */
    public final static String MSG_PARAMETER_NOT_DOCUMENTED = "Parameter not documented";

    /**
     * The message for a documented parameter not found in the code.
     */
    public final static String MSG_PARAMETER_NOT_IN_CODE = "Parameter not in code";

    /**
     * The message for a repeated documented parameter.
     */
    public final static String MSG_PARAMETER_REPEATED = "Parameter repeated";

    /**
     * The warning level for checking for the existence of Javadoc for
     * parameters.
     */
    protected final static int CHKLVL_PARAM_DOC_EXISTS = 1;

    /**
     * The Javadoc block applicable to this parameter list.
     */
    private JavadocNode javadoc;

    /**
     * The function containing the list of parameters.
     */
    private SimpleNode function;

    /**
     * The list of parameters in the code.
     */
    private ASTFormalParameters parameterList;

    /**
     * The list of parameters from the code, that were documented.
     */
    private List documentedParameters = new ArrayList();

    /**
     * Creates and runs the parameter documentation analyzer.
     *
     * @param report   The report to which to send violations.
     * @param javadoc  The javadoc for the function. Should not be null.
     * @param function The constructor or method.
     */
    public ParameterDocAnalyzer(Report report, JavadocNode javadoc, SimpleNode function, ASTFormalParameters parameterList)
    {
        super(report);

        this.javadoc = javadoc;
        this.function = function;
        this.parameterList = parameterList;
    }

    /**
     * Analyzes the Javadoc for the parameter list.
     */
    public void run()
    {
        Log.log("function: " + function);

        // foreach @throws / @parameter tag:
        //  - check for target
        //  - check for description
        //  - in order as in code

        boolean misorderedReported = false;
        int     parameterIndex     = 0;
            
        JavadocTaggedNode[] taggedComments = javadoc.getTaggedComments();
        for (int ti = 0; ti < taggedComments.length; ++ti) {
            JavadocTag tag = taggedComments[ti].getTag();
            Log.log("considering tag: " + tag);
            if (tag.text.equals(JavadocTags.PARAM)) {
                Log.log("got parameter tag");
                
                JavadocElement tgt = taggedComments[ti].getTarget();
                Log.log("target: " + tgt);

                if (parameterList.jjtGetNumChildren() == 0) {
                    addViolation(MSG_PARAMETERS_DOCUMENTED_BUT_NO_CODE_PARAMETERS, tag.start, tag.end);
                    break;
                }
                else if (tgt == null) {
                    if (isCheckable(function, CHKLVL_TAG_CONTENT)) {
                        addViolation(MSG_PARAMETER_WITHOUT_NAME, tag.start, tag.end);
                    }
                }
                else {
                    if (taggedComments[ti].getDescriptionNonTarget() == null && isCheckable(function, CHKLVL_TAG_CONTENT)) {
                        addViolation(MSG_PARAMETER_WITHOUT_DESCRIPTION, tgt.start, tgt.end);
                    }

                    String tgtStr = tgt.text;
                    int    index  = getMatchingParameter(tgtStr);
                    
                    Log.log("matching parameter: " + index);

                    if (index == -1) {
                        index = getClosestMatchingParameter(tgtStr);
                        Log.log("closest matching parameter: " + index);

                        if (index == -1) {
                            String paramType = getParameterType(parameterIndex);
                            Log.log("paramType: " + paramType + "; tgtStr: " + tgtStr);

                            if (tgtStr.equals(paramType)) {
                                addViolation(MSG_PARAMETER_TYPE_USED, tgt.start, tgt.end);
                                addDocumentedParameter(parameterIndex, tgt.start, tgt.end);
                            }
                            else {
                                addViolation(MSG_PARAMETER_NOT_IN_CODE, tgt.start, tgt.end);
                            }
                        }
                        else {
                            addViolation(MSG_PARAMETER_MISSPELLED, tgt.start, tgt.end);
                            addDocumentedParameter(index, tgt.start, tgt.end);
                        }
                    }
                    else {
                        addDocumentedParameter(index, tgt.start, tgt.end);
                    }   
                }
                ++parameterIndex;
            }
        }

        Log.log("documentedParameters: " + documentedParameters);

        if (parameterList != null && isCheckable(function, CHKLVL_PARAM_DOC_EXISTS)) {
            reportUndocumentedParameters();
        }            
    }

    protected void addDocumentedParameter(int index, Location start, Location end)
    {
        if (documentedParameters.size() > 0 && ((Integer)documentedParameters.get(documentedParameters.size() - 1)).intValue() > index) {
            addViolation(MSG_PARAMETER_NOT_IN_CODE_ORDER, start, end);
        }
        
        Integer i = new Integer(index);
        if (documentedParameters.contains(i)) {
            addViolation(MSG_PARAMETER_REPEATED, start, end);
        }
        else {
            documentedParameters.add(i);
        }
    }
    
    protected void reportUndocumentedParameters()
    {
        int nNames = parameterList.jjtGetNumChildren();
        for (int ni = 0; ni < nNames; ++ni) {
            Log.log("considering parameter: " + ni);
            if (!documentedParameters.contains(new Integer(ni))) {
                ASTFormalParameter      param    = (ASTFormalParameter)parameterList.jjtGetChild(ni);
                ASTVariableDeclaratorId vdid     = (ASTVariableDeclaratorId)param.jjtGetChild(1);
                Token                   paramTkn = vdid.getFirstToken();
                addViolation(MSG_PARAMETER_NOT_DOCUMENTED, paramTkn);
            }
        }
    }

    /**
     * Returns the first param in the list whose name matches the given string.
     */
    protected int getMatchingParameter(String str)
    {
        if (parameterList == null) {
            return -1;
        }
        else {
            int nParams = parameterList.jjtGetNumChildren();
            for (int ni = 0; ni < nParams; ++ni) {
                ASTFormalParameter      param    = (ASTFormalParameter)parameterList.jjtGetChild(ni);
                ASTVariableDeclaratorId vdid     = (ASTVariableDeclaratorId)param.jjtGetChild(1);
                Token                   paramTkn = vdid.getFirstToken();
                if (paramTkn.image.equals(str)) {
                    return ni;
                }
            }
            Log.log("no exact match for '" + str + "'");
            return -1;
        }
    }

    /**
     * Returns the first param in the list whose name most closely matches the
     * given string.
     */
    protected int getClosestMatchingParameter(String str)
    {
        if (parameterList == null) {
            return -1;
        }
        else {
            int          nParams      = parameterList.jjtGetNumChildren();
            SpellChecker spellChecker = new SpellChecker();
            int          bestDistance = -1;
            int          bestIndex    = -1;
        
            for (int ni = 0; ni < nParams; ++ni) {
                ASTFormalParameter      param    = (ASTFormalParameter)parameterList.jjtGetChild(ni);
                ASTVariableDeclaratorId vdid     = (ASTVariableDeclaratorId)param.jjtGetChild(1);
                Token                   paramTkn = vdid.getFirstToken();
                int                     dist     = spellChecker.editDistance(paramTkn.image, str);

                // Log.log("edit distance(param: '" + paramTkn.image + "', str: '" + str + "'): " + dist);
            
                if (dist >= 0 && dist <= SpellChecker.DEFAULT_MAX_DISTANCE && (bestDistance == -1 || dist < bestDistance)) {
                    bestDistance = dist;
                    bestIndex    = ni;
                }
            }

            return bestIndex;
        }
    }

    /**
     * Returns the type for the parameter at the given index.
     */
    protected String getParameterType(int index)
    {
        if (parameterList == null || index >= parameterList.jjtGetNumChildren()) {
            return null;
        }
        else {
            ASTFormalParameter param   = (ASTFormalParameter)parameterList.jjtGetChild(index);
            ASTType            type    = (ASTType)param.jjtGetChild(0);
            SimpleNode         node    = (SimpleNode)type.jjtGetChild(0);
            Token              nameTkn = node.getFirstToken();
            return nameTkn.image;
        }
    }

    /**
     * Returns the throws list for the function.
     */
    protected ASTFormalParameter getParameter(int index)
    {
        if (parameterList == null || index >= parameterList.jjtGetNumChildren()) {
            return null;
        }
        else {
            return (ASTFormalParameter)parameterList.jjtGetChild(index);
        }
    }

}
