/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
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.queryparser.ParseException;
import fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
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;
import java.util.Vector;

/**
 * A linear query.
 *
 * <p>
 * A linear query is made of subqueries linked together with boolean operators. No
 * nesting of queries is allowed here.
 */
public class LinearQuery extends AbstractQuery {

    /** All the components of this query. */
    private Vector components = new Vector();

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

    /**
     *	Constructs a linear query.
     *
     *  @param	sLocs				The SearchLocations object (indices to be searched).
     *  @param  queryTerms          The query terms.
     *	@param	connectors  		The operators.
     *	@param	fields  			The field names to search.
     *	@param	operator    The operator for the query (ie AND or OR).
     *  @see fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser#DEFAULT_OPERATOR
     *  @see fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser#OPERATOR_AND
     *  @see fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser#OPERATOR_OR
     */
    public void setUp(SearchLocations sLocs, String[] queryTerms, String[] fields, int[] connectors, int operator) throws SDXException {

        super.setSearchLocations(sLocs);

        // On va d'abord verifier si on a tout ce qu'il nous faut, et completer s'il y a lieu par des valeurs par defaut.

        // Il nous faut au moins un parametre pour les requetes

//TODOException?:		if ( queryParam == null ) throw new SDXException("fr", "Vous devez specifier un parametre pour les requetes de recherche.");

/*
        String[] queryTerms = acceptRequest.getParameterValues(queryParam);
        String[] fields = acceptRequest.getParameterValues(fieldsParam);
        String[] connectors = acceptRequest.getParameterValues(connectorParam);

*/
        if (queryTerms == null) throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_QUERY_TERMS_NULL, null, null);

        // On va boucler sur les requete de recherche car ce sont elles qui ont priorite

