/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/index/lucene/expressions/BetweenExpression.java,v 1.6 2004/12/07 17:49:57 luetzkendorf Exp $
 * $Revision: 1.6 $
 * $Date: 2004/12/07 17:49:57 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.apache.slide.index.lucene.expressions;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RangeQuery;
import org.apache.lucene.search.TermQuery;

import org.apache.slide.index.lucene.Index;
import org.apache.slide.index.lucene.IndexConfiguration;
import org.apache.slide.search.BadQueryException;
import org.jdom.Element;


/**
 * Implements a slide specific <code>between</code> and 
 * <code>between-inclusive</code> operators.
 * 
 * <p>
 * With lucene <code>(between prop val1 val2)</code> will be more efficient 
 * than <code>(and (gt prop1 val1) (lt prop val2))</code>.
 * <br>
 * In the {@link org.apache.slide.index.lucene.expressions.MergeExpression} 
 * such an optimization is implemented.
 * 
 * <p>Usage:
 * <pre>
 *  &lt;searchrequest xmlns:D="DAV:" xmlns:S="http://jakarta.apache.org/slide/"> 
 *  &lt;S:between>
 *    &lt;D:prop>&lt;D:getlastmodified/>&lt;/D:prop>
 *    &lt;D:literal>Fri, 14 Oct 2004 10:00:00 GMT&lt;/D:literal>
 *    &lt;D:literal>Fri, 15 Oct 2004 10:00:00 GMT&lt;/D:literal>
 *  &lt;/S:between>
 * </pre>
 */
public class BetweenExpression extends AbstractLuceneExpression
{

    public BetweenExpression(Index index, Element element, boolean inclusive, boolean negated)
        throws BadQueryException
    {
        super(index);
        
        IndexConfiguration config = index.getConfiguration();
        Element prop = getPropertyElement(element);
        String field = config.generateFieldName(prop.getNamespaceURI(), prop.getName());
        Element literal1 = getLiteralElement(element);
        Element literal2 = getLiteral2Element(element);

        String value1;
        String value2;
        if (index.getConfiguration().isDateProperty(prop.getNamespaceURI(), prop.getName())) {
            value1 = config.dateToIndexString(
            		IndexConfiguration.getDateValue(literal1.getTextTrim()));
            value2 = config.dateToIndexString(
            		IndexConfiguration.getDateValue(literal2.getTextTrim()));
        } 
        else if (index.getConfiguration().isIntProperty(prop.getNamespaceURI(), prop.getName())) { 
            value1 = config.intToIndexString(Long.parseLong(literal1.getTextTrim()));
            value2 = config.intToIndexString(Long.parseLong(literal2.getTextTrim()));
        } 
        else {
            value1 = literal1.getTextTrim();
            value2 = literal2.getTextTrim();
        }
        
        if (negated) {
            setQuery(createQuery(field, value1, value2, !inclusive));
            negateQuery(field);
        } else {
            setQuery(createQuery(field, value1, value2, inclusive));
        }
    }

    public BetweenExpression(Index index, String field, String lowerValue, 
            String upperValue, boolean incluseLower, boolean incluseUpper,
            boolean negated) {
        super(index);
        if (incluseLower == incluseUpper) {
            if (negated) {
                setQuery(createQuery(field, lowerValue, upperValue, !incluseLower));
                negateQuery(field);
            } else {
                setQuery(createQuery(field, lowerValue, upperValue, incluseLower));
            }
        } else {
            // TODO what about negative integer values (predecessor,successor 
            // does not work with this)
            if (incluseLower) {
                setQuery(createQuery(field, 
                        lowerValue, 
                        index.getConfiguration().predecessor(field, upperValue), 
                        true));
            } else {
                setQuery(createQuery(field, 
                        index.getConfiguration().successor(field, lowerValue),
                        upperValue,
                        true));
            }
        }
    }
    
    private void negateQuery(String field)
    {
        BooleanQuery booleanQuery = new BooleanQuery();
        booleanQuery.add(
                new TermQuery(new Term(Index.IS_DEFINED_FIELD_NAME, field)),
                true,  // required
                false);
        booleanQuery.add(getQuery(),
                false, 
                true); // prohibited
        setQuery(booleanQuery);
    }

    private Query createQuery(String field, String value1, String value2, boolean inclusive)
    {
        int comp =  value1.compareTo(value2);

        if (comp == 0) {
            // value1 == value2
            return new TermQuery(new Term(field, value1));
        } else if (comp < 0) {
            // value1 < value2
            return new RangeQuery(
                    new Term(field, value1),
                    new Term(field, value2),
                    inclusive); // inclusive or not 
        } else {
            // value1 > value2
            return new RangeQuery(
                    new Term(field, value2),
                    new Term(field, value1),
                    inclusive); // inclusive or not 
        }
    }

}
