/*
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 fr.gouv.culture.sdx.search.lucene.analysis.Analyzer;
import fr.gouv.culture.sdx.search.lucene.queryparser.ParseException;
import fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.Utilities;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.TermQuery;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.util.Date;
import java.util.Iterator;
import java.util.TreeMap;

/*
 *	La recherche s'effectue dans les valeurs d'un champ,
 *	en fonction du type de champ dont il s'agit. Ainsi,
 *	pour un champ de type date, la valeur recherchee
 *	est d'abord convertie en un objet Date.
 *
 *	Pour les champs de type mot ou champ, les caracteres
 *	generiques * et ? sont supportes.
*/

/**
 *	A search acceptRequest by field.
 *
 *Research is carried out in the values of a field,
 * according to the type of field. Thus, for a field of
 * the type "date", the required value is initially converted
 * into a Date object.
 *
 * For the fields of the "word" or "field"
 * type, generic characters and? are supported
 *
 */
public class FieldQuery extends AbstractQuery {

    /** The given query string. */
    // private String queryText;

    /** The field to search. */
    private Field field;

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

    /**
     *	Builds a field query.
     *
     *	@param	sLocs		The SearchLocations (indices to be searched).
     *	@param	fieldName	The name of the field to search (if null or non existant, the default field will be searched).
     *	@param	q			The query string.
     * @throws SDXException 
     */
    public void setUp(SearchLocations sLocs, String q, String fieldName) throws SDXException {
        // TODO: what about an operator here ??
        // Calls the super constructor.
        super.setSearchLocations(sLocs);

        if (q == null) throw new SDXException(null, SDXExceptionCode.ERROR_QUERY_NULL, null, null);

        this.field = searchLocations.getField(fieldName);
        if (this.field == null) {
            String[] args = new String[1];
            args[0] = fieldName;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_FIELD_DOES_NOT_EXIST, args, null);
        }
        // TODO: default field if none given
        //if (this.field == null) this.field = sLocs.getDefaultField();

        // The query string.
        super.queryText = q;

        // Search depends on type of field...
        switch (field.getFieldType()) {
            case Field.WORD:
                // TODO : should we allow that??? It doesn't really make sense, but anyway...
                try {
                    //                    luceneQuery = QueryParser.parse(parseWildcards(null, escapeQuery(queryText)), index.getDefaultField(), field.getAnalyzer());
                    //                    luceneQuery = QueryParser.parse(queryText, index.getDefaultField().getCode(), (Analyzer) field.getAnalyzer());
//                    luceneQuery = QueryParser.parse(queryText, index.getDefaultField().getCode(), new DefaultAnalyzer());
//                      luceneQuery = QueryParser.parse(queryText, sLocs.getDefaultField().getCode(), searchLocations.getMetaAnalyzer());
                    Analyzer analyzer = null;
                    QueryParser qp = searchLocations.getQueryParser();
                    String fName = field.getCode();//field name
                    analyzer = field.getAnalyzer();//field analyzer
                    if (qp == null)
                        throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_QUERY_PARSER_NULL, null, null);
                    if (analyzer == null)//using default field analyzer if one wasn't available
                        analyzer = searchLocations.getDefaultField().getAnalyzer();
                    if (fName != null && analyzer != null)
                        qp.setUp(field.getCode(), analyzer);
                    //OLD code: qp.setUp(searchLocations.getDefaultField().getCode(), searchLocations.getDefaultField().getAnalyzer());
                    luceneQuery = qp.parse(queryText);
//                    luceneQuery = QueryParser.parse(queryText, sLocs.getDefaultField().getCode(), sLocs.getDefaultField().getAnalyzer());
                } catch (ParseException e) {
                    String[] args = new String[1];
                    args[0] = q;
                    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_QUERY_UNPARSABLE, args, e);
                }
                break;
            case Field.DATE:
                // Try to find a valid date
                Date date = fr.gouv.culture.sdx.utils.Date.parseDate(q);
                if (date == null)
                    luceneQuery = new TermQuery(new Term(field.getCode(), q));  // Basic field search
                else
                    luceneQuery = new TermQuery(new Term(field.getCode(), DateField.dateToString(date)));   // Real date search
                break;
            case Field.FIELD:
                // TODO : May be we don't need that with Lucene 1.2 wildcard searches
                if (queryText.indexOf("*") != -1 || queryText.indexOf("?") != -1) {
                    Terms terms = new Terms();
                    terms.enableLogging(super.getLog());
                    terms.setUp(searchLocations, field.getCode(), q);
                    TreeMap termList = terms.getList();
                    Iterator it = termList.values().iterator();
                    if (termList.size() < 1)
                        // luceneQuery = new TermQuery(new Term(field.getCode(), queryText));	// Ne trouvera rien de toutes facons
                    	luceneQuery = new SDXTermQuery(new Term(field.getCode(), queryText)); // we use SDXTermQuery instead of TermQuery to get a @luceneQuery suitable to SimpleQuery [MP 2005-12-13]
                    else {
                    	luceneQuery = Utilities.newBooleanQuery();
                        while (it.hasNext())
                            // MAJ Lucene 2.1.0
                        	//((BooleanQuery) luceneQuery).add(new SDXTermQuery(((TermInfo) it.next()).getTerm()), false, false); // we use SDXTermQuery instead of TermQuery to get a @luceneQuery suitable to SimpleQuery [MP 2005-12-13]
                        	((BooleanQuery) luceneQuery).add(new SDXTermQuery(((TermInfo) it.next()).getTerm()), BooleanClause.Occur.SHOULD);
                    }
                } else
                    // luceneQuery = new TermQuery(new Term(field.getCode(), queryText)); // we use SDXTermQuery instead of TermQuery to get a @luceneQuery suitable to SimpleQuery [MP 2005-12-13]
                	luceneQuery = new SDXTermQuery(new Term(field.getCode(), queryText));
                break;
            case Field.UNINDEXED:
                //TODO: make the error message more like the original
                //throw new SDXException("fr", "Le champ '" + fieldName + "' n'est pas indexe pour cette base de documents.");
                String[] args = new String[1];
                args[0] = fieldName;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_FIELD_DOES_NOT_EXIST, args, null);
        }
    }

    /**
     *	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 = Utilities.getSDXNamespaceURI();
			String nsPrefix = Utilities.getSDXNamespacePrefix();

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

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

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

    /**
     * Formats a query in XML.
     *
     * @param   hdl     A content handler to receive XML data.
     */
    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, Node.Name.FIELD);
        atts = super.addAttributesLucene(atts);
        atts = super.addAttributesText(atts);
        atts.addAttribute("", Node.Name.FIELD, Node.Name.FIELD, Node.Type.CDATA, field.getCode());
        //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);

    }



}
