/*
SDX: Documentary System in XML.
Copyright (C) 2000  Ministere de la culture et de la communication (France), AJLSM

Ministere de la culture et de la communication,
Mission de la recherche et de la technologie
3 rue de Valois, 75042 Paris Cedex 01 (France)
mrt@culture.fr, michel.bottin@culture.fr

AJLSM, 17, rue Vital Carles, 33000 Bordeaux (France)
sevigny@ajlsm.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the
Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
or connect to:
http://www.fsf.org/copyleft/gpl.html
 */
package fr.gouv.culture.sdx.search.lucene.query;

import java.util.Vector;

import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.search.lucene.filter.Filter;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;

/**
 *	A complex boolean query, reprenseted as a tree.
 *
 * <p>
 * Any boolean query can be represented by a complex query. It is made
 * of search criterias linked with a boolean operator. All criterias are
 * themselves any type of Lucene query, so the model is recursive.
 */
public class ComplexQuery extends AbstractQuery {

    /** The components of this complex query. */
    private Vector components = new Vector();

    /** The operator linking the components. */
    private int operator;

    /** Keep the boost value in order to apply it to each component. */
    private float boost = 1;

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.search.lucene.query.Query#setBoost(float)
	 * This method must be called after all component were added.
	 */
	public void setBoost(float boost) {
		if(boost >= 0){
			this.boost	=	boost;
			for(int i=0; i < components.size(); i++){
				((Query)components.get(i)).setBoost( ((Query)components.get(i)).getBoost() * boost);
			}
		}
	}

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.search.lucene.query.Query#setBoost(float)
	 */
	public float getBoost() {
		return this.boost;
	}

    /**Creates a query
     *
     *  <p>
     * A super.getLog() must be set and then this query must be setUp.
     *
     * @see #enableLogging
     * @see #setUp
     */
    public ComplexQuery() {
    }

    /**
     *	Builds a complex query for an index using an operator.
     *
     *  @param	sLocs	The SearchLocations object (indices to be searched).
     *	@param	operator	The operator linking the components.
     */
    public void setUp(SearchLocations sLocs, int operator) throws SDXException {


        super.setSearchLocations(sLocs);

        // We'll keep the operator, but let's check if it's valid.
        switch (operator) {
            case Query.OPERATOR_AND:
            case Query.OPERATOR_OR:
            case Query.OPERATOR_NOT:
                this.operator = operator;
                break;
            default:
                this.operator = Query.OPERATOR_AND; // Default
        }
    }

