package org.apache.lucene.search;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache Lucene" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache Lucene", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.IOException;
import java.util.Vector;
import java.util.Iterator;
import java.util.HashSet;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultipleTermPositions;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermPositions;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;

/**
 * PhrasePrefixQuery is a generalized version of PhraseQuery, with an added
 * method {@link #add(Term[])}.
 * To use this class, to search for the phrase "Microsoft app*" first use
 * add(Term) on the term "Microsoft", then find all terms that has "app" as
 * prefix using IndexReader.terms(Term), and use PhrasePrefixQuery.add(Term[]
 * terms) to add them to the query.
 *
 * @author Anders Nielsen
 * @version 1.0
 */
public class PhrasePrefixQuery
    extends Query
{
    private String _field;
    private Vector _termArrays = new Vector();

    private float _idf = 0.0f;
    private float _weight = 0.0f;

    private int _slop = 0;

    /**
     * Creates a new <code>PhrasePrefixQuery</code> instance.
     *
     */
    public PhrasePrefixQuery() {
    }

    /**
     * Describe <code>setSlop</code> method here.
     *
     * @param s an <code>int</code> value
     */
    public void setSlop(int s) {
        _slop = s;
    }

    /**
     * Describe <code>getSlop</code> method here.
     *
     * @return an <code>int</code> value
     */
    public int getSlop() {
        return _slop;
    }

    /**
     * Method <code>add</code> takes a term. This class basically expects 
     * either a Term or WildTerm
     *
     * @param term a <code>Term</code> value
     */
    public void add(Term term) {
        if (_field == null) {
            _field = term.field();
        } else if (_field != term.field()) {
            throw new IllegalArgumentException(
                    "All phrase terms must be in the same field (" + _field + ")");
        }
        _termArrays.add(term);
    }

    Scorer scorer(IndexReader reader,Similarity similarity)
        throws IOException
    {
        if (_termArrays.size() == 0)  // optimize zero-term case
            return null;

        TermPositions[] tps = new TermPositions[_termArrays.size()];
        for (int i=0; i<tps.length; i++)
        {
            TermPositions p=null;
            Object o = _termArrays.get(i);
            Term[] terms = ((Term)o).getTerms();
            if (terms.length > 1)
                p = new MultipleTermPositions(reader, terms);
            else
                p = reader.termPositions(terms[0]);

            if (p == null)
                return null;

            tps[i] = p;
        }

        if (_slop == 0)
            return new ExactPhraseScorer(tps, similarity,
                                             reader.norms(_field), _weight);
        else
            return new SloppyPhraseScorer(tps,similarity,
                                             _slop, reader.norms(_field), _weight);
    }

    /**
     * Method <code>prepare</code> called from Query.parse
     * The expansion of wildcard Character cannot happen until
     * we have an IndexReader.
     */
    void prepare(IndexReader reader) {
        Iterator i = _termArrays.iterator();
        while (i.hasNext())
        {
            Object o = i.next();
            ((Term)o).prepare(reader);
        }
    }

    float sumOfSquaredWeights(Searcher searcher)
        throws IOException
    {
        Iterator i = _termArrays.iterator();
        while (i.hasNext())
        {
            Object o = i.next();
            Term[] terms = ((Term)o).getTerms();
            for (int j=0; j<terms.length; j++) {
                _idf += searcher.getSimilarity().idf(terms[j], searcher);
            }
        }

        _weight = _idf * boost;
        return _weight * _weight;
    }

    void normalize(float norm)
    {
        _weight *= norm;
        _weight *= _idf;
    }

    /**
     * Describe <code>toString</code> method here.
     *
     * This function just prints out its terms. Since this could
     * be a wildcard term then the expansion of a wildcard is printed as well 
     *
     * @param f a <code>String</code> value
     * @return a <code>String</code> value
     */
    public final String toString(String f)
    {
        StringBuffer buffer = new StringBuffer();
        if (!_field.equals(f))
        {
            buffer.append(_field);
            buffer.append(":");
        }

        buffer.append("\"");
        Iterator i = _termArrays.iterator();
        while (i.hasNext()) {
            Object o = i.next();
            buffer.append(((Term)o).toString() );
            if (i.hasNext())
                buffer.append(" ");
        }
        buffer.append("\"");

        if (_slop != 0) {
            buffer.append("~");
            buffer.append(_slop);
        }

        if (boost != 1.0f) {
            buffer.append("^");
            buffer.append(Float.toString(boost));
        }

        return buffer.toString();
    }

    /*
     * Helps immensely with phrase highlighting.
     * Compare text term to first phrase term. If you have a match
     * compare next tokens to remaining phrase terms.
     * Assumes order of Vector is the same as the terms were added. 
     */
    public boolean compareTermAt(Term term,int pos) {
        if (pos <= _termArrays.size()) {
            if (_termArrays.elementAt(pos).equals(term) ) {
                return true;
            }
        }
        return false;
    }

    /*
     * Also used for Phrase highlighting
     */
    public int getNumPhraseTerms() {
        return _termArrays.size();
    }
    
}
