package prefuse.data.search;

import java.util.Iterator;
import java.util.StringTokenizer;

import prefuse.data.Tuple;


/**
 * <p>
 * SearchTupleSet implementation supporting word prefix searches over indexed
 * Tuple data fields. This class uses a {@link Trie Trie} data structure
 * to find search results quickly; however, only prefix matches will be
 * identified as valid search matches. Multi-term search queries will result
 * in the union of the results for the individual query terms. That is, Tuples
 * that match any one of the terms will be included in the results.
 * </p>
 * 
 * <p>
 * For more advanced search capabilities, see 
 * {@link KeywordSearchTupleSet} or {@link RegexSearchTupleSet}.
 * </p>
 *
 * @version 1.0
 * @author <a href="http://jheer.org">jeffrey heer</a>
 * @see prefuse.data.query.SearchQueryBinding
 */
public class PrefixSearchTupleSet extends SearchTupleSet {
    
    private Trie m_trie;
    private Trie.TrieNode m_curNode;
    private String m_delim = " \t\n\r";
    private String m_query = "";
    
    /**
     * Creates a new KeywordSearchFocusSet that is not case sensitive.
     */
    public PrefixSearchTupleSet() {
        this(false);
    }
    
    /**
     * Creates a new KeywordSearchFocusSet with the indicated case sensitivity.
     * @param caseSensitive true if the search routines should be case
     * sensitive, false otherwise.
     */
    public PrefixSearchTupleSet(boolean caseSensitive) {
        m_trie = new Trie(caseSensitive);
    }
    
    /**
     * Returns the delimiter string used to divide data values and
     * queries into separate words. By default, the value consists
     * of just whitespace characters.
     * @return the delimiter string used. This is passed as an argument to a
     * {@link java.util.StringTokenizer} instance that will tokenize the text.
     * @see java.util.StringTokenizer
     */
    public String getDelimiterString() {
        return m_delim;
    }
    
    /**
     * Sets the delimiter string used to divide data values and
     * queries into separate words. By default, the delimiter consists
     * of just whitespace characters.
     * @param delim the delimiter string to use. This is passed as an argument
     * to a {@link java.util.StringTokenizer} instance that will tokenize the
     * text.
     * @see java.util.StringTokenizer
     */
    public void setDelimiterString(String delim) {
        m_delim = delim;
    }
    
    /**
     * @see prefuse.data.search.SearchTupleSet#getQuery()
     */
    public String getQuery() {
        return m_query;
    }
    
    /**
     * Searches the indexed Tuple fields for matching string prefixes, 
     * adding the Tuple instances for each search match to this TupleSet.
     * The query string is first broken up into separate terms, as determined
     * by the current delimiter string. A search for each term is conducted,
     * and all matching Tuples are included in the results.
     * @param query the query string to search for.
     * @see #setDelimiterString(String)
     */
    public void search(String query) {
        if ( query == null )
            query = "";
        
        if ( query.equals(m_query) )
            return;
        
        Tuple[] rem = clearInternal();    
        m_query = query;
        StringTokenizer st = new StringTokenizer(m_query, m_delim);
        if ( !st.hasMoreTokens() )
            m_query = "";
        while ( st.hasMoreTokens() )
            prefixSearch(st.nextToken());
        Tuple[] add = getTupleCount() > 0 ? toArray() : null;
        fireTupleEvent(add, rem);
    }

    /**
     * Issues a prefix search and collects the results
     */
    private void prefixSearch(String query) {
        m_curNode = m_trie.find(query);
        if ( m_curNode != null ) {
            Iterator iter = trieIterator();
            while ( iter.hasNext() )
                addInternal((Tuple)iter.next());
        }
    }
    
    /**
     * Indexes the given field of the provided Tuple instance.
     * @see prefuse.data.search.SearchTupleSet#index(prefuse.data.Tuple, java.lang.String)
     */
    public void index(Tuple t, String field) {
        String s;
        if ( (s=t.getString(field)) == null ) return;
        StringTokenizer st = new StringTokenizer(s,m_delim);
        while ( st.hasMoreTokens() ) {
            String tok = st.nextToken();
            addString(tok, t);
        }
    }
    
    private void addString(String s, Tuple t) {
        m_trie.addString(s,t);
    }
    
    /**
     * Returns true, as unidexing is supported by this class.
     * @see prefuse.data.search.SearchTupleSet#isUnindexSupported()
     */
    public boolean isUnindexSupported() {
        return true;
    }
    
    /**
     * @see prefuse.data.search.SearchTupleSet#unindex(prefuse.data.Tuple, java.lang.String)
     */
    public void unindex(Tuple t, String field) {
        String s;
        if ( (s=t.getString(field)) == null ) return;
        StringTokenizer st = new StringTokenizer(s,m_delim);
        while ( st.hasMoreTokens() ) {
            String tok = st.nextToken();
            removeString(tok, t);
        }
    }
    
    /**
     * Removes all search hits and clears out the index.
     * @see prefuse.data.tuple.TupleSet#clear()
     */
    public void clear() {
        m_trie = new Trie(m_trie.isCaseSensitive());
        super.clear();
    }
    
    private void removeString(String s, Tuple t) {
        m_trie.removeString(s,t);
    }
    
    private Iterator trieIterator() {
        return m_trie.new TrieIterator(m_curNode);
    }
    
}  // end of class PrefixSearchTupleSet
