package org.incava.doctorj;

import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.incava.analysis.Report;
import org.incava.java.*;
import org.incava.log.Log;


/**
 * Analyzes Javadoc and code.
 */
public abstract class ItemDocAnalyzer extends DocAnalyzer
{
    public final static String MSG_NO_SUMMARY_SENTENCE = "No summary sentence";

    public final static String MSG_SUMMARY_SENTENCE_DOES_NOT_END_WITH_PERIOD = "Summary sentence does not end with period";

    public final static String MSG_SUMMARY_SENTENCE_TOO_SHORT = "Summary sentence too short";

    public final static String MSG_SEE_WITHOUT_REFERENCE = "@see without reference";

    public final static String MSG_SINCE_WITHOUT_TEXT = "@since without text";

    public final static String MSG_DEPRECATED_WITHOUT_TEXT = "@deprecated without text";
    
    public final static String MSG_TAG_IMPROPER_ORDER = "Tag in improper order";

    protected final static int CHKLVL_SUMMARY_SENTENCE = 1;

    protected final static int CHKLVL_MISORDERED_TAGS = 0;

    protected final static int CHKLVL_VALID_TAGS = 0;

    public static ItemCommentSpellCheck spellChecker = new ItemCommentSpellCheck();

    private SimpleNode node;
    
    public ItemDocAnalyzer(Report r, SimpleNode node)
    {
        super(r);
        this.node = node;
    }

    /**
     * Runs the analysis. Should be invoked by either the constructors of
     * concrete, final subclasses, or by the client.
     */
    public void run()
    {
        JavadocNode javadoc = node.getJavadoc();
        if (javadoc == null) {
            if (isCheckable(node, CHKLVL_DOC_EXISTS)) {
                Log.log("no Javadoc");
                String desc = "Undocumented ";
                if (hasPublicAccess(node)) {
                    desc += "public ";
                }
                else if (hasProtectedAccess(node)) {
                    desc += "protected ";
                }
                else if (hasPrivateAccess(node)) {
                    desc += "private ";
                }

                if (isAbstract(node)) {
                    desc += "abstract ";
                }
                
                desc += getItemType();

                addUndocumentedViolation(desc);
            }
        }
        else {
            checkJavadoc(javadoc);
        }
    }

    /**
     * Adds a violation for this type of item, with the violation pointing to
     * the name for this item.
     */
    protected abstract void addUndocumentedViolation(String desc);

    protected void checkJavadoc(JavadocNode javadoc)
    {
        // check for short summary sentence and spell check the description.

        if (isCheckable(node, CHKLVL_SUMMARY_SENTENCE)) {
            JavadocElement desc = javadoc.getDescription();
            if (desc == null) {
                addViolation(MSG_NO_SUMMARY_SENTENCE, javadoc.getStartLine(), javadoc.getStartColumn(), javadoc.getEndLine(), javadoc.getEndColumn());
            }
            else {
                String descStr = desc.text;
                Log.log("desc: '" + descStr + "'");
                int    dotPos  = descStr.indexOf('.');
                int    len     = descStr.length();

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

                // skip sequences like '127.0.0.1', i.e., whitespace must follow the dot:
                while (dotPos != -1 && dotPos + 1 < len && !Character.isWhitespace(descStr.charAt(dotPos + 1))) {
                    dotPos = descStr.indexOf('.', dotPos + 1);
                }

                if (dotPos == -1) {
                    LineMapping lines = new LineMapping(descStr, desc.start.line, desc.start.column);
                    Location    end   = lines.getLocation(descStr.length() - 1);
                    addViolation(MSG_SUMMARY_SENTENCE_DOES_NOT_END_WITH_PERIOD, desc.start, end);
                }
                else {
                    String summarySentence = descStr.substring(0, dotPos + 1);
                    Log.log("summary: '" + summarySentence + "'");
                    int    nSpaces         = 0;
                    int    spacePos        = -1;
                    while ((spacePos = summarySentence.indexOf(' ', spacePos + 1)) != -1) {
                        ++nSpaces;
                    }
                    if (nSpaces < 3) {
                        LineMapping lines = new LineMapping(descStr, desc.start.line, desc.start.column);
                        Location    end   = lines.getLocation(dotPos);
                        addViolation(MSG_SUMMARY_SENTENCE_TOO_SHORT, desc.start, end);
                    }
                }

                // spell check
                Log.log("spell checking: " + descStr);

                spellChecker.check(this, desc);
            }
        }

        // check for misordered tags

        if (isCheckable(node, CHKLVL_MISORDERED_TAGS)) {
            Log.log("checking for misordered tags");
            int previousOrderIndex = -1;
            JavadocTaggedNode[] taggedComments = javadoc.getTaggedComments();
            for (int ti = 0; ti < taggedComments.length; ++ti) {
                JavadocTag tag   = taggedComments[ti].getTag();
                int        index = JavadocTags.getIndex(tag.text);
                Log.log("index of '" + tag.text + "': " + index);
                if (index < previousOrderIndex) {
                    addViolation(MSG_TAG_IMPROPER_ORDER, tag.start, tag.end);
                    break;
                }
                previousOrderIndex = index;
            }
        }

        // check for proper tags for this type of item
        if (isCheckable(node, CHKLVL_VALID_TAGS)) {
            Log.log("checking for valid tags");
            List                validTags      = getValidTags();
            JavadocTaggedNode[] taggedComments = javadoc.getTaggedComments();
            for (int ti = 0; ti < taggedComments.length; ++ti) {
                JavadocTag tag = taggedComments[ti].getTag();
                if (!validTags.contains(tag.text)) {
                    addViolation("Tag not valid for " + getItemType(), tag.start, tag.end);
                }
            }
        }        

        // check each tag for this item

        if (isCheckable(node, CHKLVL_TAG_CONTENT)) {
            JavadocTaggedNode[] taggedComments = javadoc.getTaggedComments();
            for (int ti = 0; ti < taggedComments.length; ++ti) {
                JavadocTag tag = taggedComments[ti].getTag();
                Log.log("checking tag: " + tag);
                if (tag.text.equals(JavadocTags.SEE)) {
                    checkForTagDescription(taggedComments[ti], MSG_SEE_WITHOUT_REFERENCE);
                }
                else if (tag.text.equals(JavadocTags.SINCE)) {
                    checkForTagDescription(taggedComments[ti], MSG_SINCE_WITHOUT_TEXT);
                }
                else if (tag.text.equals(JavadocTags.DEPRECATED)) {
                    checkForTagDescription(taggedComments[ti], MSG_DEPRECATED_WITHOUT_TEXT);
                }
            }
        }

        // hook into subclasses here, for their specific checks.
    }

    protected void checkForTagDescription(JavadocTaggedNode taggedNode, String msg)
    {
        JavadocElement desc = taggedNode.getDescription();
        if (desc == null) {
            JavadocTag tag = taggedNode.getTag();
            addViolation(msg, tag.start, tag.end);
        }
    }

    protected SimpleNode getNode()
    {
        return node;
    }

    /**
     * Returns the type of item this analyzer is operating on.
     */
    protected abstract String getItemType();

    /**
     * Returns the valid tags, as strings, for this type of item.
     */
    protected abstract List getValidTags();

}
