/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2005-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.feature.visitor;

import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureType;
import org.geotools.filter.IllegalFilterException;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;

/**
 * Calculates the maximum value of an attribute.
 *
 * @author Cory Horner, Refractions Research Inc.
 *
 * @since 2.2.M2
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/visitor/MaxVisitor.java $
 */
public class MaxVisitor implements FeatureCalc {
    private Expression expr;
    Comparable maxvalue;
    Comparable curvalue;
    boolean visited = false;
    int countNull = 0;
    int countNaN = 0;
    
    public MaxVisitor(String attributeTypeName) {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(attributeTypeName);
    }
    
    public MaxVisitor(int attributeTypeIndex, FeatureType type)
        throws IllegalFilterException {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(type.getAttributeType(attributeTypeIndex).getName());
    }

    public MaxVisitor(String attrName, FeatureType type)
        throws IllegalFilterException {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(type.getAttributeType(attrName).getName());
    }

    public MaxVisitor(Expression expr) throws IllegalFilterException {
        this.expr = expr;
    }

    /**
     * Visitor function, which looks at each feature and finds the maximum.
     *
     * @param feature the feature to be visited
     */
    public void visit(Feature feature) {
        Object attribValue = expr.evaluate(feature);

        if (attribValue == null) {
        	countNull++; //increment the null count, but don't store its value            
        	return;
        }

        if (attribValue instanceof Double) {
        	double doubleVal = ((Double) attribValue).doubleValue();
        	if (Double.isNaN(doubleVal) || Double.isInfinite(doubleVal)) {
        		countNaN++; //increment the NaN count, but don't store NaN as the max
        		return;
        	}
        }

        curvalue = (Comparable) attribValue;

        if ((!visited) || (curvalue.compareTo(maxvalue) > 0)) {
            maxvalue = curvalue;
            visited = true;
        }

        // throw new IllegalStateException("Expression is not comparable!");
    }

    /**
     * Get the max value.
     *
     * @return Max value
     *
     * @throws IllegalStateException DOCUMENT ME!
     */
    public Comparable getMax() {
        if (!visited) {
            throw new IllegalStateException(
                "Must visit before max value is ready!");
        }

        return maxvalue;
    }
    
    /**
     * @return the number of features which returned a NaN
     */
    public int getNaNCount() {
    	return countNaN;
    }
    
    /**
     * @return the number of features which returned a null
     */
    public int getNullCount() {
    	return countNull;
    }

    public void reset() {
        /**
         * Reset the count and current maximum
         */
        this.visited = false;
        this.maxvalue = new Integer(Integer.MIN_VALUE);
        this.countNaN = 0;
        this.countNull = 0;
    }

    public Expression getExpression() {
        return expr;
    }

    public CalcResult getResult() {
        if (!visited) {
            throw new IllegalStateException(
                "Must visit before max value is ready!");
        }

        return new MaxResult(maxvalue);
    }

    /**
     * Overwrites the result stored by the visitor. This should only be used by
     * optimizations which will tell the visitor the answer rather than
     * visiting all features.
     * 
     * <p></p>
     * For 'max', the value stored is of type 'Comparable'.
     *
     * @param result
     */
    public void setValue(Object result) {
        visited = true;
        maxvalue = (Comparable) result;
    }

    public static class MaxResult extends AbstractCalcResult {
        private Comparable maxValue;

        public MaxResult(Comparable newMaxValue) {
            maxValue = newMaxValue;
        }

        public Object getValue() {
            Comparable max = (Comparable) maxValue;

            return max;
        }

        public boolean isCompatible(CalcResult targetResults) {
            //list each calculation result which can merge with this type of result
            if (targetResults instanceof MaxResult) {
                return true;
            }

            return false;
        }

        public CalcResult merge(CalcResult resultsToAdd) {
            if (!isCompatible(resultsToAdd)) {
                throw new IllegalArgumentException(
                    "Parameter is not a compatible type");
            }

            if (resultsToAdd instanceof MaxResult) {
                //take the smaller of the 2 values
                Comparable toAdd = (Comparable) resultsToAdd.getValue();
                Comparable newMax = maxValue;

                if (newMax.getClass() != toAdd.getClass()) { //2 different data types, therefore convert
                	Class bestClass = CalcUtil.bestClass(new Object[] {toAdd, newMax});
            		if (bestClass != toAdd.getClass())
            			toAdd = (Comparable) CalcUtil.convert(toAdd, bestClass);
            		if (bestClass != newMax.getClass())
            			newMax = (Comparable) CalcUtil.convert(newMax, bestClass);
                }
                if (newMax.compareTo(toAdd) < 0) {
                    newMax = toAdd;
                }

                return new MaxResult(newMax);
            } else {
                throw new IllegalArgumentException(
                    "The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.");
            }
        }
    }
}
