package math.stat;

import math.utils.FrequencyMap;
import math.utils.MathUtil;
import math.utils.Tuple2d;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class StatUtil {
    public static final int[] dlugoscF = {48, 39, 43, 44, 34, 34, 32, 43, 40,
            46, 25, 31, 34, 49, 39, 37, 45, 48, 41, 49, 43, 46, 34, 35, 42, 32,
            41, 34, 42, 42, 38, 40, 46, 47, 34, 42, 38, 40, 38, 36, 30, 43, 41,
            40, 40, 35, 35, 41, 38, 45, 37, 42, 38, 36, 44, 39, 32, 48, 43, 39,
            43, 30, 44, 36, 42, 34, 49, 49, 49, 51, 37, 30, 50, 48, 44, 35, 45,
            34, 33, 41, 43, 45, 44, 34, 33, 39, 41, 39, 46, 31, 40, 52, 45, 39,
            35, 45, 33, 42, 42, 36, 44, 52, 40, 39, 34, 44, 40, 37, 43, 32, 32,
            42, 45, 35, 37, 43, 48, 48, 50, 32, 40, 48, 32, 43, 36, 39, 42, 40,
            37, 30, 44, 50, 46, 39, 41, 48, 44, 42, 35, 51, 44, 50, 47, 37, 33,
            34, 42, 43, 43, 47};
    public static final int[] wagaF = {3120, 2691, 2752, 3036, 2244, 2210,
            2016, 2752, 2720, 2898, 1650, 2046, 2210, 3381, 2691, 2516, 2925,
            3072, 2583, 3381, 2795, 2898, 2176, 2310, 2898, 2016, 2624, 2244,
            2814, 2772, 2508, 2600, 3128, 3196, 2244, 2898, 2470, 2720, 2508,
            2484, 1950, 2881, 2747, 2520, 2640, 2310, 2205, 2665, 2508, 3015,
            2516, 2898, 2546, 2304, 2904, 2613, 2144, 3312, 2924, 2457, 2752,
            2070, 2948, 2484, 2898, 2244, 3283, 3185, 3234, 3264, 2516, 1950,
            3200, 3168, 2948, 2310, 3060, 2278, 2277, 2706, 2881, 2880, 2860,
            2210, 2244, 2457, 2747, 2496, 2944, 1984, 2600, 3484, 2835, 2574,
            2310, 2835, 2244, 2898, 2646, 2448, 2772, 3432, 2560, 2691, 2278,
            2860, 2520, 2331, 2881, 2176, 2112, 2814, 2835, 2205, 2331, 2752,
            3168, 3312, 3400, 2080, 2720, 3264, 2208, 2967, 2484, 2574, 2646,
            2640, 2553, 1890, 2772, 3450, 2898, 2652, 2829, 3168, 2816, 2688,
            2275, 3468, 2816, 3150, 3243, 2405, 2145, 2244, 2646, 2881, 2881,
            3008};
    public static final int[] dlugoscM = {39, 47, 46, 45, 46, 42, 41, 50, 42,
            36, 41, 39, 36, 52, 32, 52, 44, 37, 48, 47, 46, 37, 35, 52, 43, 47,
            52, 46, 47, 34, 47, 46, 35, 54, 41, 31, 46, 51, 45, 44, 48, 54, 45,
            42, 45, 38, 34, 34, 48, 42, 35, 51, 33, 46, 35, 49, 52, 48, 41, 42,
            43, 43, 39, 42, 41, 43, 51, 43, 49, 39, 43, 45, 38, 36, 48, 38, 53,
            44, 41, 41, 41, 47, 39, 47, 41, 35, 38, 41, 34, 46, 46, 43, 45, 46,
            42, 54, 37, 33, 38, 34, 50, 45, 39, 43, 36, 32, 45, 41, 39, 26, 45,
            45, 46, 47, 38, 46, 47, 37, 44, 52, 47, 46, 35, 37, 36, 42, 48, 49,
            45, 55, 38, 43, 40, 53, 44, 52, 43, 34, 50};
    public static final int[] wagaM = {2613, 3102, 2990, 2925, 3036, 2898,
            2788, 3450, 2814, 2376, 2706, 2691, 2448, 3432, 2080, 3432, 2992,
            2479, 3312, 3149, 3174, 2479, 2415, 3536, 2795, 3243, 3484, 2990,
            3102, 2278, 3055, 3174, 2310, 3726, 2665, 2108, 3082, 3366, 3060,
            2992, 3264, 3510, 3105, 2814, 3060, 2584, 2346, 2312, 3264, 2730,
            2345, 3468, 2244, 3082, 2310, 3185, 3432, 3216, 2747, 2898, 2924,
            2967, 2574, 2856, 2665, 2881, 3315, 2924, 3185, 2652, 2881, 2925,
            2584, 2484, 3312, 2584, 3657, 3036, 2788, 2788, 2788, 3243, 2652,
            3149, 2665, 2345, 2470, 2747, 2278, 2990, 3082, 2967, 2970, 3082,
            2814, 3672, 2516, 2178, 2508, 2244, 3300, 3015, 2613, 2881, 2484,
            2144, 3060, 2829, 2652, 1794, 3060, 3105, 2990, 3149, 2508, 3082,
            3055, 2553, 2860, 3484, 3149, 3128, 2415, 2553, 2448, 2856, 3264,
            3185, 3015, 3630, 2546, 2924, 2640, 3604, 2904, 3484, 2795, 2244,
            3450};

    private StatUtil() {
    }

    //oblicza mediane
    public static double median2(int[] array) {
        Arrays.sort(array);
        int len = array.length;
        double value = 0;
        if (len % 2 == 0) {
            value = ((double) array[(len + 1) / 2] + (double) array[(len + 1) / 2 + 1]) / 2;
        } else {
            value = array[(len + 1) / 2];
        }
        return value;
    }

    // rozstęp - roznica miedzy maksymalna i minimalna wartoscia w tablicy
    public static int r(int[] array) {
        Arrays.sort(array);
        int min = array[0];
        int max = array[array.length - 1];
        return max - min;
    }

    public static double r(double[] array) {
        Arrays.sort(array);
        double min = array[0];
        double max = array[array.length - 1];
        return max - min;
    }

    //liczba przedzialow klasowych
    public static int k(int[] array) {
        int n = array.length;
        return (int) Math.floor(1.0 + 3.3 * MathUtil.lg(n));
    }

    public static int k(double[] array) {
        int n = array.length;
        return (int) Math.floor(1.0 + 3.3 * MathUtil.lg(n));
    }

    //dlugosc przedzialu klasowego
    public static double c(int[] array, int decimals) {
        Arrays.sort(array);
        int r = r(array);
        int k = k(array);
        return MathUtil.roundToDecimal((double) r / (double) k, decimals);
    }

    //decimals - liczba miejsc po przeciku (dokladnosc)
    public static double c(double[] array, int decimals) {
        Arrays.sort(array);
        double r = r(array);
        int k = k(array);
        return MathUtil.roundToDecimal(r / k, decimals);
    }

    public static Tuple2d[] klasy(int[] array) {
        Arrays.sort(array);
        int len = array.length;// dlugosc
        int min = array[0];
        int k = k(array);
        double c = c(array, 1);// dlugosc przedzialu klasowego
        FrequencyMap<Double> farray = new FrequencyMap<>();
        for (int i1 : array) {
            for (int j = 0; j < k; j++) {
                double a = MathUtil.roundToDecimal(min + c * j, 1);
                double b = MathUtil.roundToDecimal((min + c) + c * j, 1);
                if (i1 >= a && i1 < b) {
                    // farray.add(((a.toString()).concat("-")).concat(b));
                    farray.add(MathUtil.roundToDecimal(a + 0.5 * (b - a), 1));
                    break;
                }
            }
        }
        return farray.getAllTuplesD();
    }

    //oblicza wartosc maksymalna
    public static int max(int[] array) {
        Arrays.sort(array);
        return array[array.length - 1];
    }

    public static double max(double[] array) {
        Arrays.sort(array);
        return array[array.length - 1];
    }

    //oblicza wartosc minimalna
    public static int min(int[] array) {
        Arrays.sort(array);
        return array[0];
    }

    public static double min(double[] array) {
        Arrays.sort(array);
        return array[0];
    }

    //oblicza rozstep
    public static int gap(int[] array) {
        Arrays.sort(array);
        int min = array[0];
        int max = array[array.length - 1];
        return max - min;
    }

    public static double gap(double[] array) {
        Arrays.sort(array);
        double min = array[0];
        double max = array[array.length - 1];
        return max - min;
    }

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

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

    //oblicza srednia arytmetyczna szeregu
    public static double mean(int[] array) {
        int i = 0;
        int sum = 0;
        for (i = 0; i < array.length; i++) {
            sum += array[i];
        }
        return (double) sum / (double) array.length;
    }

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

    // trzeba podac dokladnosc (liczbe miejsc po przecinku) doubli
    public static double[] mode(double[] array, int decimal) {
        Arrays.sort(array);
        int i = 0;
        int j = 0;
        int k = 0;
        ArrayList<Integer> ali = new ArrayList<>();
        int numb = 1; // liczba roznych wartosci w tablicy
        for (i = 0; i < array.length - 1; i++) {
            if (!(MathUtil.roundToDecimal(array[i + 1], decimal) == MathUtil
                    .roundToDecimal(array[i], decimal))) {
                numb++;
            }
        }
        double[] liczby = new double[numb];
        int[] freq = new int[numb];
        liczby[j] = array[0];
        freq[k] = 1;
        for (i = 0; i < array.length - 1; i++) {
            if (MathUtil.roundToDecimal(array[i + 1], decimal) == MathUtil
                    .roundToDecimal(array[i], decimal)) {
                freq[k]++;
            } else {
                j++;
                k++;
                liczby[j] = MathUtil.roundToDecimal(array[i + 1], decimal);
                freq[k] = 1;
            }
        }
        for (i = 0; i < freq.length; i++) {
            ali.add(freq[i]);
        }
        int number1 = Collections.max(ali);
        int[] indexes = findIndexes(number1, freq);
        double[] values = new double[indexes.length];
        for (i = 0; i < indexes.length; i++) {
            values[i] = liczby[indexes[i]];
        }
        return values;
    }

    //dominanta
    public static int[] mode(int[] array) {
        Arrays.sort(array);
        int i = 0;
        int j = 0;
        int k = 0;
        ArrayList<Integer> ali = new ArrayList<>();
        int numb = 1; // liczba roznych wartosci w tablicy
        for (i = 0; i < array.length - 1; i++) {
            if (!(array[i + 1] == array[i])) {
                numb++;
            }
        }
        int[] liczby = new int[numb];
        int[] freq = new int[numb];
        liczby[j] = array[0];
        freq[k] = 1;
        for (i = 0; i < array.length - 1; i++) {
            if (array[i + 1] == array[i]) {
                freq[k]++;
            } else {
                j++;
                k++;
                liczby[j] = array[i + 1];
                freq[k] = 1;
            }
        }
        for (i = 0; i < freq.length; i++) {
            ali.add(freq[i]);
        }
        int number1 = Collections.max(ali);
        int[] indexes = findIndexes(number1, freq);
        int[] values = new int[indexes.length];
        for (i = 0; i < indexes.length; i++) {
            values[i] = liczby[indexes[i]];
        }
        return values;
    }

    //wyszukuje wartosc w tablicy i zwraca jej indeks lub indeksy w tablicy
    //(jesli wystepuje wiecej niz jeden raz)
    public static int[] findIndexes(int value, int[] array) {
        int k = 0;
        int freq = 0;
        for (int i1 : array) {
            if (i1 == value) {
                freq++;
            }
        }
        int[] tabl = new int[freq];
        for (int j = 0; j < array.length; j++) {
            if (array[j] == value) {
                tabl[k] = j;
                k++;
            }
        }
        return tabl;
    }

    // trzeba podac dokladnosc(liczba miejsc po przecinku) dla doubli
    public static int[] findIndexes(double value, double[] array, int decimal) {
        int k = 0;
        int freq = 0;
        for (double v : array) {
            if (MathUtil.roundToDecimal(v, decimal) == MathUtil
                    .roundToDecimal(value, decimal)) {
                freq++;
            }
        }
        int[] tabl = new int[freq];
        for (int j = 0; j < array.length; j++) {
            if (MathUtil.roundToDecimal(array[j], decimal) == MathUtil
                    .roundToDecimal(value, decimal)) {
                tabl[k] = j;
                k++;
            }
        }
        return tabl;
    }

    //oblicza percentyl
    public static double percentyl(int perc, int[] array) {
        Arrays.sort(array);
        double miejsce = (array.length + 1) * ((double) perc / 100);
        int index = (int) miejsce;
        double konc = miejsce % index;
        double konc2 = (array[index] - array[index - 1]) * konc;
        return array[index - 1] + konc2;
    }

    public static double percentyl(int perc, double[] array) {
        Arrays.sort(array);
        double miejsce = (array.length + 1) * ((double) perc / 100);
        int index = (int) miejsce;
        double konc = miejsce % index;
        double konc2 = (array[index] - array[index - 1]) * konc;
        return array[index - 1] + konc2;
    }

    //oblicza mediane
    public static double median(int[] array) {
        return percentyl(50, array);
    }

    public static double median(double[] array) {
        return percentyl(50, array);
    }

    // odstep międzykwartylowy
    public static double in_quart_dist(int[] array) {
        Arrays.sort(array);
        double perc1 = percentyl(75, array);
        double perc2 = percentyl(25, array);
        return (perc1 - perc2);
    }

    public static double in_quart_dist(double[] array) {
        Arrays.sort(array);
        double perc1 = percentyl(75, array);
        double perc2 = percentyl(25, array);
        return (perc1 - perc2);
    }

    //wariancja w populacji
    public static double var_pop(int[] array) {
        int i = 0;
        double sum = 0;
        int len = array.length;
        double srednia = mean(array);
        for (i = 0; i < len; i++) {
            sum += Math.pow(array[i] - srednia, 2);
        }
        return sum / len;
    }

    public static double var_pop(double[] array) {
        int i = 0;
        double sum = 0;
        int len = array.length;
        double srednia = mean(array);
        for (i = 0; i < len; i++) {
            sum += Math.pow(array[i] - srednia, 2);
        }
        return sum / len;
    }

    //wariancja w probie
    public static double var_samp(int[] array) {
        int i = 0;
        double sum = 0;
        int len = array.length;
        double srednia = mean(array);
        for (i = 0; i < len; i++) {
            sum += Math.pow(array[i] - srednia, 2);
        }
        return sum / (len - 1);
    }

    public static double var_samp(double[] array) {
        int i = 0;
        double sum = 0;
        int len = array.length;
        double srednia = mean(array);
        for (i = 0; i < len; i++) {
            sum += Math.pow(array[i] - srednia, 2);
        }
        return sum / (len - 1);
    }

    // odchylenie standardowe w populacji
    public static double sd_pop(int[] array) {
        return Math.sqrt(var_pop(array));
    }

    public static double sd_pop(double[] array) {
        return Math.sqrt(var_pop(array));
    }

    // odchylenie standardowe w próbie
    public static double sd_samp(int[] array) {
        return Math.sqrt(var_samp(array));
    }

    public static double sd_samp(double[] array) {
        return Math.sqrt(var_samp(array));
    }

    public static double cv_samp(int[] array) {
        return sd_samp(array) / mean(array);
    }

    public static double cv_samp(double[] array) {
        return sd_samp(array) / mean(array);
    }

    public static double m1(int[] array) {
        return 0;
    }

    public static double m1(double[] array) {
        return 0;
    }

    public static double m2(int[] array) {
        return var_samp(array);
    }

    public static double m2(double[] array) {
        return var_samp(array);
    }

    public static double m3(int[] array) {
        double sum = 0;
        double me = mean(array);
        for (int i1 : array) {
            sum += Math.pow(i1 - me, 3);
        }
        return sum / array.length;
    }

    public static double m3(double[] array) {
        double sum = 0;
        double me = mean(array);
        for (double v : array) {
            sum += Math.pow(v - me, 3);
        }
        return sum / array.length;
    }

    public static double g1(int[] array) {
        return m3(array) / Math.pow(sd_samp(array), 3);
    }

    public static double g1(double[] array) {
        return m3(array) / Math.pow(sd_samp(array), 3);
    }

    public static double m4(int[] array) {
        double sum = 0;
        double me = mean(array);
        for (int i1 : array) {
            sum += Math.pow(i1 - me, 4);
        }
        return sum / array.length;
    }

    public static double m4(double[] array) {
        double sum = 0;
        double me = mean(array);
        for (double v : array) {
            sum += Math.pow(v - me, 4);
        }
        return sum / array.length;
    }

    public static double g2(int[] array) {
        return (m4(array) / Math.pow(var_samp(array), 2)) - 3;
    }

    public static double g2(double[] array) {
        return (m4(array) / Math.pow(var_samp(array), 2)) - 3;
    }
}
