/*
 *    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.AttributeType;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureType;
import org.geotools.feature.visitor.AverageVisitor.AverageResult;
import org.geotools.feature.visitor.CountVisitor.CountResult;
import org.geotools.filter.IllegalFilterException;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;


/**
 * Calculates the Sum of an attribute (of a FeatureVisitor)
 *
 * @author Cory Horner, Refractions
 *
 * @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/SumVisitor.java $
 */
public class SumVisitor implements FeatureCalc {
    private Expression expr;
    SumStrategy strategy;

    public SumVisitor(int attributeTypeIndex, FeatureType type)
        throws IllegalFilterException {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        AttributeType attributeType = type.getAttributeType(attributeTypeIndex);
        expr = factory.property(attributeType.getName());
        createStrategy(attributeType.getType());
    }

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

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

    /**
     * Factory method
     *
     * @param type The Class of the attributeType
     *
     * @return The correct strategy class (which returns the correct data type)
     */
    private static SumStrategy createStrategy(Class type) {
        if (type == Integer.class) {
            return new IntegerSumStrategy();
        } else if (type == Double.class) {
            return new DoubleSumStrategy();
        } else if (type == Long.class) {
            return new LongSumStrategy();
        } else if (type == Float.class) {
            return new FloatSumStrategy();
        }

        return null;
    }

    public void visit(Feature feature) {
        Object value = expr.evaluate(feature);

        if (strategy == null) {
            strategy = createStrategy(value.getClass());
        }

        strategy.add(value);
    }

    public Expression getExpression() {
        return expr;
    }

    public Object getSum() {
        return strategy.getResult();
    }
    
    public void setValue(Object newSum) {
    	strategy = createStrategy(newSum.getClass());
    	strategy.add(newSum);
    }
    
    public void reset() {
        strategy = null;
    }

    public CalcResult getResult() {
        return new SumResult(strategy);
    }

    interface SumStrategy {
        public void add(Object value);

        public Object getResult();
    }

    static class DoubleSumStrategy implements SumStrategy {
        double number = 0;

        public void add(Object value) {
            number += ((Number) value).doubleValue();
        }

        public Object getResult() {
            return new Double(number);
        }
    }

    static class FloatSumStrategy implements SumStrategy {
        float number = 0;

        public void add(Object value) {
            number += ((Number) value).floatValue();
        }

        public Object getResult() {
            return new Float(number);
        }
    }

    static class LongSumStrategy implements SumStrategy {
        long number = 0;

        public void add(Object value) {
            number += ((Number) value).longValue();
        }

        public Object getResult() {
            return new Long(number);
        }
    }

    static class IntegerSumStrategy implements SumStrategy {
        int number = 0;

        public void add(Object value) {
            number += ((Number) value).intValue();
        }

        public Object getResult() {
            return new Integer(number);
        }
    }

    public static class SumResult extends AbstractCalcResult {
        private SumStrategy sum;
        
        public SumResult(SumStrategy newSum) {
            sum = newSum;
        }

        public Object getValue() {
            return sum.getResult();
        }

        public boolean isCompatible(CalcResult targetResults) {
            if (targetResults instanceof SumResult) return true;
            if (targetResults instanceof CountResult) return true;
            return false;
        }

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

            if (resultsToAdd instanceof SumResult) {
                //create a new strategy object of the correct dataType
            	Number[] sums = new Number[2];
            	sums[0] = (Number) sum.getResult(); sums[1] = (Number) resultsToAdd.getValue();
            	SumStrategy newSum = createStrategy(CalcUtil.getObject(sums).getClass());
            	//add the two sums
            	newSum.add(sums[0]);
            	newSum.add(sums[1]);
                return new SumResult(newSum);
            } else if (resultsToAdd instanceof CountResult) {
                //SumResult + CountResult = AverageResult
                int count = resultsToAdd.toInt();
                AverageResult newResult = new AverageResult(count, sum.getResult());

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