/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002, 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.data.geometryless.filter;

import java.io.IOException;
import java.util.logging.Logger;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

import org.geotools.data.DataSourceException;
import org.geotools.data.jdbc.FilterToSQL;
import org.opengis.filter.ExcludeFilter;
//import org.opengis.filter.FilterVisitor;
import org.opengis.filter.Id;
import org.opengis.filter.IncludeFilter;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
// import org.geotools.filter.LiteralExpression;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;

import org.geotools.filter.FilterCapabilities;

/**
 * Encodes a filter into a SQL WHERE statement for generic SQL.  This class adds
 * the ability to turn geometry filters into sql statements if they are
 * based on xmin,ymin, xmax, ymax (longitude/latitude) columns ..
 *
 * @author Rob Atkinson , SCO
 * 
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/geometryless/src/main/java/org/geotools/data/geometryless/filter/SQLEncoderBBOX.java $
 */
public class SQLEncoderBBOX extends FilterToSQL {
    /** Standard java logger */
    private static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");

 
    /**
     * The srid of the schema, so the bbox conforms.  Could be better to have
     * it in the bbox filter itself, but this works for now.
     */
    private int srid;

  // names of sql addressable columns containing numerical coordinates
    private String XMinColumnName,YMinColumnName = null;
    private String XMaxColumnName,YMaxColumnName = null;
     private String geomName = null;
     
    /** The geometry attribute to use if none is specified. */
    private String defaultGeom;

    /**
     * Empty constructor TODO: rethink empty constructor, as BBOXes _need_ an
     * SRID, must make client set it somehow.  Maybe detect when encode is
     * called?
     */
    public SQLEncoderBBOX(String minx, String miny, String maxx, String maxy) {
        capabilities = createFilterCapabilities();
        this.XMinColumnName = minx;
        this.YMinColumnName = miny;
        this.XMaxColumnName = maxx;
        this.YMaxColumnName = maxy;
        this.geomName = geomName;

        setSqlNameEscape("");
    }

    public SQLEncoderBBOX(int srid) {
        this.srid = srid;
    }

    /**
     * @see org.geotools.filter.SQLEncoder#createFilterCapabilities()
     */
    protected FilterCapabilities createFilterCapabilities() {
        FilterCapabilities capabilities = new FilterCapabilities();


        capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
        capabilities.addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
        capabilities.addType(PropertyIsNull.class);
        capabilities.addType(PropertyIsBetween.class);
        capabilities.addType(Id.class);
        capabilities.addType(IncludeFilter.class);
        capabilities.addType(ExcludeFilter.class);
        capabilities.addType(PropertyIsLike.class);

        capabilities.addType(BBOX.class);
        

        return capabilities;
    }

    /**
     * Sets a spatial reference system ESPG number, so that the geometry can be
     * properly encoded for postgis.  If geotools starts actually creating
     * geometries with valid srids then this method will no longer be needed.
     *
     * @param srid the integer code for the EPSG spatial reference system.
     */
    public void setSRID(int srid) {
        this.srid = srid;
    }

    /**
     * Sets the default geometry, so that filters with null for one of their
     * expressions can assume that the default geometry is intended.
     *
     * @param name the name of the default geometry Attribute.
     *
     * @task REVISIT: pass in a featureType so that geometries can figure out
     *       their own default geometry?
     */
    public void setDefaultGeometry(String name) {
        //Do we really want clients to be using malformed filters?  
        //I mean, this is a useful method for unit tests, but shouldn't 
        //fully formed filters usually be used?  Though I guess adding 
        //the option wouldn't hurt. -ch
        this.defaultGeom = name;
    }

    /**
     * Turns a geometry filter into the postgis sql bbox statement.
     *
     * @param filter the geometry filter to be encoded.
     *
     * @throws RuntimeException for IO exception (need a better error)
     */
    public Object visitBinarySpatialOperator(BinarySpatialOperator filter, Object extraData) throws RuntimeException {
 
        if (filter instanceof BBOX) {
            Expression left = (Expression) filter.getExpression1();
            Expression right = (Expression) filter.getExpression2();
            
            PropertyName propertyExpr;
            Literal geomLiteralExpr;
            
            // left and right have to be valid expressions
            try {
                if (left instanceof PropertyName &&
                        right instanceof Literal) {
                        propertyExpr = (PropertyName)left;
                        geomLiteralExpr = (Literal)right;
                    } else if (right instanceof PropertyName &&
                               left instanceof Literal) {
                        propertyExpr = (PropertyName) right;
                        geomLiteralExpr = (Literal) left;
                    } else {
                        String err = "LocationsXY currently supports one geometry and one " +
                            "attribute expr.  You gave: " + left + ", " + right;
                        throw new DataSourceException(err);
                    }
 
                visitLiteralGeometry(geomLiteralExpr);
                
            } catch (java.io.IOException ioe) {
                LOGGER.warning("Unable to export filter" + ioe);
            }
        } else {
            LOGGER.warning("exporting unknown filter type, only bbox supported");
            throw new RuntimeException("Only BBox is currently supported");
        }
        
        return extraData;
    }

    /**
     * Checks to see if the literal is a geometry, and encodes it if it  is, if
     * not just sends to the parent class.
     *
     * @param expression the expression to visit and encode.
     *
     * @throws IOException for IO exception (need a better error)
     */
    public void visitLiteralGeometry(Literal expression)
        throws IOException {
        Geometry bbox = (Geometry) expression.getValue();
        Envelope e = bbox.getEnvelopeInternal();
        double x1 = e.getMinX();
       double x2 = e.getMaxX();
        double y1 = e.getMinY();
        double y2 = e.getMaxY();
       out.write( "( " +  XMinColumnName  + " < " + x2 + " and " + XMaxColumnName + " > " + x1 + " and " +  YMinColumnName  + " < " + y2 + " and " + YMaxColumnName + " > " + y1 + " )"        );
    }
}
