package com.jidesoft.utils;

import com.jidesoft.range.IntegerRange;
import com.jidesoft.range.LongRange;
import com.jidesoft.range.NumericRange;
import com.jidesoft.range.Range;

import java.util.ArrayList;
import java.util.List;

import static java.lang.Double.*;

/**
 * A collection of several util methods related to Math. We only used it in DefaultSummaryCalculator in JIDE Pivot Grid
 * to calculate statistics but this class will be reserved as a place holder for methods related to Math.
 */
public final class MathUtils {
    protected MathUtils() {
    }

    /**
     * Returns the sum number in the numbers list.
     *
     * @param numbers the numbers to calculate the sum.
     * @return the sum of the numbers.
     */
    public static double sum(List<Number> numbers) {
        double sum = 0;
        for (Number value : numbers) {
            sum += value.doubleValue();
        }
        return sum;
    }

    /**
     * Returns the mean number in the numbers list.
     *
     * @param numbers the numbers to calculate the mean.
     * @return the mean of the numbers.
     */
    public static double mean(List<Number> numbers) {
        double sum = sum(numbers);
        return sum / numbers.size();
    }

    /**
     * Returns the min number in the numbers list.
     *
     * @param numbers the numbers to calculate the min.
     * @return the min number in the numbers list.
     */
    public static double min(List<Number> numbers) {
        double min = MAX_VALUE;
        for (Number value : numbers) {
            double v = value.doubleValue();
            if (v < min) {
                min = v;
            }
        }
        return min;
    }

    /**
     * Returns the max number in the numbers list.
     *
     * @param numbers the numbers to calculate the max.
     * @return the max number in the numbers list.
     */
    public static double max(List<Number> numbers) {
        double max = MIN_VALUE;
        for (Number value : numbers) {
            double v = value.doubleValue();
            if (v > max) {
                max = v;
            }
        }
        return max;
    }

    /**
     * Returns the standard deviation of the numbers.
     * <p/>
     * Double.NaN is returned if the numbers list is empty.
     *
     * @param numbers       the numbers to calculate the standard deviation.
     * @param biasCorrected true if variance is calculated by dividing by n - 1. False if by n. stddev is a sqrt of the
     *                      variance.
     * @return the standard deviation
     */
    public static double stddev(List<Number> numbers, boolean biasCorrected) {
        double stddev = NaN;
        int n = numbers.size();
        if (n > 0) {
            if (n > 1) {
                stddev = Math.sqrt(var(numbers, biasCorrected));
            }
            else {
                stddev = 0.0;
            }
        }
        return stddev;
    }

    /**
     * Computes the variance of the available values. By default, the unbiased "sample variance" definitional formula is
     * used: variance = sum((x_i - mean)^2) / (n - 1)
     * <p/>
     * The "population variance"  ( sum((x_i - mean)^2) / n ) can also be computed using this statistic.  The
     * <code>biasCorrected</code> property determines whether the "population" or "sample" value is returned by the
     * <code>evaluate</code> and <code>getResult</code> methods. To compute population variances, set this property to
     * <code>false</code>.
     *
     * @param numbers       the numbers to calculate the variance.
     * @param biasCorrected true if variance is calculated by dividing by n - 1. False if by n.
     * @return the variance of the numbers.
     */
    public static double var(List<Number> numbers, boolean biasCorrected) {
        int n = numbers.size();
        if (n == 0) {
            return NaN;
        }
        else if (n == 1) {
            return 0d;
        }
        double mean = mean(numbers);
        List<Number> squares = new ArrayList<Number>();
        for (Number number : numbers) {
            double XminMean = number.doubleValue() - mean;
            squares.add(Math.pow(XminMean, 2));
        }
        double sum = sum(squares);
        return sum / (biasCorrected ? (n - 1) : n);

    }

    /**
     * Returns the range of numbers.
     *
     * @param numbers the numbers to calculate the range.
     * @return the range of the numbers.
     */
    public static Range<Double> range(List<Number> numbers) {
        double min = MAX_VALUE;
        double max = MIN_VALUE;
        for (Number value : numbers) {
            double v = value.doubleValue();
            if (v < min) {
                min = v;
            }
            if (v > max) {
                max = v;
            }
        }
        return new NumericRange(min, max);
    }

    /**
     * Returns the range of numbers.
     *
     * @param numbers the numbers to calculate the range.
     * @return the range of the numbers.
     */
    public static Range<Integer> rangeInteger(List<Number> numbers) {
        double min = Integer.MAX_VALUE;
        double max = Integer.MIN_VALUE;
        for (Number value : numbers) {
            double v = value.doubleValue();
            if (v < min) {
                min = v;
            }
            if (v > max) {
                max = v;
            }
        }
        return new IntegerRange((int) min, (int) max);
    }

    /**
     * Returns the range of numbers.
     *
     * @param numbers the numbers to calculate the range.
     * @return the range of the numbers.
     */
    public static Range<Long> rangeLong(List<Number> numbers) {
        double min = Long.MAX_VALUE;
        double max = Long.MIN_VALUE;
        for (Number value : numbers) {
            double v = value.doubleValue();
            if (v < min) {
                min = v;
            }
            if (v > max) {
                max = v;
            }
        }
        return new LongRange((long) min, (long) max);
    }
}