    /**
     *	Prepares the query to make it executable.
     *
     * <p>
     * This method will basically call the prepare methods of its
     * components and merge the filters.
     */
    public Filter prepare() {

        // We must prepare the Lucene query to execute.
        if (components != null && components.size() > 0) {

            // Our query will definitely be a boolean one.
            luceneQuery = Utilities.newBooleanQuery();

            // Let's go by all the components.
            for (int i = 0; i < components.size(); i++) {

                // The current query
                Query q = (Query) components.get(i);

                // The filter returned by this query
                Filter f = q.prepare();

                // Merge the filters
                if (f != null) {
                    switch (operator) {
                        case Query.OPERATOR_AND:
                            if (filter == null) filter = new fr.gouv.culture.sdx.search.lucene.filter.Filter(Filter.BOOLEAN_OPERATOR_AND);
                            ((fr.gouv.culture.sdx.search.lucene.filter.Filter) filter).add(f);
                            break;
                        case Query.OPERATOR_OR:
                            if (filter == null) filter = new fr.gouv.culture.sdx.search.lucene.filter.Filter(Filter.BOOLEAN_OPERATOR_OR);
                            ((fr.gouv.culture.sdx.search.lucene.filter.Filter) filter).add(f);
                            break;
                        case Query.OPERATOR_NOT:
                            if (filter == null) filter = new fr.gouv.culture.sdx.search.lucene.filter.Filter(Filter.BOOLEAN_OPERATOR_NOT);
                            ((fr.gouv.culture.sdx.search.lucene.filter.Filter) filter).add(f);
                            break;
                    }
                }

                // Add the current query to the global boolean query
                org.apache.lucene.search.Query componentQuery = q.getLuceneQuery();
                if (componentQuery == null) continue;//no query given so continue to next component
                switch (operator) {
                    case Query.OPERATOR_AND:
                    	// MAJ Lucene 2.1.0
                        //((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), true, false);
                    	((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), BooleanClause.Occur.MUST);
                        break;
                    case Query.OPERATOR_OR:
                    	// MAJ Lucene 2.1.0
                        //((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), false, false);
                    	((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), BooleanClause.Occur.SHOULD);
                        break;
                    case Query.OPERATOR_NOT:
                    	// MAJ Lucene 2.1.0
                        //if ( i == 0 ) ((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), true, false);
                        //else ((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), false, true);
                    	if ( i == 0 )	((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), BooleanClause.Occur.MUST);
                        else			((BooleanQuery) luceneQuery).add(q.getLuceneQuery(), BooleanClause.Occur.MUST_NOT);
                        break;
                }
            }

            // We add the base query
            if (baseQuery != null) {

                // We must create a new boolean query
                BooleanQuery newQuery = Utilities.newBooleanQuery();
                switch (baseOperator) {
                    case Query.OPERATOR_OR:
                    	// MAJ Lucene 2.1.0
                        //newQuery.add(luceneQuery, false, false);
                        //newQuery.add(baseQuery.getLuceneQuery(), false, false);
                    	newQuery.add(luceneQuery, BooleanClause.Occur.SHOULD);
                        newQuery.add(baseQuery.getLuceneQuery(), BooleanClause.Occur.SHOULD);
                        break;
                    case Query.OPERATOR_NOT:
                    	// MAJ Lucene 2.1.0
                        //newQuery.add(luceneQuery, false, true);
                        //newQuery.add(baseQuery.getLuceneQuery(), true, false);
                    	newQuery.add(luceneQuery, BooleanClause.Occur.MUST_NOT);
                        newQuery.add(baseQuery.getLuceneQuery(), BooleanClause.Occur.MUST);
                        break;
                    case Query.OPERATOR_AND:	// Valeur par defaut ET
                    default:
                        baseOperator = Query.OPERATOR_AND;
                    	// MAJ Lucene 2.1.0
                        //newQuery.add(luceneQuery, true, false);
                        //newQuery.add(baseQuery.getLuceneQuery(), true, false);
	                    newQuery.add(luceneQuery, BooleanClause.Occur.MUST);
	                    newQuery.add(baseQuery.getLuceneQuery(), BooleanClause.Occur.MUST);
                        break;
                }
                // And sets the Lucene query to this new one
                luceneQuery = newQuery;
            }
        }

        // And return the merged filter
        return filter;
    }

    /**
     *	Adds a component to this query.
     *
     *	@param	component		The component to add.
     */
    public void addComponent(Query component) {
        if (component != null) {
        	components.add(component);
        }
    }

