/*
 *    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 java.awt.Dialog;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.geotools.util.MapEntry;
import org.opengis.filter.And;
import org.opengis.filter.Not;
import org.opengis.filter.Or;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.PropertyIsGreaterThan;
import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
import org.opengis.filter.PropertyIsLessThan;
import org.opengis.filter.PropertyIsLessThanOrEqualTo;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNotEqualTo;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Add;
import org.opengis.filter.expression.Divide;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Multiply;
import org.opengis.filter.expression.Subtract;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.Beyond;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Crosses;
import org.opengis.filter.spatial.DWithin;
import org.opengis.filter.spatial.Disjoint;
import org.opengis.filter.spatial.Equals;
import org.opengis.filter.spatial.Intersects;
import org.opengis.filter.spatial.Overlaps;
import org.opengis.filter.spatial.Touches;
import org.opengis.filter.spatial.Within;



/**
 * Represents the Filter capabilities that are supported by a SQLEncoder.  Each
 * SQLEncoder class should have one static FilterCapabilities, representing
 * the filter encoding operations that it can successfully perform.
 *
 * @author Chris Holmes, TOPP
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/filter/FilterCapabilities.java $
 */
public class FilterCapabilities {
	/**
	 * Mask for no operation
	 */
    public static final long NO_OP = 0;

    /**
     * Mask for Filter.INCLUDE
     */
    public static final long NONE = 0x01<<30;

    /**
     * Mask for Filter.EXCLUDE
     */
    public static final long ALL = 0x01<<31;
    
    // spatial masks
	/**
	 * Spatial Mask for bbox operation
	 */
    public static final long SPATIAL_BBOX = 0x01;
	/**
	 * Spatial Mask for equals operation
	 */
    public static final long SPATIAL_EQUALS = 0x01<<1;
	/**
	 * Spatial Mask for disjoint operation
	 */
    public static final long SPATIAL_DISJOINT = 0x01<<2;
	/**
	 * Spatial Mask for intersect operation
	 */
    public static final long SPATIAL_INTERSECT = 0x01<<3;
	/**
	 * Spatial Mask for touches operation
	 */
    public static final long SPATIAL_TOUCHES = 0x01<<4;
	/**
	 * Spatial Mask for crosses operation
	 */
    public static final long SPATIAL_CROSSES = 0x01<<5;
	/**
	 * Spatial Mask for within operation
	 */
    public static final long SPATIAL_WITHIN = 0x01<<6;
	/**
	 * Spatial Mask for contains operation
	 */
    public static final long SPATIAL_CONTAINS = 0x01<<7;
	/**
	 * Spatial Mask for overlaps operation
	 */
    public static final long SPATIAL_OVERLAPS = 0x01<<8;
	/**
	 * Spatial Mask for beyond operation
	 */
    public static final long SPATIAL_BEYOND = 0x01<<9;
	/**
	 * Spatial Mask for dwithin operation
	 */
    public static final long SPATIAL_DWITHIN = 0x01<<10;

    //scalar masks
	/**
	 * Scalar Mask for like operation
	 */
    public static final long LIKE = 0x01<<11;
	/**
	 * Scalar Mask for between opelongion
	 */
    public static final long BETWEEN = 0x01<<12;
	/**
	 * Scalar Mask for null check operation
	 */
    public static final long NULL_CHECK = 0x01<<13;
	/**
	 * Scalar Mask for simple arithmetic operations
	 */
    public static final long SIMPLE_ARITHMETIC = 0x01<<14;
	/**
	 * Scalar Mask for function operations
	 */
    public static final long FUNCTIONS = 0x01<<15;

    // masks for different comparison filters
	public static final long COMPARE_EQUALS = 0x01<<16;

	public static final long COMPARE_GREATER_THAN = 0x01<<17;
	
	public static final long COMPARE_GREATER_THAN_EQUAL = 0x01<<18;

	public static final long COMPARE_LESS_THAN = 0x01<<19;

	public static final long COMPARE_LESS_THAN_EQUAL = 0x01<<20;

	public static final long COMPARE_NOT_EQUALS = 0x01<<21;

	public static final long FID = 0x01<<22;

	// masks for different logic filters
	public static final long LOGIC_AND = 0x01<<23;

	public static final long LOGIC_NOT = 0x01<<24;

	public static final long LOGIC_OR = 0x01<<25;

	/**
	 * Scalar Mask for logical operation
	 */
    public static final long LOGICAL = (LOGIC_AND|LOGIC_OR|LOGIC_NOT);
	/**
	 * Scalar Mask for simple comparison operations
	 */
    public static final long SIMPLE_COMPARISONS = COMPARE_EQUALS|COMPARE_GREATER_THAN|COMPARE_GREATER_THAN_EQUAL|COMPARE_LESS_THAN|COMPARE_LESS_THAN_EQUAL|COMPARE_NOT_EQUALS;
    
    public static final FilterCapabilities SIMPLE_COMPARISONS_OPENGIS;
    
