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


/**
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/visitor/CalcUtil.java $
 */
public class CalcUtil {

	/**
     * Sums an array of numbers together while using the correct class type.
     *
     * @param numbers
     *
     * @return the sum contained in the most appropriate number class
     */
    static Number sum(Number[] numbers) {
        Number newSum = (Number) getObject(numbers);

        if (newSum == null) {
            return null;
        }

        //Integer, Long, Float, Double
        if (newSum instanceof Integer) {
            int sum = 0;
            int nextValue;

            for (int i = 0; i < numbers.length; i++) {
                nextValue = numbers[i].intValue();
                sum += nextValue;
            }

            newSum = new Integer(sum);
        } else if (newSum instanceof Long) {
            long sum = 0;
            long nextValue;

            for (int i = 0; i < numbers.length; i++) {
                nextValue = numbers[i].longValue();
                sum += nextValue;
            }

            newSum = new Long(sum);
        } else if (newSum instanceof Float) {
            float sum = 0;
            float nextValue;

            for (int i = 0; i < numbers.length; i++) {
                nextValue = numbers[i].floatValue();
                sum += nextValue;
            }

            newSum = new Float(sum);
        } else if (newSum instanceof Double) {
            double sum = 0;
            double nextValue;

            for (int i = 0; i < numbers.length; i++) {
                nextValue = numbers[i].doubleValue();
                sum += nextValue;
            }

            newSum = new Double(sum);
        } else {
            return null;
        }

        return newSum;
    }

    /**
     * Divides num1 by num2, and return the result in the correct number class.
     * 
     * @param num1 numerator
     * @param num2 denominator
     * @return num1/num2 in the most appropriate class
     */
    static Number divide(Number num1, Number num2) {
        Number[] both = new Number[2];
        both[0] = num1;
        both[1] = num2;

        Number division = (Number) getObject(both);

        if (division == null) {
            return null;
        }

        //Integer, Long, Float, Double
        if (division instanceof Integer) {
            //we've got 2 integers, but we're going to use double anyways
            return new Double(num1.doubleValue() / num2.doubleValue());
        } else if (division instanceof Long) {
            return new Long(num1.longValue() / num2.longValue());
        } else if (division instanceof Float) {
            return new Float(num1.floatValue() / num2.floatValue());
        } else if (division instanceof Double) {
            return new Double(num1.doubleValue() / num2.doubleValue());
        } else {
            return null;
        }
    }

    /**
     * Calculates the average, and returns it in the correct class.
     * @param numbers
     */
    static Number average(Number[] numbers) {
        Number sum = sum(numbers);

        return divide(sum, new Integer(numbers.length));
    }

    /**
     * Determines the most appropriate class to use for a multiclass calculation.
     * 
     * @param objects
     * @return the most
     */static Class bestClass(Object[] objects) {
        boolean hasInt = false;
        boolean hasFloat = false;
        boolean hasLong = false;
        boolean hasDouble = false;
        boolean hasString = false;

        for (int i = 0; i < objects.length; i++) {
            if (objects[i] instanceof Double) {
                hasDouble = true;
            } else if (objects[i] instanceof Float) {
                hasFloat = true;
            } else if (objects[i] instanceof Long) {
                hasLong = true;
            } else if (objects[i] instanceof Integer) {
                hasInt = true;
            } else if (objects[i] instanceof String) {
                hasString = true;
            }
        }

        if (hasString) {
            return String.class;
        } else if (hasDouble) {
            return Double.class;
        } else if (hasFloat) {
            return Float.class;
        } else if (hasLong) {
            return Long.class;
        } else if (hasInt) {
            return Integer.class;
        } else { //it's a type we don't have here yet

            return null;
        }
    }

    /**
     * Casts an object to the specified type
     *
     * @param var
     * @param type
     *
     */
    static Object convert(Object var, Class type) {
        if (var instanceof Number) { //use number conversion

            Number newNum = (Number) var;

            if (type == Integer.class) {
                return new Integer(newNum.intValue());
            } else if (type == Long.class) {
                return new Long(newNum.longValue());
            } else if (type == Float.class) {
                return new Float(newNum.floatValue());
            } else if (type == Double.class) {
                return new Double(newNum.doubleValue());
            } else if (type == String.class) {
                return new String(newNum.toString());
            }
        } else { //direct cast

            if (type == Integer.class) {
                return new Integer(((Integer) var).intValue());
            } else if (type == Long.class) {
                return new Long(((Long) var).longValue());
            } else if (type == Float.class) {
                return new Float(((Float) var).floatValue());
            } else if (type == Double.class) {
                return new Double(((Double) var).doubleValue());
            } else if (type == String.class) {
                return new String(var.toString());
            }
        }

        return null;
    }

    static Object convert(Object[] objects, Object var) {
        Object newVar = getObject(objects);

        if (newVar instanceof Number) {
            Number newNum = (Number) var;

            if (newVar instanceof Integer) {
                return new Integer(newNum.intValue());
            } else if (newVar instanceof Long) {
                return new Long(newNum.longValue());
            } else if (newVar instanceof Float) {
                return new Float(newNum.floatValue());
            } else if (newVar instanceof Double) {
                return new Double(newNum.doubleValue());
            } else {
                return null;
            }
        } else if (newVar instanceof String) {
            return new String((String) newVar);
        } else {
            //TODO: add other classes
            return null;
        }
    }

    /**
     * Given an array of objects, traverses the array and determines the most
     * suitable data type to perform the calculation in. An empty object of
     * the correct class is returned;
     *
     * @param objects
     *
     */
    static Object getObject(Object[] objects) {
    	Class bestClass = bestClass(objects);
    	
        if (bestClass == String.class) {
            return new String(""); //$NON-NLS-1$
        } else if (bestClass == Double.class) {
            return new Double(0);
        } else if (bestClass == Float.class) {
            return new Float(0);
        } else if (bestClass == Float.class) {
            return new Long(0);
        } else if (bestClass == Integer.class) {
            return new Integer(0);
        } else { //it's a type we don't have here yet
            return null;
        }
    }

    /**
     * Similar to java.lang.Comparable.compareTo, but can handle 2 different
     * data types.
     *
     * @param val1
     * @param val2
     *
     */
    static int compare(Comparable val1, Comparable val2) {
        if (val1.getClass() == val2.getClass()) {
            //both the same type, no conversion is necessary.
            return val1.compareTo(val2);
        }

        //find most appropriate class
        Object[] objects = new Object[] { val1, val2 };
        Class bestClass = bestClass(objects);

        if (bestClass != val1.getClass()) {
            val1 = (Comparable) convert(val1, bestClass);
        }

        if (bestClass != val2.getClass()) {
            val2 = (Comparable) convert(val2, bestClass);
        }

        //now do the comparison
        return val1.compareTo(val2);
    }
}