        String currentTerm;
        String currentField = null;
        int currentConnector = -1;
        Query currentQuery = null;
        BooleanQuery myQuery = Utilities.newBooleanQuery();
        try {
            for (int i = 0; i < queryTerms.length; i++) {
                if (Utilities.checkString(queryTerms[i]) && !queryTerms[i].equals(" ")) {
                    currentTerm = queryTerms[i];

                    // On a un terme de recherche, il faut donc voir si on a d'autres informations ou prendre les valeurs par defaut

                    if (fields != null && i < fields.length && Utilities.checkString(fields[i]) && !fields[i].equals(" "))
                        currentField = fields[i];
                    else if (currentField == null) currentField = searchLocations.getDefaultField().getCode();

                    if (connectors != null && i < connectors.length)
                        currentConnector = connectors[i];
                    if (currentConnector == -1) currentConnector = defaultOperator;

                    // On en garde une copie
                    components.add(new LinearQueryComponent(currentField, currentTerm, currentConnector));

                    Field theField = searchLocations.getField(currentField);
                    if (theField == null) {
                        String[] args = new String[1];
                        args[0] = currentField;
                        throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_FIELD_DOES_NOT_EXIST, args, null);
                    }
                    int fieldType = searchLocations.getFieldType(currentField);

                    switch (fieldType) {
                        case Field.WORD:
//                            currentQuery = QueryParser.parse(parseWildcards(theField.getCode(), escapeQuery(currentTerm)), theField.getCode(), db.getAnalyzer());
//                            currentQuery = QueryParser.parse(currentTerm, theField.getCode(), (Analyzer) theField.getAnalyzer());
//                            currentQuery = QueryParser.parse(currentTerm, theField.getCode(), new DefaultAnalyzer());
//                            currentQuery = QueryParser.parse(currentTerm, theField.getCode(), searchLocations.getMetaAnalyzer());
                            QueryParser qp = searchLocations.getQueryParser();
                            if (qp == null)
                                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_QUERY_PARSER_NULL, null, null);
                            qp.setUp(theField.getCode(), theField.getAnalyzer());
                            qp.setOperator(operator);
                            currentQuery = qp.parse(currentTerm);
//                            currentQuery = QueryParser.parse(currentTerm, theField.getCode(), theField.getAnalyzer());

                            break;

                        case Field.FIELD:
                        case Field.UNINDEXED:		// Tant pis!
                        case Field.XML:
                            // On va chercher les termes en fonction des masques.
                            if (currentTerm.indexOf("*") != -1 || currentTerm.indexOf("?") != -1) {
/*
								try
								{
*/                              Terms terms = new Terms();
                                terms.enableLogging(super.getLog());
                                terms.setUp(searchLocations, theField.getCode(), currentTerm);
                                TreeMap termList = terms.getList();
                                Iterator it = termList.values().iterator();
                                if (termList.size() < 1)
                                    currentQuery = new TermQuery(new Term(theField.getCode(), currentTerm));	// Ne trouvera rien de toutes facons
                                else {
                                    currentQuery = Utilities.newBooleanQuery();
                                    while (it.hasNext())
                                    	// MAJ Lucene 2.1.0
                                        //((BooleanQuery) currentQuery).add(new TermQuery(((TermInfo) it.next()).getTerm()), false, false);
                                    	((BooleanQuery) currentQuery).add(new TermQuery(((TermInfo) it.next()).getTerm()), BooleanClause.Occur.SHOULD);
                                }
/*
								}
								catch ( IOException e ) { currentQuery = new TermQuery(new Term(theField.getCode(), currentTerm)); }
*/
                            } else
                                currentQuery = new TermQuery(new Term(theField.getCode(), currentTerm));
                            break;

                        case Field.DATE:
                            Date date = fr.gouv.culture.sdx.utils.Date.parseDate(currentTerm);
                            if (date == null)
                                currentQuery = new TermQuery(new Term(theField.getCode(), currentTerm));
                            else
                                currentQuery = new TermQuery(new Term(theField.getCode(), DateField.dateToString(date)));
                            break;
                    }

                    boolean prohibited = false;
                    if (currentConnector == OPERATOR_NOT) prohibited = true;

                    boolean required = false;
                    if (currentConnector == OPERATOR_AND) required = true;

                    // MAJ Lucene 2.1.0
                    //if (currentQuery != null) myQuery.add(currentQuery, required, prohibited);
                    if (currentQuery != null) {
                    	if (required)	myQuery.add(currentQuery, BooleanClause.Occur.MUST);
                    	if (prohibited)	myQuery.add(currentQuery, BooleanClause.Occur.MUST_NOT);
                    	else			myQuery.add(currentQuery, BooleanClause.Occur.SHOULD);
                    }

                }
            }
            luceneQuery = myQuery;
        } catch (ParseException e) {
            String[] args = new String[1];
            args[0] = myQuery.toString();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_QUERY_UNPARSABLE, args, e);
        }
    }

    /**
     *	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 )
		{
			queryElement = factory.createElementNS(Utilities.getSDXNamespaceURI(), Utilities.getSDXNamespacePrefix() + ":query");
			queryElement.setAttribute("xmlns:" + Utilities.getSDXNamespacePrefix(), Utilities.getSDXNamespaceURI());
			queryElement.setAttribute("type", "linear");
			queryElement.setAttribute("luceneQuery", luceneQuery.toString(dbInfo.toString()));

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

			// On ajoute les composantes
			for (int i=0; i<components.size(); i++)
			{
				LinearQueryComponent c = (LinearQueryComponent)components.get(i);
				queryElement.appendChild(c.toDOM(factory));
			}

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

*/
    /**
     *	Returns a SAX representation of this acceptRequest.
     *
     *	@param	hdl		The ContentHandler to feed with 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, "linear");
        atts = super.addAttributesLucene(atts);

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

        // On ajoute les composantes
        for (int i = 0; i < components.size(); i++) {
            LinearQueryComponent c = (LinearQueryComponent) components.get(i);
            c.toSAX(hdl);
        }

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

    /**
     *	Represente une composante d'une requete lineaire.
     */
    private class LinearQueryComponent {

        /** Le champ de recherche */
        private String fieldname;

        /** Le terme recherche. */
        private String text;

        /** L'operateur associe. */
        private int operator;

        /**
         *	Construit une composante.
         *
         *	@param	f	Le nom du champ de recherche.
         *	@param	t	Le texte recherche.
         *	@param	op	L'operateur.
         */
        public LinearQueryComponent(String f, String t, int op) {
            fieldname = f;
            text = t;
            operator = op;
        }

        /*
		*	Retourne une representation DOM de cette composante.
		*
		*	@param	factory		Un document pour creer des elements.
		*/
/*
		public Element toDOM(Document factory)
        {
			Element c = factory.createElementNS(Utilities.getSDXNamespaceURI(), Utilities.getSDXNamespacePrefix() + ":component");
			c.setAttribute("xmlns:" + Utilities.getSDXNamespacePrefix(), Utilities.getSDXNamespaceURI());
			c.setAttribute("field", fieldname);
			c.setAttribute("text", text);
			switch ( operator )
			{
				case Query.OPERATOR_AND:
					c.setAttribute("operator", "and");
					break;
				case fr.gouv.culture.sdx.query.lucene.Query.OPERATOR_OR:
					c.setAttribute("operator", "or");
					break;
				case fr.gouv.culture.sdx.query.lucene.Query.OPERATOR_NOT:
					c.setAttribute("operator", "not");
					break;
			}
			return c;
		}

*/
        /**
         *	Retourne une representation SAX de cette composante.
         *
         *	@param	hdl		Le ContentHandler SAX servant de manufacture.
         * @throws SAXException 
         */
        public void toSAX(ContentHandler hdl) throws SAXException {
            String sdxNsUri = Framework.SDXNamespaceURI;
            String sdxNsPrefix = Framework.SDXNamespacePrefix;
            String localName = Node.Name.COMPONENT;
            String qName = sdxNsPrefix + ":" + localName;
            AttributesImpl atts = new AttributesImpl();
            atts.addAttribute("", Node.Name.FIELD, Node.Name.FIELD, Node.Type.CDATA, fieldname);
            atts.addAttribute("", Node.Name.TEXT, Node.Name.TEXT, Node.Type.CDATA, text);
            switch (operator) {
                case fr.gouv.culture.sdx.search.lucene.query.Query.OPERATOR_AND:
                    atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "and");
                    break;
                case fr.gouv.culture.sdx.search.lucene.query.Query.OPERATOR_OR:
                    atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "or");
                    break;
                case fr.gouv.culture.sdx.search.lucene.query.Query.OPERATOR_NOT:
                    atts.addAttribute("", Node.Name.OPERATOR, Node.Name.OPERATOR, Node.Type.CDATA, "not");
                    break;
            }
            //Let's build the component representation
            hdl.startElement(sdxNsUri, localName, qName, atts);
            //All over
            hdl.endElement(sdxNsUri, localName, qName);

        }
    }



}