    public static final FilterCapabilities LOGICAL_OPENGIS;
    
    static final Map/*<int,Class>*/ intTypeToOpenGisTypeMap = new HashMap();
    
    static {
        SIMPLE_COMPARISONS_OPENGIS = new FilterCapabilities();
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsEqualTo.class);
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsGreaterThan.class);
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsGreaterThanOrEqualTo.class);
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsLessThanOrEqualTo.class);
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsLessThan.class);
        SIMPLE_COMPARISONS_OPENGIS.addType(PropertyIsNotEqualTo.class);
        
        LOGICAL_OPENGIS = new FilterCapabilities();
        LOGICAL_OPENGIS.addType(And.class);
        LOGICAL_OPENGIS.addType(Not.class);
        LOGICAL_OPENGIS.addType(Or.class);
        
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_BBOX), BBOX.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_EQUALS), Equals.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_DISJOINT), Disjoint.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_INTERSECT), Intersects.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_TOUCHES), Touches.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_CROSSES), Crosses.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_WITHIN), Within.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_CONTAINS), Contains.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_OVERLAPS), Overlaps.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_BEYOND), Beyond.class);
        intTypeToOpenGisTypeMap.put(new Long(SPATIAL_DWITHIN), DWithin.class);
        intTypeToOpenGisTypeMap.put(new Long(SIMPLE_ARITHMETIC), new Class[] {Add.class, Subtract.class, Multiply.class, Divide.class});
        intTypeToOpenGisTypeMap.put(new Long(FUNCTIONS), Function.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_EQUALS), PropertyIsEqualTo.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_NOT_EQUALS), PropertyIsNotEqualTo.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_GREATER_THAN), PropertyIsGreaterThan.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_GREATER_THAN_EQUAL), PropertyIsGreaterThanOrEqualTo.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_LESS_THAN), PropertyIsLessThan.class);
        intTypeToOpenGisTypeMap.put(new Long(COMPARE_LESS_THAN_EQUAL), PropertyIsLessThanOrEqualTo.class);
        intTypeToOpenGisTypeMap.put(new Long(NULL_CHECK), PropertyIsNull.class);
        intTypeToOpenGisTypeMap.put(new Long(LIKE), PropertyIsLike.class);
        intTypeToOpenGisTypeMap.put(new Long(BETWEEN), PropertyIsBetween.class);
        intTypeToOpenGisTypeMap.put(new Long(LOGIC_AND), And.class);
        intTypeToOpenGisTypeMap.put(new Long(LOGIC_OR), Or.class);
        intTypeToOpenGisTypeMap.put(new Long(LOGIC_NOT), Not.class);
    }
    
    private long ops = NO_OP;

	private Set functions=new HashSet();

    public FilterCapabilities(long filterCapabilitiesType) {
		addType(filterCapabilitiesType);
	}

	public FilterCapabilities() {
		this(NO_OP);
	}

    public FilterCapabilities(Class type) {
    	addType(type); 
	}

	/**
     * Adds a new support type to capabilities.
     *
     * @param type The one of the masks enumerated in this class
     */
    public void addType( long type ) {
        // avoid infinite recursion with addType(class)
        if((ops & type) != 0)
            return;
        
        ops = ops | type;
        // the type can be a mask signifying multiple filter types, we have to add them all
        for (Iterator it = intTypeToOpenGisTypeMap.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            long key = ((Long) entry.getKey()).longValue();
            if((key & type) != 0) {
                Object filter = entry.getValue();
                if(filter != null) {
                    if(filter instanceof Class[]) {
                        Class[] filters = (Class[]) filter;
                        for (int i = 0; i < filters.length; i++) {
                            addType(filters[i], false);
                        }
                    } else {
                        addType((Class) filter, false);
                    }
                }
            }
        }
    }    
    
    /**
     * Adds a new support type to capabilities.  For 2.2 only function expression support is added this way.
     * As of geotools 2.3 this will be the supported way of adding to Filtercapabilities.
     * 
     * @param type the Class that indicates the new support.
     */
    public void addType( Class type ){
    	if( org.opengis.filter.Filter.class.isAssignableFrom(type)
                ||
            org.opengis.filter.expression.Expression.class.isAssignableFrom(type)){
			addType(FUNCTIONS);
    		functions.add(type);
    	}
    }
    
    /**
     * Adds a new support type to capabilities.  For 2.2 only function expression support is added this way.
     * As of geotools 2.3 this will be the supported way of adding to Filtercapabilities.
     * 
     * @param type the Class that indicates the new support.
     */
    public void addType(Class type, boolean addFunctionType ){
        if( org.opengis.filter.Filter.class.isAssignableFrom(type)
                ||
            org.opengis.filter.expression.Expression.class.isAssignableFrom(type)){
            if(addFunctionType) addType(FUNCTIONS);
            functions.add(type);
        }
    }
    
    /**
     * Add all the capabilities in the provided FilterCapabilities to this capabilities.
     * 
     * @param capabilities capabilities to add.
     */
    public void addAll( FilterCapabilities capabilities){
    	addType(capabilities.ops);
    	functions.addAll(capabilities.functions);
    }
    
    /**
     * Adds a new support type to capabilities.
     *
     * @param type The {@link FilterType} type that is supported
     * @deprecated
     * @see #convertFilterTypeToMask(short)
     * @see #addType(long)
     */
    public void addType( short type ) {
        addAll(convertFilterTypeToMask(type));
    }

    /**
     * Returns the mask that is equivalent to the FilterType constant.
     * 
     * @param type a constant from {@link FilterType}
     * @return the mask that is equivalent to the FilterType constant.
     */
	public FilterCapabilities convertFilterTypeToMask(short type) {
		if( type==FilterType.ALL )
			return FilterNameTypeMapping.NO_OP_CAPS;
		if( type==FilterType.NONE )
			return FilterNameTypeMapping.ALL_CAPS;
		Object object = FilterNameTypeMapping.filterTypeToFilterCapabilitiesMap.get(new Short(type));
		return (FilterCapabilities)object;
		
	}
    
 
    /**
     * Determines if the filter passed in is supported.
     *
     * @param filter The Filter to be tested.
     *
     * @return true if supported, false otherwise.
     */
    public boolean supports(org.opengis.filter.Filter filter) {
        for (Iterator ifunc = functions.iterator(); ifunc.hasNext();) {
            if (((Class)ifunc.next()).isAssignableFrom(filter.getClass()))
                return true;
        }
        
        if (functions.contains(filter.getClass()))
            return true;
        
        short filterType = Filters.getFilterType( filter );

        return supports(filterType);
    }

    /**
     * Determines if the filter and all its sub filters are supported.  Is most
     * important for logic filters, as they are the only ones with subFilters.
     * Null filters should not be used here, if nothing should be filtered
     * than Filter.INCLUDE can be used.  Embedded nulls can be a particular
     * source of problems, buried in logic filters.
     *
     * @param filter the filter to be tested.
     *
     * @return true if all sub filters are supported, false otherwise.
     *
     * @throws IllegalArgumentException If a null filter is passed in.  As this
     *         function is recursive a null in a logic filter will also cause
     *         an error.
     */
    public boolean fullySupports(org.opengis.filter.Filter filter) {
        boolean supports = true;

        if (filter == null) {
            throw new IllegalArgumentException("Null filters can not be "
                + "unpacked, did you mean " + "Filter.INCLUDE?");
        }

        short filterType = Filters.getFilterType(filter);

        if (AbstractFilter.isLogicFilter(filterType)) {
            Iterator filters = ((LogicFilter) filter).getFilterIterator();
            org.opengis.filter.Filter testFilter = null;

            //short testFtype = 0;
            while (filters.hasNext()) {
                testFilter = (org.opengis.filter.Filter) filters.next();

                if (!(this.fullySupports(testFilter))) {
                    supports = false;
                    break;
                }
            }
        } else {
            supports = this.supports(filter);
        }

        return supports;
    }
    

    /**
     * Determines if the filter type passed in is supported.
     *
     * @param type The AbstractFilter type to be tested
     *
     * @return true if supported, false otherwise.
     * @deprecated
     */
    public boolean supports( short type ) {
        return supports(convertFilterTypeToMask(type));
    }
    
    public boolean supports(long type) {
    	return (ops & type) == type;
	}
    
    public boolean supports(FilterCapabilities type) {
    	return (ops & type.ops) == type.ops && functions.containsAll(type.functions);
	}
    
    public boolean supports(Class type){
    	return functions.contains(type);
    }
    
    public long getScalarOps() {
        return ops & (SIMPLE_ARITHMETIC|SIMPLE_COMPARISONS|FID|FUNCTIONS|LIKE|LOGICAL|NULL_CHECK|BETWEEN);
    }
    public long getSpatialOps() {
        return ops & (SPATIAL_BBOX|SPATIAL_BEYOND|SPATIAL_CONTAINS|SPATIAL_CROSSES
        		|SPATIAL_DISJOINT|SPATIAL_DWITHIN|SPATIAL_EQUALS|SPATIAL_INTERSECT
        		|SPATIAL_OVERLAPS|SPATIAL_TOUCHES|SPATIAL_WITHIN);
    }
    /**
     * Translates a String into an object that represents the operation
     * 
     * @param name String, operation name
     * 
     * @return one of the {@link FilterCapabilities} constants
     */
	public static FilterCapabilities findOperation(String name) {
		return FilterNameTypeMapping.findOperation(name);
	}
    /**
     * Translates a String into  an object that represents function expression
     * 
     * @param name String, expression name
     * 
     * @return one of the {@link FilterCapabilities} constants
     */
	public static FilterCapabilities findFunction(String name) {
		return FilterNameTypeMapping.findFunction(name);
	}

}
