package cnp.ew.util;
import java.util.*;

/**
 * Provides a mechanism for matching keyboard input as a prefix
 * against a list of strings.  Every time a key is processed, it is
 * appended to a current prefix, and a new match is searched for.
 * If one is found, its index is made the new selection. If none is found,
 * the previous substring is retained.
 *
 * The editing keys backspace (for deleting the last character),
 * delete (for clearing the entire substring), and escape
 * (for expanding to the largest common substring) are supported.
 *
 * Note: matching is case insensitive.
 */
public class CpPrefixMatcher
{

    Vector lowerCasedStringList, realStringList;
    String currentPrefixString="";
    int currentSelectionIndex=0;
    boolean wasNoMatch;
    boolean selectionChanged;
    boolean prefixChanged;

    public void setSelectionIndex(int newIndex)
    {
        if (newIndex != currentSelectionIndex) {
            currentSelectionIndex = newIndex;
            clearPrefix();
        }
    }

    public void setPrefixString(String newPrefixString)
    {
        currentPrefixString = newPrefixString.toLowerCase();
    }


    public int getSelectionIndex()
    {
        return currentSelectionIndex;
    }

    public String getPrefixString()
    {
        if (currentPrefixString == null || currentPrefixString.length() == 0) {
            return "";
        }
        return ((String)realStringList.elementAt(currentSelectionIndex)).substring(0,currentPrefixString.length());
    }

    public void setStringList(Vector newStringList)
    {
        String string;
        Enumeration e;

        realStringList = newStringList;
        lowerCasedStringList = new Vector(newStringList.size());
        e = newStringList.elements();
        while (e.hasMoreElements()) {
            lowerCasedStringList.addElement(((String)e.nextElement()).toLowerCase());
        }
        currentPrefixString = "";
        currentSelectionIndex = 0;
    }

    /**
     * Process the given keystroke. If the key is a backspace,
     * delete the last character from the current prefix (if there is
     * one). If it's a delete, clear the entire prefix.  If it's escape,
     * expand the prefix to the largest common prefix possible for the
     * current subset of strings that match.
     *
     * After this keystroke is processed, information can be determined
     * about the results of the processing by querying the matcher.
     *
     * @see CpPrefixMatcher#wasNoMatch
     * @see CpPrefixMatcher#selectionChanged;
     * @see CpPrefixMatcher#prefixChanged;
     * @see CpPrefixMatcher#visiblyChanged;
     */
    public void processKey(int key)
    {

        initializeResults();
        switch (key) {
        case 8:     // ascii backspace
            backspace();
            break;
        case 127:   // ascii delete
            clearPrefix();
            break;
        case 27:    // ascii escape
            expandToLargestPrefix();
            break;
        default:
            appendCharacterToPrefix((char)key);
            break;
        }
    }

    void appendCharacterToPrefix(char character)
    {
        String savedPrefix;

        savedPrefix = currentPrefixString;
        currentPrefixString = currentPrefixString + String.valueOf(Character.toLowerCase(character));
        findMatch();
        if (wasNoMatch()) {
            currentPrefixString = savedPrefix;
        } else {
            prefixChanged = true;
        }
    }


    void initializeResults()
    {
        wasNoMatch = false;
        prefixChanged = false;
        selectionChanged = false;
    }

    public boolean wasNoMatch()
    {
        return wasNoMatch;
    }

    public boolean prefixChanged()
    {
        return prefixChanged;
    }

    public boolean selectionChanged()
    {
        return selectionChanged;
    }

    public boolean visiblyChanged()
    {
        return prefixChanged || selectionChanged;
    }

    /**
     * Delete the last character from the prefix.
     */
    void backspace()
    {
        if (currentPrefixString.length() == 0) { return; }

        currentPrefixString = currentPrefixString.substring(0, currentPrefixString.length() - 1);
        prefixChanged = true;
        findMatch();
    }

    /**
     * Remove the current prefix, without changing the selection.
     */
    void clearPrefix()
    {
        if (currentPrefixString.length() == 0) {
            return;
        }
        currentPrefixString = "";
        prefixChanged = true;
    }

    /**
     * Increases the size of the current prefix to the 'longest common prefix'.
     * This is the longest prefix that all strings matching the current prefix have
     * in common (saves typing).
     */
    void expandToLargestPrefix()
    {
        Vector matchingStrings = new Vector(10);
        int i, oldPrefixLength;
        char currentChar;
        String modelString, curMatch, s;

        // Watch out for an empty list
        if (lowerCasedStringList.size() == 0) { return; }

        // First, find all strings that have the current prefix.
        Enumeration e = lowerCasedStringList.elements();
        while (e.hasMoreElements()) {
            s = (String)e.nextElement();
            if (s.startsWith(currentPrefixString)) {
                matchingStrings.addElement(s);
            }
        }

        // Then compare these strings character by character for a failure to match.
        // As soon as there is a failure, copy the prefix up to this
        // point as the expanded prefix.
        i = oldPrefixLength = currentPrefixString.length() - 1;
        modelString = (String)lowerCasedStringList.elementAt(currentSelectionIndex);
        while (i < modelString.length()) {
            currentChar = modelString.charAt(i);
            e = matchingStrings.elements();
            while (e.hasMoreElements()) {
                curMatch = (String)e.nextElement();
                if (i >= curMatch.length() || curMatch.charAt(i) != currentChar) {
                    currentPrefixString = modelString.substring(0, i);
                    prefixChanged = i > oldPrefixLength;
                    return;
                }
            }
            i++;
        }
        prefixChanged = i > oldPrefixLength;
    }


    /**
     * Scan from the beginning of the list, looking for a string that has a prefix
     * matching the current prefix.
     */
    void findMatch()
    {

        for (int i=0; i < lowerCasedStringList.size(); i++) {
            if (((String)lowerCasedStringList.elementAt(i)).startsWith(currentPrefixString)) {
                if (i != currentSelectionIndex) {
                    currentSelectionIndex = i;
                    selectionChanged = true;
                }
                return;
            }
        }
        wasNoMatch = true;
    }
}
