/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.filter;

import org.geotools.feature.Feature;
import org.opengis.filter.FilterVisitor;


/**
 * Defines a 'between' filter (which is a specialized compare filter). A
 * between filter is just shorthand for a less-than-or-equal filter ANDed with
 * a greater-than-or-equal filter.  Arguably, this would be better handled
 * using those constructs, but the OGC filter specification creates its own
 * object for this, so we do as well.  An important note here is that a
 * between filter is actually a math filter, so its outer (left and right)
 * expressions must be math expressions.  This is enforced by the
 * FilterAbstract class, which considers a BETWEEN operator to be a math
 * filter.
 *
 * @author Rob Hranac, TOPP
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/filter/BetweenFilterImpl.java $
 * @version $Id: BetweenFilterImpl.java 26757 2007-08-29 20:10:22Z saul.farber $
 *
 * @task REVISIT: I think AbstractFilter right now does not consider between a
 *       math filter. ch
 *       
 * @deprecated use {@link org.geotools.filter.IsBetweenImpl}
 */
public class BetweenFilterImpl extends CompareFilterImpl
    implements BetweenFilter {
    /** The 'middle' value, which must be an attribute expression. */
    protected org.opengis.filter.expression.Expression middleValue = null;

    protected BetweenFilterImpl(FilterFactory factory) {
    	super(factory,null,null);
    	this.filterType = BETWEEN;
    }
    
    /**
     * Constructor which flags the operator as between.
     *
     * @throws IllegalFilterException Should never happen.
     */
    protected BetweenFilterImpl() throws IllegalFilterException {
        super(BETWEEN);
    }

    /**
     * Sets the values to be compared as between the left and right values.
     *
     * @param middleValue The expression to be compared.
     * 
     * @deprecated use {@link #setExpression(org.opengis.filter.expression.Expression)}
     */
    public final void addMiddleValue(Expression middleValue) {
        setExpression(middleValue);
    }
    
    /**
     * Sets the expression or middle value.
     */
    public void setExpression(org.opengis.filter.expression.Expression expression) {
    	this.middleValue = expression;
    	
    }

    /**
     * Gets the middle value of the between.
     *
     * @return The expression in the middle.
     * 
     * @deprecated use {@link #getExpression()}
     */
    public final Expression getMiddleValue() {
        return (Expression) getExpression();
    }

    /**
     * Gets the middle value of the between.
     *
     * @return The expression in the middle.
     */
    public org.opengis.filter.expression.Expression getExpression() {
    	return middleValue;
    }
    
    /**
     * Returns the left,lower, or first expression.
     */
    public org.opengis.filter.expression.Expression getLowerBoundary() {
    	return getExpression1();
    }
    
    /**
     * Sets the left,lower, or first expression.
     */
    public void setLowerBoundary(org.opengis.filter.expression.Expression lowerBounds) {
    	setExpression1(lowerBounds);
    }
    
    /**
     * Returns the right,upper, or second expression.
     */
    public org.opengis.filter.expression.Expression getUpperBoundary() {
    	return getExpression2();
    }
    
    /**
     * Sets the right,upper, or second expression.
     */
    public void setUpperBoundary(org.opengis.filter.expression.Expression upperBounds) {
    	setExpression2(upperBounds);
    }
    

	/**
     * Determines whether or not a given feature is 'inside' this filter.
     *
     * @param feature Specified feature to examine.
     *
     * @return Flag confirming whether or not this feature is inside the
     *         filter.
     */
    public boolean evaluate(Object feature) {
        if (middleValue == null) {
            return false;
        } else {
	    /*Object leftObj = leftValue.getValue(feature);
	    Object rightObj = rightValue.getValue(feature);
	    Object middleObj = middleValue.getValue(feature);
	    //if (leftObj instanceof Number &&
	    //middleObj instanceof Number &&
	    //rightObj instanceof Number) {
		double left = ((Number) leftObj).doubleValue();
		double right = ((Number) rightObj).doubleValue();
		double mid = ((Number) middleObj).doubleValue();
		
		return (left <= mid) && (right >= mid);
	    */
        Object middleObj = eval(middleValue, feature);
        //evaluate the between wrt the class of the middle object
	    Object leftObj = eval(expression1, feature, middleObj.getClass());
	    Object rightObj = eval(expression2, feature, middleObj.getClass());
	    
	    
	    if (leftObj instanceof Number &&
		middleObj instanceof Number &&
		rightObj instanceof Number) {
		double left = ((Number) leftObj).doubleValue();
		double right = ((Number) rightObj).doubleValue();
		double mid = ((Number) middleObj).doubleValue();
		
		return (left <= mid) && (right >= mid);
		//instanceof Comparator?  And same object type?
	    
		//this may miss variations on similar classes that actually
		//could be compared, but I think AttributeType parsing may
		//have helped us out before we get here.
	    } else if (leftObj.getClass() == middleObj.getClass() &&
		       rightObj.getClass() == middleObj.getClass() &&
		       //I don't think we need to check all for comparator
		       //if they are all the same class?
		       leftObj instanceof Comparable) {
		return (((Comparable)leftObj).compareTo(middleObj) <= 0 &&
			((Comparable)middleObj).compareTo(rightObj) <= 0);
	    } else {
		String mesg = "Supplied between values are either not " +
		    "compatible or not supported for comparison: " + leftObj + 
		    " <= " + middleObj + " <= " + rightObj;
		throw new IllegalArgumentException(mesg);
	    }			 

		
        }
    }

    /**
     * Returns a string representation of this filter.
     *
     * @return String representation of the between filter.
     */
    public String toString() {
        return "[ " + expression1.toString() + " < " + middleValue.toString()
        + " < " + expression2.toString() + " ]";
    }

    /**
     * Returns true if the passed in object is the same as this filter.  Checks
     * to make sure the filter types are the same as well as all three of the
     * values.
     *
     * @param oFilter the filter to test for eqaulity.
     *
     * @return True if the objects are equal.
     */
    public boolean equals(Object oFilter) {
        if (oFilter.getClass() == this.getClass()) {
            BetweenFilterImpl bFilter = (BetweenFilterImpl) oFilter;

            return ((bFilter.getFilterType() == this.filterType)
            && bFilter.getLeftValue().equals(this.expression1)
            && bFilter.getMiddleValue().equals(this.middleValue)
            && bFilter.getRightValue().equals(this.expression2));
        } else {
            return false;
        }
    }

    /**
     * Override of hashCode method.
     *
     * @return a code to hash this filter by.
     */
    public int hashCode() {
        int result = 17;

        result = (37 * result)
            + ((expression1 == null) ? 0 : expression1.hashCode());
        result = (37 * result)
            + ((middleValue == null) ? 0 : middleValue.hashCode());
        result = (37 * result)
            + ((expression2 == null) ? 0 : expression2.hashCode());

        return result;
    }

    /**
     * Used by FilterVisitors to perform some action on this filter instance.
     * Typicaly used by Filter decoders, but may also be used by any thing
     * which needs infomration from filter structure. Implementations should
     * always call: visitor.visit(this); It is importatant that this is not
     * left to a parent class unless the parents API is identical.
     *
     * @param visitor The visitor which requires access to this filter, the
     *        method must call visitor.visit(this);
     */
    public Object accept(FilterVisitor visitor, Object extraData) {
    	return visitor.visit(this,extraData);
    }

}
