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


// J2SE dependencies
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;


/**
 * Processes messages from clients to create Logic Filters.  Handles nested
 * logic filters.  Filters should call start and end when they reach logic
 * filters, and create when the filter is complete.
 * 
 *   This documenation provided by Dave Blasby April 1/2005 after fixing GEOS-328:
 *  DJB: okay, there's no where near enough comments in here to understand whats going on.  Hopefully I'm correct.
 *       I've looked at this for a bit, and this is what I can figure out.
 * 
 *       This is called by the FilterFilter class (nice name...NOT) which is also a sax parser-like class.
 *       Basically, the FilterFilter does most of the Filter parsing - but it hands most of the work off to the
 *       appropriate classes.  For NOT, AND, OR clauses, this class is used.
 * 
 *       As a simple example, <filter> <OR> [STATE_NAME = 'NY'] [STATE_NAME = 'WA'] </OR></FILTER>
 *        Or, in long form:
 *     <Filter>
 *     <Or>
 *         <PropertyIsEqualTo>
 *            <PropertyName>STATE_NAME</PropertyName>
 *              <Literal>NY</Literal>
 *        	</PropertyIsEqualTo>
 *          <PropertyIsEqualTo>
 *            <PropertyName>STATE_NAME</PropertyName>
 *              <Literal>WA</Literal>
 *        	</PropertyIsEqualTo>
 *     </Or>
 *     </Filter>
 * 
 *   The "PropertyIsEqualTo" is handled by another parser, so we dont have to worry about it here for the moment.
 * 
 *   So, the order of events are like this:
 * 
 *    
 *    start( "OR" )
 *    add([STATE_NAME = 'NY'])   // these are handled by another class 
 *    add([STATE_NAME = 'WA'])   // these are handled by another class 
 *    end ("OR")
 *    create()                   // this creates an actual Filter [[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]]
 *  
 *    This is pretty simple, but it gets more complex when you have nested structures.
 *
 * 
 *       <Filter>
 *       <And>
 *       <Or>
 *           <PropertyIsEqualTo>
 *              <PropertyName>STATE_NAME</PropertyName>
 *                <Literal>NY</Literal>
 *          	</PropertyIsEqualTo>
 *            <PropertyIsEqualTo>
 *              <PropertyName>STATE_NAME</PropertyName>
 *                <Literal>WA</Literal>
 *          	</PropertyIsEqualTo>
 *       </Or>
 *                  <PropertyIsEqualTo>
 *                    <PropertyName>STATE_NAME</PropertyName>
 *                      <Literal>BC</Literal>
 *          	</PropertyIsEqualTo>
 *       </And>
 *       </Filter>
 *
 *     Again, we're going to ignore the "PropertyIsEqualTo" stuff since its handled elsewhere.
 *     
 *      The main idea is that there will be a LogicSAXParser for the top-level "AND" and another one
 *      for the nested "OR".  It gets a bit harder to describe because the classes start passing events
 *      to each other.
 *
 *        start("AND")  -- the parent LogicSAXParser starts to construct an "AND" filter
 *        start("OR")   -- the "AND" parser sees that its sub-element is another logic operator.
 *                         It makes another LogicSAXParser that will handle the "OR" SAX events.
 *        add([STATE_NAME = 'NY']) -- this is sent to the "AND" parser.  It then sends it to the "OR" parser.
 *                                    + "OR" parser remembers this component
 *        add([STATE_NAME = 'WA']) -- this is sent to the "AND" parser.  It then sends it to the "OR" parser.
 *                                    + "OR" parser remembers this component
 *        end("OR") -- this is sent to the "AND" parser.  It then sends it to the "OR" parser.
 *                                    + The "OR" parser marks itself as complete
 *                              + The "AND" parser notices that its child is completed parsing
 *                              + The "AND" parser calls create() on the "OR" parser to make a filter (see next step)
 *                              + Since "OR" is finished, "AND" stop passing events to it.
 *        "OR".create() -- makes a "[[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]]" and "AND" remembers it as a component
 *        add ([ STATE_NAME = BC ]) --This is added as a component to the "AND" filter.
 *        end ("AND")   --  the "AND" parser marks itself as complete
 *        create()      --  the "AND" parser creates a FILTER [[[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]] AND [ STATE_NAME = BC ]]
 *
 *
 *      Higher levels of nesting work the same way - each level will send the event down to the next level.
 *
 *      If logicFilter == null then this object is the one doing the processing.  If its non-null, then
 *      the sub-object is doing the processing - event are sent to it.
 *
 * @author Rob Hranac, Vision for New York
 * @author Chris Holmes, TOPP
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/filter/LogicSAXParser.java $
 * @version $Id: LogicSAXParser.java 27862 2007-11-12 19:51:19Z desruisseaux $
 */
