package math.utils;

import java.util.Arrays;
import java.util.Random;

public class MathUtil {
    private MathUtil() {
    }

    //logarytm naturalny z x
    public static double ln(double x) {
        return Math.log(x);
    }

    //logarytm dziesiętny z x
    public static double lg(double x) {
        return Math.log10(x);
    }

    //logarytm o dowolnej podstawie także o podstawie 10 lub e
    //logarytm z x przy podstawie base
    public static double log(double x, double base) {
        return Math.log(x) / Math.log(base);
    }

    /**
     * Funkcja zamieniająca stopnie na radiany. Może być zastąpiona
     * przez {@link Math#toRadians(double)}
     *
     * @param deg double - kąt w stopniach
     * @return double - kąt w radianach
     */
    public static double degToRad(double deg) {
        return Math.PI * deg / 180.0;
    }

    /**
     * Funkcja zamieniająca radiany na stopnie. Może być zastąpiona
     * przez {@link Math#toDegrees(double)}
     *
     * @param rad double - kąt w radianach
     * @return double - kąt w stopniach
     */
    public static double radToDeg(double rad) {
        return rad * 180.0 / Math.PI;
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double sinDeg(double angleDeg) {
        return Math.sin(angleDeg * Math.PI / 180);
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double cosDeg(double angleDeg) {
        return Math.cos(angleDeg * Math.PI / 180);
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double tanDeg(double angleDeg) {
        return Math.tan(angleDeg * Math.PI / 180);
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double atanDeg(double ratio) {
        return Math.atan(ratio) * 180 / Math.PI;
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double atan2Deg(double yy, double xx) {
        return Math.atan2(yy, xx) * 180 / Math.PI;
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double asinDeg(double ratio) {
        return Math.asin(ratio) * 180 / Math.PI;
    }

    /**
     * jak funkcja Javy, ale przyjmuje kąt w stopniach
     */
    public static double acosDeg(double ratio) {
        return Math.acos(ratio) * 180 / Math.PI;
    }

    /**
     * tangens hiperboliczny uzywany w sieciach neuronowych
     *
     * @param u double - wartość dla której obliczamy funkcję
     * @return double wartość funkcji
     */
    public static double tanh(double u) {
        double a = Math.exp(u);
        double b = Math.exp(-u);
        return (a - b) / (a + b);
    }

    //Zamienia ulamek dziesiętny na zwykły
    //ulamek okresowy zapisac jako 1.92(6)
    //zwraca tabl
    // tabl[0] licznik
    //tabl[1] mianownik
    public static int[] fractDecimToNormal(String fractDecimal) {
        int[] tabl = new int[2];
        int start = fractDecimal.indexOf("(");
        if (start != -1) {
            int end = fractDecimal.indexOf(")");
            int dot = fractDecimal.indexOf(".");
            int fractLen = start - dot - 1;
            int perLen = end - start - 1;
            int mn = (int) Math.pow(10, perLen);
            String nums = fractDecimal.substring(0, start).concat(
                    fractDecimal.substring(start + 1, start + 1 + perLen));
            String x = nums.substring(0, start);
            double numsd = Double.parseDouble(nums) * mn;
            double xd = Double.parseDouble(x);
            double rozn = numsd - xd;
            tabl[0] = (int) Math.round((rozn * Math.pow(10, fractLen)));
            System.out.println(tabl[0]);
            tabl[1] = (int) (Math.pow(10, fractLen) * (mn - 1));
            System.out.println(tabl[1]);
            int nwd = nwd2(tabl[0], tabl[1]);
            tabl[0] = tabl[0] / nwd;
            tabl[1] = tabl[1] / nwd;
        } else {
            int dot = fractDecimal.indexOf(".");
            int fractLen = fractDecimal.length() - dot - 1;
            int mn = (int) Math.pow(10, fractLen);
            tabl[0] = (int) (Double.parseDouble(fractDecimal) * mn);
            tabl[1] = mn;
            int nwd = nwd2(tabl[0], tabl[1]);
            tabl[0] = tabl[0] / nwd;
            tabl[1] = tabl[1] / nwd;
            return tabl;
        }
        return tabl;
    }

    /**
     * zwraca największy wspólny dzielnik dwóch liczb
     *
     * @param a int - pierwsza liczba
     * @param b int - druga liczba
     * @return int - największy wspólny dzielnik
     */
    public static int nwd(int a, int b) {
        int temp;
        if (b == 0) {
            return a;
        } else {
            temp = nwd(b, a % b);
        }
        return temp;
    }

    private static int nwd2(int x, int y) {
        if (y == 0) {
            return x;
        }
        while (x != y) {
            if (x > y)
                x -= y;
            else
                y -= x;
        }
        return x;
    }

    public static String binToGray(String bin) {
        int gr = Integer.parseInt(bin, 2);
        int gr1 = gr ^ (gr >> 1);
        return Integer.toString(gr1, 2);
    }

    public static String decToGray(int dec) {
        int gr1 = dec ^ (dec >> 1);
        return Integer.toString(gr1, 2);
    }

    public static String grayToBin(String gray) {
        StringBuilder sb = new StringBuilder();
        String a = gray.substring(0, 1);
        sb.append(a);
        for (int i = 1; i < gray.length(); i++) {
            int g = Integer.parseInt(a)
                    ^ Integer.parseInt(gray.substring(i, i + 1));
            a = Integer.toString(g);
            sb.append(g);
        }
        return sb.toString();
    }

    public static int grayToDec(String gray) {
        StringBuilder sb = new StringBuilder();
        String a = gray.substring(0, 1);
        sb.append(a);
        for (int i = 1; i < gray.length(); i++) {
            int g = Integer.parseInt(a)
                    ^ Integer.parseInt(gray.substring(i, i + 1));
            a = Integer.toString(g);
            sb.append(g);
        }
        return Integer.parseInt(sb.toString(), 2);
    }

    public static String[] grays(int bitsNum) {
        String[] strs = new String[(int) Math.pow(2, bitsNum)];
        for (int i = 0; i < strs.length; i++) {
            strs[i] = decToGray(i);
        }
        return strs;
    }

    /**
     * Podaje losową liczbę z przedziału zamkniętego <min, max>
     *
     * @param min long - najmniejsza wartość
     * @param max long - największa wartość
     * @return long - wylosowana wartość z podanego zakresu, z uwzględniem min i
     * max
     */
    public static long randomInRange(long min, long max) {
        long random = -1;
        if (min > max) {
            System.out.println("pierwsza liczba musi byc mniejsza od drugiej");
        } else {
            random = (long) (Math.floor(Math.random() * (max - min + 1)) + min);
        }
        return random;
    }

    public static int randomInRange(int min, int max) {
        int random = -1;
        if (min > max) {
            System.out.println("pierwsza liczba musi byc mniejsza od drugiej");
        } else {
            random = (int) (Math.floor(Math.random() * (max - min + 1)) + min);
        }
        return random;
    }

    /**
     * Podaje losową liczbę z przedziału zamkniętego <min, max>
     *
     * @param min int - najmniejsza wartość
     * @param max int - największa wartość
     * @return int - wylosowana wartość z podanego zakresu, z uwzględniem min i
     * max
     */
    public static int randomInRange2(int min, int max) {
        Random r = new Random();
        return r.nextInt(max + 1 - min) + min;
    }

    public static double randomInRange(double min, double max) {
        double rR = -1;
        if (min > max) {
            System.out.println("pierwsza liczba musi byc mniejsza od drugiej");
        } else {
            rR = Math.floor(Math.random() * (max - min + 1)) + min;
        }
        return rR;
    }

    /**
     * wybiera losowo liczbe z przedzialu zamknietego <min, max>
     * zgodnie z krzywa rozkladu Gaussa tzn. im bliżej środka
     * przedziału tym wartości są częściej wybierane
     *
     * @param min - dolna granica przedziału
     * @param max - górna granica przedziału
     * @return double - losowa liczba z rozkładu Gaussa
     */
    public static double randomGaussian(int min, int max) {
        final int dif = max - min;
        final double gmin = -2.0;
        final double gmax = 2.0;
        final double gdif = gmax - gmin;
        final double interv = gdif / dif;
        Random r = new Random();
        double gauss = r.nextGaussian();
        if (gauss < -2.0) {
            gauss = -2.0;
        }
        if (gauss > 2.0) {
            gauss = 2.0;
        }
        double g = ((gauss - gmin) / interv);
        return (int) g + min;
    }

    public static int reverteInt(int liczba) {
        String str1 = String.valueOf(liczba);
        return Integer.parseInt(reverteString(str1));
    }

    public static String reverteString(String str) {
        StringBuilder sb = new StringBuilder();
        int len = str.length();
        for (int i = len; i > 0; i--) {
            sb.append(str, i - 1, i);
        }
        return sb.toString();
    }

    public static long nwd2(long x, long y) {
        if (y == 0) {
            return x;
        }
        while (x != y) {
            if (x > y)
                x -= y;
            else
                y -= x;
        }
        return x;
    }

    /**
     * Zwraca tablice k liczb wybraych losowo z tablicy od 1 do n liczb bez
     * zwracania (powtórzeń)
     *
     * @param k      int - liczba wybieranych liczb
     * @param n      int - największa liczba
     * @param sorted boolean - określa czy wyjściowa tablica liczb będzie
     *               posortowana czy nie, <code>true</code> oznacza tablicę posortowaną, <code>false</code>
     *               oznacza tablicę nie sortowaną
     * @return int[] - tablica wybranych liczb
     */
    public static int[] withoutReturn(int k, int n, boolean sorted) {
        int[] wynik = new int[k];
        if (k > n) {
            System.out
                    .println("pierwsza liczba nie moze byc wieksza od drugiej");
        } else {
            int[] liczby = new int[n];
            for (int j = 0; j < liczby.length; j++) {
                liczby[j] = j + 1;
            }
            for (int i = 0; i < wynik.length; i++) {
                int l = (int) Math.floor(Math.random() * n);
                wynik[i] = liczby[l];
                liczby[l] = liczby[n - 1];
                n--;
            }
            if (sorted) {
                Arrays.sort(wynik);
            }
        }
        return wynik;
    }

    /**
     * Skaluje tablice double[] na tablicę kątów (w stopniach). Suma wszyskich
     * liczb po przetworzeniu wynosi 360.00
     */
    public static double[] doubleToAngle(double[] data) {
        int len = data.length;
        double[] temp = new double[len];
        double sum = 0;
        int i;
        for (i = 0; i < len; i++) {
            sum += data[i];
        }
        for (i = 0; i < len; i++) {
            temp[i] = data[i] * 360.0 / sum;
        }
        return temp;
    }

    /**
     * Funkcja zaokragla liczby 2, 5, 10 etc,
     * do najbliższej wielokrotności calkowitej tej liczby
     * np roundTo(92.5, 5) zaokragla do 95, roundTo(92.5, 10) do 90, etc.
     *
     * @param num   double - liczba do zaokrąglenia
     * @param round int - rzad wielkości do zaokrąglenia
     * @return int - zwraca przetworzoną liczbę
     */
    public static int roundToMulti(double num, int round) {
        return (int) (Math.round(num / (double) round) * (double) round);
    }

    public static int roundToMulti(float num, int round) {
        return (int) (Math.round((double) num / (double) round) * (double) round);
    }

    public static float roundToDecimal(float num, int dec) {
        int multi = (int) Math.pow(10, dec);
        int temp = Math.round(num * multi);
        return (float) temp / multi;
    }

    /**
     * funkcja zaokragla podaną liczbę do określonej liczby miejsc po przecinku
     *
     * @param num double - liczba do zaokrąglenia
     * @param dec int - liczba miejsc po przecinku;
     * @return double - zwraca zaokrągloną liczbę
     */
    public static double roundToDecimal(double num, int dec) {
        int multi = (int) Math.pow(10, dec);
        int temp = (int) Math.round(num * multi);
        return (double) temp / multi;
    }

    ///zwraca liczbę znaków po przecinku
    public static int getFractLen(float num) {
        String temp = (String.valueOf(num));
        String temp2 = temp.substring(temp.indexOf(".") + 1);
        return temp2.length();
    }

    /**
     * Obcina dane po przecinku
     *
     * @param num double - liczba do przetworzenia
     * @return int - zwraca liczbę bez miesc po przecinku
     */
    public static String getDecim(double num) {
        int num1 = (int) (num);
        return String.valueOf(num1);
    }

    /**
     * Obcina dane po przecinku
     *
     * @param num double - liczba do przetworzenia
     * @return int - zwraca liczbę bez miesc po przecinku
     */
    public static String getDecimAsString(double num) {
        int num1 = (int) (num);
        return String.valueOf(num1);
    }

    /**
     * Obcina dane przed przecinkiem i przecinek
     *
     * @param num - numer do obcięcia
     * @return - część ułamkową jako String
     */
    public static String getFract(double num) {
        String temp = (String.valueOf(num));
        return temp.substring(temp.indexOf(".") + 1);
    }

    /**
     * Obcina dane przed przecinkiem i przecinek
     *
     * @param num - numer do obcięcia
     * @return - część ułamkową jako String
     */
    public static String getFractAsString(double num) {
        String temp = (String.valueOf(num));
        return temp.substring(temp.indexOf(".") + 1);
    }

    public static double nww(double x, double y) {
        return (x * y) / nwd2(x, y);
    }

    public static double nwd2(double x, double y) {
        if (y == 0) {
            return x;
        }
        while (x != y) {
            if (x > y)
                x -= y;
            else
                y -= x;
        }
        return x;
    }

    /**
     * Metoda używana w sieciach neuronowych
     *
     * @param sum double - wartość dla której obliczamy funkcję
     * @return double wartość funkcji
     */
    public static double sigmoid(double sum) {
        return 1.0 / (1 + Math.exp(-1.0 * sum));
    }

    public static float getMantissa(float x) {
        int exp = Math.getExponent(x);
        return (float) (x / Math.pow(2, exp));
    }

    public static int nww(int x, int y) {
        return (x * y) / nwd2(x, y);
    }


}
