/*
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 fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.search.lucene.DateField;
import fr.gouv.culture.sdx.search.lucene.Field;
import org.apache.lucene.search.TermQuery;
import fr.gouv.culture.sdx.documentbase.LuceneDocumentBase;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.search.lucene.filter.Criteria;
import fr.gouv.culture.sdx.search.lucene.DateFilter;
import org.apache.lucene.index.Term;
//import org.apache.lucene.search.RangeQuery;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import fr.gouv.culture.sdx.search.lucene.filter.Filter;

import java.util.Date;

/**
 * A search for date intervals, opened or closed.
 *
 * <p>
 * This query will only work on a Date field.
 * <p>
 * Two dates can be given : the first one gives the lower
 * bound, the second one gives the upper bound. Bounds are inclusive NOT
 * exclusive If only one of the two is not null, the query is unbounded.
 * It is an error to give two null dates.
 */
public class DateIntervalQuery extends AbstractQuery {

    /** The searched field. */
    private Field field;

    /** The lower bound date. */
    private Date _beginDate;

    /** The upper bound date. */
    private Date _endDate;

    protected boolean _inclusive = true;

    /**Creates a query
     *
     *  <p>
     * A super.getLog() must be set and then this query must be setUp.
     *
     * @see #enableLogging
     * @see #setUp
     */
    public DateIntervalQuery() {
    }
    /**
     *	Buils a date interval query for oai responses.
     *
     * <p>
     * One of beginDate or endDate must be non null.
     *
     *  @param	sLocs		The SearchLocations object (indices to be searched).
     *	@param	fieldName	The field name to search (if null or non existent, default field will be searched).
     *	@param	beginDate	The lower bound date (may be null).
     *	@param	endDate		The upper bound date (may be null).
     */

