/*
 *    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;


// Geotools dependencies
import java.util.logging.Level;
import java.util.logging.Logger;

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


/**
 * Defines a comparison filter (can be a math comparison or generic equals).
 * This filter implements a comparison - of some sort - between two
 * expressions. The comparison may be a math comparison or a generic equals
 * comparison.  If it is a math comparison, only math expressions are allowed;
 * if it is an equals comparison, any expression types are allowed. Note that
 * this comparison does not attempt to restrict its expressions to be
 * meaningful.  This means that it considers itself a valid filter as long as
 * the expression comparison returns a valid result.  It does no checking to
 * see whether or not the expression comparison is meaningful with regard to
 * checking feature attributes.  In other words, this is a valid filter:
 * <b>52 = 92</b>, even though it will always return the same result and could
 * be simplified away.  It is up the the filter creator, therefore, to attempt
 * to simplify/make meaningful filter logic.
 *
 * @author Rob Hranac, Vision for New York
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/filter/CompareFilterImpl.java $
 * @version $Id: CompareFilterImpl.java 27862 2007-11-12 19:51:19Z desruisseaux $
 */
public abstract class CompareFilterImpl extends BinaryComparisonAbstract
    implements CompareFilter {
    /** The logger for the default core module. */
    static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.core");

    /**
     * Constructor with filter type.
     *
     * @param filterType The type of comparison.
     *
     * @throws IllegalFilterException Non-compare type.
     * @deprecated use {@link #CompareFilterImpl(FilterFactory, org.opengis.filter.expression.Expression, org.opengis.filter.expression.Expression)}
     */
    protected CompareFilterImpl(short filterType) throws IllegalFilterException {
    	super(CommonFactoryFinder.getFilterFactory(null));
    	if (isCompareFilter(filterType)) {
            this.filterType = filterType;
        } else {
            throw new IllegalFilterException(
                "Attempted to create compare filter with non-compare type.");
        }
    }

    protected CompareFilterImpl(FilterFactory factory, org.opengis.filter.expression.Expression e1, org.opengis.filter.expression.Expression e2) {
    	this(factory,e1,e2,true);
    }
    
    protected CompareFilterImpl(FilterFactory factory, org.opengis.filter.expression.Expression e1, org.opengis.filter.expression.Expression e2, boolean matchCase ) {
    	super(factory,e1,e2,matchCase);
    }
    
    /**
     * Adds the 'left' value to this filter.
     *
     * @param leftValue Expression for 'left' value.
     *
     * @throws IllegalFilterException Filter is not internally consistent.
     *
     * @task REVISIT: immutability?
     */
    public final void addLeftValue(Expression leftValue)
        throws IllegalFilterException {
        
    	setExpression1(leftValue);
    }
    
    public void setExpression1(org.opengis.filter.expression.Expression leftValue) {
    	//Checks if this is math filter or not and handles appropriately
        if (isMathFilter(filterType)) {
            if (DefaultExpression.isMathExpression(leftValue)
                    || permissiveConstruction) {
                this.expression1 = leftValue;
            } else {
            	throw new IllegalFilterException(
                    "Attempted to add non-math expression to math filter."
				);	
            }
                
        } else {
            this.expression1 = leftValue;
        }
    }

    /**
     * Adds the 'right' value to this filter.
     *
     * @param rightValue Expression for 'right' value.
     *
     * @throws IllegalFilterException Filter is not internally consistent.
     *
     * @task REVISIT: make immutable.
     */
    public final void addRightValue(Expression rightValue)
        throws IllegalFilterException {
       
    	setExpression2(rightValue);
    }

    public void setExpression2(org.opengis.filter.expression.Expression rightValue) {
    	 // Checks if this is math filter or not and handles appropriately
        if (isMathFilter(filterType)) {
            if (DefaultExpression.isMathExpression(rightValue)
                    || permissiveConstruction) {
                this.expression2 = rightValue;
            } else {
            	
            	throw new IllegalFilterException(
                    "Attempted to add non-math expression to math filter."
    			);
                
            }
        } else {
            this.expression2 = rightValue;
        }
    }
    /**
     * Gets the left expression.
     *
     * @return The expression on the left of the comparison.
     * 
     * * @deprecated use {@link #getExpression1()}
     */
    public final Expression getLeftValue() {
        return (Expression)getExpression1();
    }

    /**
     * Gets the right expression.
     *
     * @return The expression on the right of the comparison.
     *
     * @deprecated use {@link #getExpression2()}
     */
    public final Expression getRightValue() {
        return (Expression)getExpression2();
    }

    /**
     * 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(Feature feature){
    	return evaluate((Object)feature);
    }
  
    /**
     * Subclass convenience method which compares to instances of comparables
     * in a pretty lax way, converting types among String, Number, Double when 
     * appropriate.
     * 
     * @return same contract as {@link Comparable#compareTo(java.lang.Object)}.
     */
    protected int compare (Comparable leftObj, Comparable rightObj) {
    	//implements a lax compare, doing some back flips for numbers
    	if (!(leftObj instanceof Number && rightObj instanceof Number)) 
    	{	
    		//check for case of one number one string
    		if (!(leftObj.getClass() == rightObj.getClass()))  
    	    {
        		//differnt classes, if numbers lets try and match them up
    	    	if ( (Number.class.isAssignableFrom( leftObj.getClass() )) && (rightObj.getClass() == String.class) )
    	    	{
    	    		try{
    	    			rightObj = new Double( Double.parseDouble( (String) rightObj ));
    	    			leftObj  = new Double(  ((Number) leftObj).doubleValue() );
    	    		}
    	    		catch(Exception e)
    				{
    			    	leftObj = leftObj.toString();
    			    	rightObj = rightObj.toString();
    				}
    	    	}
    	    	else if ( (leftObj.getClass() == String.class) && (Number.class.isAssignableFrom( rightObj.getClass() )) )
    	    	{
    	    		try{
    	    			leftObj = new Double( Double.parseDouble( (String) leftObj ) );
    	    			rightObj  = new Double(  ((Number) rightObj).doubleValue() );
    	    		}
    	    		catch(Exception e)
    				{
    			    	leftObj = leftObj.toString();
    			    	rightObj = rightObj.toString();
    				}
    	    	}
    	    	else
    	    	{
    	    		leftObj = leftObj.toString();
    	    		rightObj = rightObj.toString();
    	    	}
    	    }
    	}
    	else {
    		//both numbers, make double
    		leftObj = new Double(((Number)leftObj).doubleValue());
    		rightObj = new Double(((Number)rightObj).doubleValue());
    	}
    	
    	return leftObj.compareTo(rightObj);
    }
    
    /**
     * Returns a string representation of this filter.
     *
     * @return String representation of the compare filter.
     */
    public String toString() {
        String operator = null;

        if (filterType == COMPARE_EQUALS) {
            operator = " = ";
        }

        if (filterType == COMPARE_LESS_THAN) {
            operator = " < ";
        }

        if (filterType == COMPARE_GREATER_THAN) {
            operator = " > ";
        }

        if (filterType == COMPARE_LESS_THAN_EQUAL) {
            operator = " <= ";
        }

        if (filterType == COMPARE_GREATER_THAN_EQUAL) {
            operator = " >= ";
        }

        if (filterType == COMPARE_NOT_EQUALS) {
            operator = " != ";
        }
        return "[ " + expression1 + operator + expression2 + " ]";
    }

    /**
     * Compares this filter to the specified object.  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 both of the values.
     *
     * @param obj - the object to compare this CompareFilter against.
     *
     * @return true if specified object is equal to this filter; false
     *         otherwise.
     */
    public boolean equals(Object obj) {
        if (obj instanceof CompareFilter) {
            CompareFilter cFilter = (CompareFilter) obj;

            // todo - check for nulls here, or make immutable.
            //
            return
                filterType == cFilter.getFilterType()
            	&& (    expression1 == cFilter.getLeftValue()
            			|| (expression1 != null && expression1.equals( cFilter.getLeftValue() ) )
            	    )
            	&& (    expression2 == cFilter.getRightValue()
            			|| (expression2 != null && expression2.equals( cFilter.getRightValue() ) )
            			);            
        } else {
            return false;
        }
    }

    /**
     * Override of hashCode method.
     *
     * @return a code to hash this object by.
     */
    public int hashCode() {
        int result = 17;
        result = (37 * result) + filterType;
        result = (37 * result)
            + ((expression1 == null) ? 0 : expression1.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 abstract Object accept(FilterVisitor visitor, Object extraData);
}
