package dotnet54.util.math.doubles;

import java.util.Arrays;
import java.util.List;

public class StatisticsDbl {
    public static final double threshold = 1e-10;

    public static double sum(double ... list) {
        double sum = 0;
        for (int i = 0; i < list.length; i++) {
            sum += list[i];
        }
        return sum;
    }

    public static double sum(List<Double> list) {
        double sum = 0;
        int length = list.size();
        for (int i = 0; i < length; i++) {
            sum += list.get(i);
        }
        return sum;
    }

    public static double mean(double ... list) {
        double sum = 0;
        for (int i = 0; i < list.length; i++) {
            sum += list[i];
        }
        return sum/list.length;
    }

    public static double mean(List<Double> list) {
        return sum(list) / list.size();
    }

    public static double varP(double ... list) {
        double mean = mean(list);
        double total = 0;
        for (int i = 0; i < list.length; i++) {
            total += (list[i] - mean) * (list[i] - mean);
        }
        return total/list.length;
    }

    public static double varP(double meanP, double ... list) {
        double total = 0;
        for (int i = 0; i < list.length; i++) {
            total += (list[i] - meanP) * (list[i] - meanP);
        }
        return total/list.length;
    }

    public static double varS(double ... list) {
        double mean = mean(list);
        double total = 0;
        for (int i = 0; i < list.length; i++) {
            total += (list[i] - mean) * (list[i] - mean);
        }
        return total/(list.length-1);
    }

    public static double varS(double meanS, double ... list) {
        double total = 0;
        for (int i = 0; i < list.length; i++) {
            total += (list[i] - meanS) * (list[i] - meanS);
        }
        return total/(list.length-1);
    }

    public static double stdP(double ... list) {
        return Math.sqrt(varP(list));
    }

    public static double stdP(double meanP, double ... list) {
        return Math.sqrt(varP(meanP, list));
    }

    public static double stdS(double ... list) {
        return Math.sqrt(varS(list));
    }

    public static double stdS(double meanS, double ... list) {
        return Math.sqrt(varS(meanS, list));
    }

    public static double median(double ... list) {
        Arrays.sort(list);
        int middle = (list.length / 2);
        if (list.length % 2 == 0) {
            return (list[middle] +  list[middle - 1]) / 2;
        }else{
            return list[middle];
        }
    }

    public static double max(double ... list){
        if (list.length == 0){
            throw new RuntimeException("List is empty");
        }
        double maxValue = list[0];
        for(int i=1;i<list.length;i++){
            if(list[i] > maxValue){
                maxValue = list[i];
            }
        }
        return maxValue;
    }

    public static double min(double ... list){
        if (list.length == 0){
            throw new RuntimeException("List is empty");
        }
        double minValue = list[0];
        for(int i=1;i<list.length;i++){
            if(list[i] < minValue){
                minValue = list[i];
            }
        }
        return minValue;
    }

    public static int argmax(double ... list){
        if (list.length == 0){
            throw new RuntimeException("List is empty");
        }
        double maxValue = list[0];
        int maxIndex = 0;
        for(int i=1;i<list.length;i++){
            if(list[i] > maxValue){
                maxValue = list[i];
                maxIndex = i;
            }
        }
        return maxIndex;
    }

//    public static int argmax_tie_break(Random rand, double ... list){
//        if (list.length == 0){
//            throw new RuntimeException("List is empty");
//        }
//        double maxValue = list[0];
//        for(int i=1;i<list.length;i++){
//            if(list[i] > maxValue){
//                maxValue = list[i];
//            }
//        }
//        return maxValue;
//    }

    public static int argmin(double ... list){
        if (list.length == 0){
            throw new RuntimeException("List is empty");
        }
        double minValue = list[0];
        int minIndex = 0;
        for(int i=1;i<list.length;i++){
            if(list[i] < minValue){
                minValue = list[i];
                minIndex = i;
            }
        }
        return minIndex;
    }

//    public static int argmin_tie_break(Random rand,double ... list){
//        if (list.length == 0){
//            throw new RuntimeException("List is empty");
//        }
//        double minValue = list[0];
//        for(int i=1;i<list.length;i++){
//            if(list[i] < minValue){
//                minValue = list[i];
//            }
//        }
//        return minValue;
//    }

    /**
     * Welford's online algoirithm for variance
     *
     * Reference: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
     *
     * @param aggregate array of 5 values for existing values[count, mean, sqDistanceFromMean, varPopulation. varSample]
     * @param newValue new value to add to the aggregates
     * @return array of 3 values for the next call [count, mean, sqDistanceFromMean, varPopulation. varSample]
     */
    public static double[] varOnline(double[] aggregate, double newValue){
        aggregate[0] += 1; //count += 1
        double delta = newValue - aggregate[1]; //delta = newValue - mean
        aggregate[1] += delta / aggregate[0]; //mean += delta / count
        double delta2 = newValue - aggregate[1]; //delta2 = newValue - mean
        aggregate[2] += delta * delta2; //sqDistanceFromMean += delta * delta2

        if (aggregate[0] < 2){
            return new double[]{1,newValue,0,0,0};
        }else{
            aggregate[3] = aggregate[2] / aggregate[0];  //sqDistanceFromMean / count
            aggregate[4] = aggregate[2] / (aggregate[0] - 1); //sqDistanceFromMean / (count - 1)
            return aggregate;
        }
    }

    /**
     * Welford's online algoirithm for standard deviation
     *
     * Reference: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
     *
     * @param aggregate array of 5 values for existing values[count, mean, sqDistanceFromMean, varPopulation. stdPopulation]
     * @param newValue new value to add to the aggregates
     * @return array of 3 values for the next call [count, mean, sqDistanceFromMean, varPopulation. stdPopulation]
     */
    public static double[] stdvPopulationOnline(double[] aggregate, double newValue){

        if (aggregate == null || aggregate.length != 5){
            aggregate = new double[5];
        }

        aggregate[0] += 1; //count += 1
        double delta = newValue - aggregate[1]; //delta = newValue - mean
        aggregate[1] += delta / aggregate[0]; //mean += delta / count
        double delta2 = newValue - aggregate[1]; //delta2 = newValue - mean
        aggregate[2] += delta * delta2; //sqDistanceFromMean += delta * delta2

        if (aggregate[0] < 2){
            return new double[]{1,newValue,0,0,0};
        }else{
            aggregate[3] = aggregate[2] / aggregate[0];  //sqDistanceFromMean / count
            aggregate[4] = Math.sqrt(aggregate[2] / aggregate[0]); //sqDistanceFromMean / count
            return aggregate;
        }
    }

}