    public void setUpOai(SearchLocations sLocs, String fieldName, Date beginDate, Date endDate) throws SDXException {
        // MD : created because the range query causes pbs for oai resumptionToken
    	//so i put here old code that works
    	// Calls the super constructor.
        super.setSearchLocations(sLocs);
        // Get the field to use
        Field field = searchLocations.getField(fieldName);
        if (field == null) {
            String[] args = new String[1];
            args[0] = fieldName;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_FIELD_DOES_NOT_EXIST, args, null);
        } else if (field.getFieldType() != Field.DATE) {
            String[] args = new String[1];
            args[0] = fieldName;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_NOT_DATE_FIELD, args, null);
        } else
            this.field = field;

        if (beginDate == null && endDate == null)
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_DATE_INVALID, null, null);
        // Keep the dates
        this._beginDate = beginDate;
        this._endDate = endDate;

        // Puisque Lucene gere les intervalles a l'aide d'un filtre, nous devons trouver
        // une requete qui retourne l'ensemble des documents et la filter a l'aide des dates.

        // Lucene handles date interval queries using a filter, which means that we need a
        // query that will return all the results, and then apply the date filter.
        luceneQuery = new TermQuery(new Term(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXALL, LuceneDocumentBase.INTERNAL_SDXALL_FIELD_VALUE));

        // Build the filter
		DateFilter f;
		if (beginDate == null)
		    f = DateFilter.Before(field.getCode(), endDate); // Upper bounded
		else if (endDate == null)
		    f = DateFilter.After(field.getCode(), beginDate); // Lower bounded
		else
		    f = new DateFilter(field.getCode(), beginDate, endDate);    // Closed interval

		Criteria c = new Criteria();
		c.enableLogging(super.getLog());
		c.setUp(f);
		Filter filt = new Filter();
		filt.setUp(Filter.BOOLEAN_OPERATOR_AND);
		filt.add(c);
		addFilter(filt);
    }

    /**
     *	Buils a date interval query.
     *
     * <p>
     * One of beginDate or endDate must be non null.
     *
     *  @param	sLocs		The SearchLocations object (indices to be searched).
     *	@param	fieldName	The field name to search (if null or non existent, default field will be searched).
     *	@param	beginDate	The lower bound date (may be null).
     *	@param	endDate		The upper bound date (may be null).
     */
    public void setUp(SearchLocations sLocs, String fieldName, Date beginDate, Date endDate) throws SDXException {
        setUp(sLocs, fieldName, beginDate, endDate, _inclusive);
    }

    /**
     *	Buils a date interval query.
     *
     * <p>
     * One of beginDate or endDate must be non null.
     *
     *  @param	sLocs		The SearchLocations object (indices to be searched).
     *	@param	fieldName	The field name to search (if null or non existent, default field will be searched).
     *	@param	beginDate	The lower bound date (may be null).
     *	@param	endDate		The upper bound date (may be null).
     *  @param  inclusive   Whether the bounds should be included in the request
     */
    public void setUp(SearchLocations sLocs, String fieldName, Date beginDate, Date endDate, boolean inclusive) throws SDXException {

        // Calls the super constructor.
        super.setSearchLocations(sLocs);

        // Get the field to use
        Field field = searchLocations.getField(fieldName);
        if (field == null) {
            String[] args = new String[1];
            args[0] = fieldName;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_FIELD_DOES_NOT_EXIST, args, null);
        } else if (field.getFieldType() != Field.DATE) {
            String[] args = new String[1];
            args[0] = fieldName;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_NOT_DATE_FIELD, args, null);
        } else
            this.field = field;

        if (beginDate == null && endDate == null)
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_DATE_INVALID, null, null);

        // Keep the dates
        this._beginDate = beginDate;
        this._endDate = endDate;

        String lowerBound = DateField.MIN_DATE_STRING();
        String upperBound = DateField.MAX_DATE_STRING();
        if (beginDate != null)
        	lowerBound = fr.gouv.culture.sdx.search.lucene.DateField.dateToString(_beginDate);
        	//MAJ Lucene 2.2 : lowerBound = org.apache.lucene.document.DateTools.dateToString(_beginDate,org.apache.lucene.document.DateTools.Resolution.MILLISECOND);
        if (endDate != null)
        	upperBound = fr.gouv.culture.sdx.search.lucene.DateField.dateToString(_endDate);
        	//MAJ Lucene 2.2 : upperBound = org.apache.lucene.document.DateTools.dateToString(_endDate,org.apache.lucene.document.DateTools.Resolution.MILLISECOND);
        setLuceneRangeQuery(fieldName, lowerBound, upperBound, inclusive);

    }

    protected void setLuceneRangeQuery(String fieldName, String lowerBound, String upperBound, boolean inclusive) {
    	super.luceneQuery = new SDXRangeQuery(new Term(fieldName, lowerBound), new Term(fieldName, upperBound), inclusive);
    	// super.luceneQuery = new RangeQuery(new Term(fieldName, lowerBound), new Term(fieldName, upperBound), inclusive);
    }

    /**
     *	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 )
		{
			String nsURI = Framework.SDXNamespaceURI;
			String nsPrefix = Framework.SDXNamespacePrefix;

			// On doit la construire
			queryElement = factory.createElementNS(nsURI, nsPrefix + ":query");
			queryElement.setAttribute("xmlns:" + nsPrefix, nsURI);
			queryElement.setAttribute("type", "dateInterval");
			queryElement.setAttribute("luceneQuery", luceneQuery.toString(db.toString()));
			queryElement.setAttribute("field", fieldName);

			if ( beginDate != null ) queryElement.setAttribute("beginDate", fr.gouv.culture.sdx.utils.Date.formatDate(beginDate));
			if ( endDate != null ) queryElement.setAttribute("endDate", fr.gouv.culture.sdx.utils.Date.formatDate(endDate));

			//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(Framework.SDXNamespaceURI, Framework.SDXNamespacePrefix + ":baseQuery");
				baseQueryEl.setAttribute("xmlns:" + Framework.SDXNamespacePrefix, Framework.SDXNamespaceURI);
				baseQueryEl.appendChild(baseQuery.toDOM(factory));
				queryElement.appendChild(baseQueryEl);
			}

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

*/

    /**
     *	Returns a SAX representation of this query.
     *
     *	@param	hdl		The ContentHandler 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, "dateInterval");
        atts.addAttribute("", Node.Name.SEARCH_ENGINE, Node.Name.SEARCH_ENGINE, Node.Type.CDATA, Query.SEARCH_ENGINE);
        try {
            atts.addAttribute("", Node.Name.LUCENE_QUERY, Node.Name.LUCENE_QUERY, Node.Type.CDATA, luceneQuery.toString(searchLocations.getDefaultField().getCode()));
        } catch (SDXException sdxE) {
            throw new SAXException(sdxE.getMessage(), sdxE);
        }
        atts.addAttribute("", Node.Name.FIELD, Node.Name.FIELD, Node.Type.CDATA, field.getCode());

        if (_beginDate != null) atts.addAttribute("", Node.Name.BEGIN_DATE, Node.Name.BEGIN_DATE, Node.Type.CDATA, fr.gouv.culture.sdx.utils.Date.formatDate(_beginDate));
        if (_endDate != null) atts.addAttribute("", Node.Name.END_DATE, Node.Name.END_DATE, Node.Type.CDATA, fr.gouv.culture.sdx.utils.Date.formatDate(_endDate));
        //Let's build the query representation
        hdl.startElement(sdxNsUri, localName, qName, atts);

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

        //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);
        }

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

    }


}