    /**
     *	Retourne une representation DOM de cette requete.
     *
     *	@param	factory		Le document DOM servant de manufacture.
     */
/*
	public Element toDOM(Document factory) throws SDXException
	{
		if ( queryElement == null )
		{
			// On doit la construire
			queryElement = factory.createElementNS(Framework.SDXNamespaceURI, Framework.SDXNamespacePrefix + ":query");
			queryElement.setAttribute("xmlns:" + Framework.SDXNamespacePrefix, Framework.SDXNamespaceURI);
			queryElement.setAttribute("type", "complex");
			queryElement.setAttribute("luceneQuery", luceneQuery.toString(db.toString()));
			switch ( operator )
			{
				case fr.gouv.culture.sdx.query.lucene.Query.OPERATOR_AND:
					queryElement.setAttribute("operator", "and");
					break;
				case fr.gouv.culture.sdx.query.lucene.Query.OPERATOR_OR:
					queryElement.setAttribute("operator", "or");
					break;
				case fr.gouv.culture.sdx.query.lucene.Query.OPERATOR_NOT:
					queryElement.setAttribute("operator", "not");
					break;
			}

			//Then add the search locations
			this.getSearchLocations().toSAX(hdl);

			// On ajoute les filtres
			if ( filter != null ) queryElement.appendChild(filter.toDOM(factory));

			// On ajoute la requete de base
			if ( baseQuery != null )
			{
				Element baseQueryEl = factory.createElementNS(Utilities.getSDXNamespaceURI(), Utilities.getSDXNamespacePrefix() + ":baseQuery");
				baseQueryEl.setAttribute("xmlns:" + Utilities.getSDXNamespacePrefix(), Utilities.getSDXNamespaceURI());
				baseQueryEl.appendChild(baseQuery.toDOM(factory));
				queryElement.appendChild(baseQueryEl);
			}

			// Et on ajoute les sous-requetes...
			for (int i=0; i<components.size(); i++)
			{
				fr.gouv.culture.sdx.query.lucene.Query q = (fr.gouv.culture.sdx.query.lucene.Query)components.get(i);
				queryElement.appendChild(q.toDOM(factory));
			}

			return queryElement;
		}
		else
		{
			// On l'a deja, on l'importe
			return (Element)factory.importNode(queryElement, true);
		}
	}

 */
    /**
     *	Returns the number of components for this query.
     */
    public int size() {
        return components.size();
    }


    /**
     *	Returns a SAX representation of this query..
     *
     *	@param	hdl		The content handler that will receive the events.
     */
    public void toSAX(ContentHandler hdl) throws SAXException {
        String sdxNsUri = Framework.SDXNamespaceURI;
        String sdxNsPrefix = Framework.SDXNamespacePrefix;
        String localName = Node.Name.QUERY;
        String qName = sdxNsPrefix + ":" + localName;
        AttributesImpl atts = new AttributesImpl();
        atts.addAttribute("", Node.Name.TYPE, Node.Name.TYPE, Node.Type.CDATA, "complex");
        atts = super.addAttributesLucene(atts);
        switch (operator) {
            case Query.OPERATOR_AND:
                atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "and");
                break;
            case Query.OPERATOR_OR:
                atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "or");
                break;
            case Query.OPERATOR_NOT:
                atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "not");
                break;
        }
        //Let's build the query representation
        hdl.startElement(sdxNsUri, localName, qName, atts);

        //Then add the search locations
        if ( this.getSearchLocations() != null ) this.getSearchLocations().toSAX(hdl); //TODO : is it necessary as info will be duplicated in each component ? -pb

        //Then add the filter
        if (filter != null) filter.toSAX(hdl);

        //Then add the base query
        if (baseQuery != null) {
            String bqLocalName = Node.Name.BASE_QUERY;
            String bqQName = sdxNsPrefix + ":" + bqLocalName;
            AttributesImpl emptyAtts = new AttributesImpl();
            hdl.startElement(sdxNsUri, bqLocalName, bqQName, emptyAtts);
            baseQuery.toSAX(hdl);
            hdl.endElement(sdxNsUri, bqLocalName, bqQName);
        }

        //Then add the components
        for (int i = 0; i < components.size(); i++) {
            Query q = (Query) components.get(i);
            q.toSAX(hdl);
        }

        //All over
        hdl.endElement(sdxNsUri, localName, qName);
    }

    /**
     * Removes the last component added from the list of components, and returns it.
     * @return	The last component, or null if no component added.
     */
    protected Query removeLastComponent() {
        int lastIndex = components.size() - 1;
        if ( lastIndex < 0 ) return null;
        else return (Query)components.remove(lastIndex);
    }


}