public class LogicSAXParser {
    /** The logger for the filter module. */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");

    /** factory for creating filters. */
    private FilterFactory ff;

    /** AbstractFilter filte type. */
    private short logicType = -1;

    /** The array of filters to be logically joined. */
    private List subFilters = new ArrayList();

    /** An instance of this class for nested logic filter structures. */
    private LogicSAXParser logicFactory = null;

    /** if this logic filter is ready to be created. */
    private boolean isComplete = false;

    /**
     * Constructor which flags the operator as between.
     */
    public LogicSAXParser() {
    	this( FilterFactoryFinder.createFilterFactory() );    	
    }
    /** Constructor injection */
    public LogicSAXParser( FilterFactory factory ){
    	ff = factory;
    	LOGGER.finer("made new logic factory");
    }
    /** Setter injection */
    public void setFilterFactory( FilterFactory factory ){
    	ff = factory;
    }

    /**
     * To be called by a parser to start the creation of a logic filter. Can
     * start a nested or a base logic filter.
     *
     * @param logicType OR, or AND abstract filter type.
     *
     * @throws IllegalFilterException if filter type does not match  declared
     *         type.
     */
     // logic types are AND=2, OR=1, NOT=3
    public void start(short logicType) throws IllegalFilterException {
        LOGGER.finest("got a start element: " + logicType);

        if (this.logicType != -1) {
        	//DJB: for GEOS-328 we need to keep the old parser around to handle multiple nestings of logic operators.
        	if (logicFactory == null) {
        		  logicFactory = new LogicSAXParser();
        		}
            logicFactory.start(logicType);
        } else if (!AbstractFilter.isLogicFilter(logicType)) {
            throw new IllegalFilterException(
                "Add logic filter type does not match declared type.");
        } else {
            this.logicType = logicType;
        }
    }

    /**
     * To be called when the sax parser reaches the end of a logic filter.
     * Tells this class to complete.
     *
     * @param logicType the Filter type.
     *
     * @throws IllegalFilterException If the end message can't be processed in
     *         this state.
     */
     // logic types are AND=2, OR=1, NOT=3
    public void end(short logicType) throws IllegalFilterException {
        LOGGER.finer("got an end element: " + logicType);

        if (logicFactory != null) {
            LOGGER.finer("sending end element to nested logic filter: "
                + logicType);
            logicFactory.end(logicType);

            if (logicFactory.isComplete()) {
                subFilters.add(logicFactory.create());
                logicFactory = null;
            }
        } else if (this.logicType == logicType) {
            LOGGER.finer("end element matched internal type: " + this.logicType);
            isComplete = true;
        } else {
            throw new IllegalFilterException(
                "Logic Factory got an end message that it can't process.");
        }
    }

    /**
     * Adds a filter to the current logic list.
     *
     * @param filter The filter to be added.
     */
    public void add(Filter filter) {
        LOGGER.finer("added a filter: " + filter.toString());

        if (logicFactory != null) {
            LOGGER.finer("adding to nested logic filter: " + filter.toString());
            logicFactory.add(filter);
        } else {
            LOGGER.finer("added to sub filters: " + filter.toString());
            subFilters.add(filter);
        }
    }

    /**
     * Creates the the logic filter if in a complete state.
     *
     * @return The created logic filter.
     *
     * @throws IllegalFilterException if the filter is not complete.
     */
    public Filter create() throws IllegalFilterException {
        LogicFilter filter = null;

        LOGGER.finer("creating a logic filter");

        if (isComplete()) {
            LOGGER.finer("filter is complete, with type: " + this.logicType);

            if (logicType == AbstractFilter.LOGIC_NOT) {
                filter = ff.createLogicFilter((Filter) subFilters.get(
                            0), this.logicType);
            } else {
                filter = ff.createLogicFilter(this.logicType);

                Iterator iterator = subFilters.iterator();

                while (iterator.hasNext()) {
                    filter.addFilter((org.opengis.filter.Filter) iterator.next());
                }
            }

            //reset the variables so it works right if called again.
            subFilters = new ArrayList();
            this.logicType = -1;
            isComplete = false;

            return filter;
        } else {
            throw new IllegalFilterException(
                "Attempted to generate incomplete logic filter.");
        }
    }

    /**
     * indicates if the logic filter is complete.
     *
     * @return <tt>true</tt> if this holds a complete logic filter to be
     *         created, <tt>false</tt> otherwise.
     */
    public boolean isComplete() {
        return isComplete;
    }
}
