package org.incava.doctorj;

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


public class CommentSpellCheck
{
    public static CommentSpellCheck instance = null;

    public static CommentSpellCheck getInstance()
    {
        if (instance == null) {
            instance = new CommentSpellCheck();
        }
        return instance;
    }

    private boolean canCheck;

    private SpellChecker checker;

    /**
     * The current description we're working on.
     */
    private String desc;

    /**
     * The length of the current description.
     */
    private int len;

    /**
     * The current position within the description.
     */
    private int pos;
    
    protected CommentSpellCheck()
    {
        checker = new NoCaseSpellChecker();
        canCheck = false;
    }

    public boolean addDictionary(String dictionary)
    {
        canCheck = checker.addDictionary(dictionary) || canCheck;
        return canCheck;
    }

    public void addWord(String word)
    {
        checker.addWord(word);
        canCheck = true;
    }

    public void check(String description)
    {
        if (canCheck) {
            // Log.log("checking '" + description + "'");

            desc = description;
            len  = desc.length();
            pos  = 0;
    
            while (pos < len) {
                skipToWord();
                if (pos < len) {
                    if (Character.isLetter(desc.charAt(pos))) {
                        checkCurrentWord();
                    }
                    else {
                        // not at an alpha character. Might be some screwy formatting or
                        // a nonstandard tag.
                        skipThroughWord();
                    }
                }
            }
        }
    }

    protected void skipSection(String section)
    {
        // Log.log("section: " + section);
        if (consume("<" + section + ">")) {
            // Log.log("got section: " + section);
            consumeTo("</" + section + ">");
        }
    }
    
    protected void skipLink()
    {
        if (consume("{@link")) {
            consumeTo("}");
        }
    }

    protected void skipBlanks()
    {
        while (pos + 2 < len && desc.charAt(pos) != '<' && !desc.substring(pos, pos + 2).equals("{@") && !Character.isLetterOrDigit(desc.charAt(pos))) {
            ++pos;
        }
    }
    
    protected void skipToWord()
    {
        skipSection("code");
        skipSection("pre");
        skipLink();
        consume("&nbsp;");
    }

    protected void checkWord(String word, int position)
    {
        // Log.log("checking word '" + word + "' at position " + position);

        List nearMatches = new ArrayList();
        boolean valid = checker.isCorrect(word, nearMatches);
        if (!valid) {
            wordMisspelled(word, position, nearMatches);
        }
    }

    protected void wordMisspelled(String word, int position, List nearMatches)
    {
        int nPrinted = 0;
        final int printGoal = 15;
        for (int i = 0; nPrinted < printGoal && i < 4; ++i) { // 4 == max edit distance
            List matches = (List)nearMatches.get(i);
            if (matches != null) {
                Iterator it = matches.iterator();
                while (it.hasNext()) {
                    // This is not debugging output -- this is actually wanted. 
                    // But I often run "glark '^\s*System.out' to find all my
                    // print statements, so we'll hide this very sneakily:
                    /* escond */ System.out.println("    near match '" + it.next() + "': " + i);
                    ++nPrinted;
                }
            }
        }
    }

    protected boolean consume(String what)
    {
        skipBlanks();
        if (pos + what.length() < len && desc.substring(pos).startsWith(what)) {
            pos += what.length();
            return true;
        }
        else {
            return false;
        }
    }

    protected void consumeTo(String what)
    {
        int len = desc.length();
        while (pos < len && pos + what.length() < len && !desc.substring(pos).startsWith(what)) {
            ++pos;
        }
    }

    protected void checkCurrentWord()
    {
        StringBuffer word = new StringBuffer();
        word.append(desc.charAt(pos));
        int startingPosition = pos;
        boolean canCheck = true;

        ++pos;

        // spell check words that do not have:
        //     - mixed case (varName)
        //     - embedded punctuation ("wouldn't", "pkg.foo")
        //     - numbers (M16, BR549)
        while (pos < len) {
            char ch = desc.charAt(pos);
            if (Character.isWhitespace(ch)) {
                break;
            }
            else if (Character.isLowerCase(ch)) {
                word.append(ch);
                ++pos;
            }
            else if (Character.isUpperCase(ch)) {
                skipThroughWord();
                return;
            }
            else if (Character.isDigit(ch)) {
                skipThroughWord();
                return;
            }
            else {
                // must be punctuation, which we can check it if there's nothing
                // but punctuation up to the next space or end of description.
                if (pos + 1 == len) {
                    // that's OK to check
                    break;
                }
                else {
                    ++pos;
                    while (pos < len && !Character.isWhitespace(desc.charAt(pos)) && !Character.isLetterOrDigit(desc.charAt(pos))) {
                        // skipping through punctuation
                        ++pos;
                    }
                    if (pos == len || Character.isWhitespace(desc.charAt(pos))) {
                        // punctuation ended the word, so we can check this
                        break;
                    }
                    else {
                        // punctuation did NOT end the word, so we can NOT check this
                        skipThroughWord();
                        return;
                    }
                }
            }
        }

        // Log.log("word: '" + word + "'");
    
        // has to be more than one character:
        if (canCheck && pos - startingPosition > 1) {
            checkWord(word.toString(), startingPosition);
        }
    }

    protected void skipThroughWord()
    {
        ++pos;
        while (pos < len && !Character.isWhitespace(desc.charAt(pos))) {
            ++pos;
        }
    }

}
