package math.utils;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;

public class ArrayUtil {
    public static <T> void print(T[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(array[i]);
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    // drukuje arrayliste z danymi dowolnego typu
    // w przypadku typow numerycznych uzywac typow otoczkowych
    // a nie prostych czyli Double a nie double;
    public static <T> void print(ArrayList<T> array) {
        System.out.print("[");
        for (int i = 0; i < array.size(); i++) {
            if (i == array.size() - 1) {
                System.out.print(array.get(i));
            } else {
                System.out.print(array.get(i) + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    public static void printErastotenes(boolean[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (array[i]) {
                System.out.print(i + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    public static void print(int[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(array[i]);
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    public static void print(long[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(array[i]);
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    public static void print(double[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(array[i]);
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    /**
     * dodaje wartosc do tablicy pod wskazanym indeksem
     * rozszuwajac elementy i przedluzajac dlugosc tablicy
     */
    public static int[] extendArray(int[] tabl, int indexInserted, int value) {
        int len = tabl.length;
        int lenNew = 0;
        int[] tablNew = null;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            if (lenNew - 1 - indexInserted >= 0)
                System.arraycopy(tablNew, indexInserted, tablNew, indexInserted + 1, lenNew - 1 - indexInserted);
            tablNew[indexInserted] = value;
        }
        return tablNew;
    }

    public static long[] extendArray(long[] tabl, int indexInserted,
                                     long value) {
        int len = tabl.length;
        int lenNew = 0;
        long[] tablNew = null;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            if (lenNew - 1 - indexInserted >= 0)
                System.arraycopy(tablNew, indexInserted, tablNew, indexInserted + 1, lenNew - 1 - indexInserted);
            tablNew[indexInserted] = value;
        }
        return tablNew;
    }

    /**
     * Przedluza tablice o 1 element
     */
    public static int[] extendArray(int[] tabl) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + 1);
    }

    public static long[] extendArray(long[] tabl) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + 1);
    }

    public static int[] clone(int[] array) {
        return Arrays.copyOf(array, array.length);
    }

    public static double[][] clone(double[][] array) {
        int r = array.length;
        int c = array[0].length;
        double[][] array1 = new double[r][c];
        for (int i = 0; i < r; i++) {
            System.arraycopy(array[i], 0, array1[i], 0, c);
        }
        return array1;
    }

    /**
     * Zamienia tablicę pojedynczą na macierz
     *
     * @param tabl - tablica do przekształcenia
     * @param cols - liczba kolumn
     * @return zwraca macierz
     */
    public static double[][] oneToTwo(double[] tabl, int cols) {
        int rows = tabl.length / cols;
        double[][] temp = new double[rows][cols];
        for (int i = 0; i < tabl.length; i++) {
            temp[i / cols][i % cols] = tabl[i];
        }
        return temp;
    }

    /**
     * Zamienia macierz na tablice
     *
     * @param tabl - macierz do przekształcenia
     * @return - zwraca tablicę
     */
    public static double[] twoToOne(double[][] tabl) {
        int row = tabl.length;
        int col = tabl[0].length;
        int len = row * col;
        double[] temp = new double[len];
        int k = 0;
        for (double[] doubles : tabl) {
            for (int j = 0; j < col; j++) {
                temp[k] = doubles[j];
                k++;
            }
        }
        return temp;
    }


    public static double[] shortenArray(double[] tabl, int indexDeleted) {
        int len = tabl.length;
        //sprawdza czy index miesci sie w tablicy
        if ((indexDeleted > len - 1) || (indexDeleted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //to przepisuje rekordy za indeksem o 1 w lewo
            if (tabl.length - 1 - indexDeleted >= 0)
                System.arraycopy(tabl, indexDeleted + 1, tabl, indexDeleted, tabl.length - 1 - indexDeleted);
        }
        //a nastepnie skraca tablice o 1
        return Arrays.copyOf(tabl, len - 1);
    }

    public static double[] shortenArray(double[] tabl, int index1, int index2) {
        int len = tabl.length;
        int lenAdd = index2 - index1;
        if (index1 > index2) {
            throw new IllegalArgumentException(
                    "index1 nie może być większy od index2");
        }
        //sprawdza czy indeksy mieszcza sie w tablicy
        if ((index2 > len - 1) || (index1 < 0)) {
            throw new IndexOutOfBoundsException("indeks poza tablicą");
        } else {//jesli tak
            //to przepisuje rekordy za indeksem o 1 w lewo
            for (int i = index1; i < tabl.length - lenAdd - 1; i++) {
                tabl[i] = tabl[i + lenAdd + 1];
            }
        }
        //a nastepnie skraca tablice o niezbedna dlugosc
        return Arrays.copyOf(tabl, len - lenAdd - 1);
    }

    public static double[] extendArray(double[] tabl) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + 1);
    }

    public static double[] extendArray(double[] tabl, int oIle) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + oIle);
    }

    public static double[] extendArray(double[] tabl, int indexInserted,
                                       double value) {
        int len = tabl.length;
        int lenNew = 0;
        double[] tablNew = null;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            if (lenNew - 1 - indexInserted >= 0)
                System.arraycopy(tablNew, indexInserted, tablNew, indexInserted + 1, lenNew - 1 - indexInserted);
            tablNew[indexInserted] = value;
        }
        return tablNew;
    }

    public static double[] extendArray(double[] tabl, int indexInserted,
                                       double[] tablWstaw) {
        int len = tabl.length;
        int lenWstaw = tablWstaw.length;
        double[] tablNew = null;
        int lenNew = 0;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl, lenWstaw);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            for (int i = lenNew - 1; i > indexInserted - 1 + lenWstaw; i--) {
                tablNew[i] = tablNew[i - lenWstaw];
            }
            //wstawia tablWstaw w oproznione miejsce
            for (int k = indexInserted; k < lenWstaw + indexInserted; k++) {
                tablNew[k] = tablWstaw[k - indexInserted];
            }
        }
        return tablNew;
    }

    /**
     * public static double[] extendArray(double[] tabl, int indexInserted,
     * double[] tablWstaw) {
     * int len = tabl.length;
     * int lenWstaw = tablWstaw.length;
     * double[] tablNew = null;
     * int lenNew = 0;
     * //sprawdza czy index miesci sie w tablicy
     * if ((indexInserted > len) || (indexInserted < 0)) {
     * throw new IndexOutOfBoundsException("index poza tablicą");
     * } else {//jesli tak
     * //przedluza tablice
     * tablNew = extendArray(tabl, lenWstaw);
     * lenNew = tablNew.length;
     * //to przepisuje rekordy za indeksem o 1 w prawo
     * //zaczynajac od tylu
     * if (lenNew - 1 - indexInserted - 1 + lenWstaw >= 0)
     * System.arraycopy(tablNew, indexInserted - 1 + lenWstaw + 1 - lenWstaw, tablNew, indexInserted - 1 + lenWstaw + 1, lenNew - 1 - indexInserted - 1 + lenWstaw);
     * //wstawia tablWstaw w oproznione miejsce
     * if (lenWstaw + indexInserted - indexInserted >= 0)
     * System.arraycopy(tablWstaw, indexInserted - indexInserted, tablNew, indexInserted, lenWstaw + indexInserted - indexInserted);
     * }
     * return tablNew;
     * }
     */

    //przelicza indeks z [][] na []
    public static int index(int row, int col, int cols) {
        return row * cols + col;
    }

    /**
     * Przedluza tablice o 1 element
     */
    public static <T> T[] extendArray(T[] tabl) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + 1);
    }

    /**
     * Skraca tablice o jeden usuwajac element o podanym indeksie
     */
    public static <T> T[] shortenArray(T[] tabl, int indexDeleted) {
        int len = tabl.length;
        //sprawdza czy index miesci sie w tablicy
        if ((indexDeleted > len - 1) || (indexDeleted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //to przepisuje rekordy za indeksem o 1 w lewo
            if (tabl.length - 1 - indexDeleted >= 0)
                System.arraycopy(tabl, indexDeleted + 1, tabl, indexDeleted, tabl.length - 1 - indexDeleted);
        }
        //a nastepnie skraca tablice o 1
        return Arrays.copyOf(tabl, len - 1);
    }

    /**
     * Skraca tablice usuwajac elementy od index1 wlacznie do index2 wlacznie
     */
    public static <T> T[] shortenArray(T[] tabl, int index1, int index2) {
        int len = tabl.length;
        int lenAdd = index2 - index1;
        if (index1 > index2) {
            throw new IllegalArgumentException(
                    "index1 nie może być większy od index2");
        }
        //sprawdza czy indeksy mieszcza sie w tablicy
        if ((index2 > len - 1) || (index1 < 0)) {
            throw new IndexOutOfBoundsException("indeks poza tablicą");
        } else {//jesli tak
            //to przepisuje rekordy za indeksem o 1 w lewo
            for (int i = index1; i < tabl.length - lenAdd - 1; i++) {
                tabl[i] = tabl[i + lenAdd + 1];
            }
        }
        //a nastepnie skraca tablice o niezbedna dlugosc
        return Arrays.copyOf(tabl, len - lenAdd - 1);
    }

    /**
     * dodaje wartosc do tablicy pod wskazanym indeksem
     * rozszuwajac elementy i przedluzajac dlugosc tablicy
     */
    public static <T> T[] extendArray(T[] tabl, int indexInserted, T value) {
        int len = tabl.length;
        int lenNew = 0;
        T[] tablNew = null;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            if (lenNew - 1 - indexInserted >= 0)
                System.arraycopy(tablNew, indexInserted, tablNew, indexInserted + 1, lenNew - 1 - indexInserted);
            tablNew[indexInserted] = value;
        }
        return tablNew;
    }


    /**
     * wstawia tablWstaw do tablicy tabl rozszerzajac
     * tabl do odpwoiedniej dlugosci
     */
    public static <T> T[] extendArray(T[] tabl, int indexInserted, T[] tablWstaw) {
        int len = tabl.length;
        int lenWstaw = tablWstaw.length;
        T[] tablNew = null;
        int lenNew = 0;
        //sprawdza czy index miesci sie w tablicy
        if ((indexInserted > len) || (indexInserted < 0)) {
            throw new IndexOutOfBoundsException("index poza tablicą");
        } else {//jesli tak
            //przedluza tablice
            tablNew = extendArray(tabl, lenWstaw);
            lenNew = tablNew.length;
            //to przepisuje rekordy za indeksem o 1 w prawo
            //zaczynajac od tylu
            if (lenNew - 1 - indexInserted - 1 >= 0)
                System.arraycopy(tablNew, indexInserted - lenWstaw, tablNew, indexInserted, lenNew - 1 - indexInserted - 1);
            //wstawia tablWstaw w oproznione miejsce
            if (lenWstaw + indexInserted - indexInserted >= 0)
                System.arraycopy(tablWstaw, indexInserted - lenWstaw, tablNew, indexInserted, lenWstaw + indexInserted - indexInserted);
        }
        return tablNew;
    }

    /**
     * Przedluza tablice o wskazana liczbe elementow
     */
    public static <T> T[] extendArray(T[] tabl, int oIle) {
        int len = tabl.length;
        return Arrays.copyOf(tabl, len + oIle);
    }

    public static void print(double[][] array) {
        for (double[] doubles : array) {
            ArrayUtil.print(doubles);
        }
    }

    public static int[][] clone(int[][] array) {
        int r = array.length;
        int c = array[0].length;
        int[][] array1 = new int[r][c];
        for (int i = 0; i < r; i++) {
            System.arraycopy(array[i], 0, array1[i], 0, c);
        }
        return array1;
    }

    public static void fill(int[][] tabl, int value) {
        for (int i = 0; i < tabl.length; i++) {
            for (int j = 0; j < tabl[0].length; j++) {
                tabl[i][j] = value;
            }
        }
    }

    //dodaje tabl2 do tabl1
    public static int[] concat(int[] tabl1, int[] tabl2) {
        int len1 = tabl1.length;
        int len2 = tabl2.length;
        int[] tablNew = Arrays.copyOf(tabl1, len1 + len2);
        System.arraycopy(tabl2, 0, tablNew, 0 + len1, len2);
        return tablNew;
    }

    public static void print(double[] array, int decim) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(MathUtil.roundToDecimal(array[i], 2));
            } else {
                System.out.print(MathUtil.roundToDecimal(array[i], 2) + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }

    //--
    public static <T> T[] clone(T[] array) {
        return Arrays.copyOf(array, array.length);
    }

    public static Tuple2d[] clone(Tuple2d[] array) {
        Tuple2d[] temp = new Tuple2d[array.length];
        for (int i = 0; i < array.length; i++) {
            temp[i] = new Tuple2d(array[i].getX(), array[i].getY());
        }
        return temp;
    }

    public static double[] clone(double[] array) {
        return Arrays.copyOf(array, array.length);
    }

    /**
     * Odwraca podaną tablicę []
     *
     * @param array [] - tablica do odwrócenia
     * @return temp - odwrócona tablica
     */
    public static Tuple2d[] reverte(Tuple2d[] array) {
        int length = array.length;
        Tuple2d[] temp = new Tuple2d[length];
        for (int i = 0; i < length; i++) {
            temp[length - 1 - i] = array[i];
        }
        return temp;
    }

    /**
     * Odwraca podaną tablicę []
     *
     * @param array [] - tablica do odwrócenia
     * @return temp - odwrócona tablica
     */
    public static double[] reverte(double[] array) {
        int length = array.length;
        double[] temp = new double[length];
        for (int i = 0; i < length; i++) {
            temp[length - 1 - i] = array[i];
        }
        return temp;
    }

    public static void print(byte[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.print(array[i]);
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.print("]");
        System.out.println();
    }


    public static byte[] stringToByteArray(String str) {
        byte[] bytes = str.getBytes(Charset.forName("UTF-8"));
        int bytesLength = bytes.length;
        byte[] byteL = ArrayUtil.intToByteArray(bytesLength);
        int arrayLen = byteL.length + bytesLength;
        byte[] byteArray = new byte[arrayLen];
        int i;
        int a = 0;
        for (i = 0; i < byteL.length; i++) {
            byteArray[a] = byteL[i];
            a++;
        }
        for (i = 0; i < bytes.length; i++) {
            byteArray[a] = bytes[i];
            a++;
        }
        return byteArray;
    }

    public static byte[] intToByteArray(final int integer) {
        int byteNum = (40 - Integer.numberOfLeadingZeros(integer < 0
                ? ~integer
                : integer)) / 8;
        byte[] byteArray = new byte[4];
        for (int n = 0; n < byteNum; n++) {
            byteArray[3 - n] = (byte) (integer >>> (n * 8));
        }
        return (byteArray);
    }

    public static String byteArrayToString(byte[] tabl) {
        Charset charset = Charset.forName("UTF-8");
        ByteBuffer buf = ByteBuffer.wrap(tabl);
        CharBuffer cbuf = charset.decode(buf);
        return cbuf.toString();
    }

    public static void printToConsole(double[][] tabl) {
        for (double[] doubles : tabl) {
            for (int j = 0; j < tabl[0].length; j++) {
                System.out.print(doubles[j] + " ");
            }
            System.out.println();
        }
    }

    /**
     * Drukuje wskazaną macierz na konsoli
     *
     * @param plist - macierz od wydrukowania
     */
    public static void print2(double[][] plist) {
        for (double[] temp : plist) {
            for (double v : temp) {
                System.out.print(v + " ");
            }
            System.out.print("\n");
        }
    }

    public static void printToConsole(double[][] tabl, int dec) {
        for (double[] doubles : tabl) {
            for (int j = 0; j < tabl[0].length; j++) {
                System.out
                        .print(MathUtil.roundToDecimal(doubles[j], dec) + " ");
            }
            System.out.println();
        }
    }


    public static void swapArrays(double[] jeden, double[] dwa) {
        if (jeden.length == dwa.length) {
            for (int i = 0; i < jeden.length; i++) {
                double temp = jeden[i];
                jeden[i] = dwa[i];
                dwa[i] = temp;
            }
        } else {
            throw new IllegalArgumentException("rżone długości tablic");
        }
    }


    //zamienia indeks w tablicy [] na row col tablicy [][]
    //cols= liczba kolumn
    public static int[] rowCol(int indeks, int cols) {
        int row = indeks / cols;
        int col = indeks % cols;
        return new int[]{row, col};
    }

    /**
     * przelicza rząd i kolumnę macierzy na indeks tablicy lub arraylisty
     *
     * @param row int - rząd macierzy
     * @param col int - kolumna macierzy
     * @return int - indeks tablicy
     */
    public static int obliczIndeks(int row, int col) {
        return ((row + 1) * (col + 1)) - 1;
    }

    // przelicza indeks arraylisty na rzad i kolumne macierzy dla podanej
    // liczby kolumn;

    /**
     * przelicza indeks tablicy lub arraylisty na rzad i kolumne
     * macierzy
     *
     * @param indeks int - indeks tablicy
     * @param cols   - int - liczba kolumn
     * @return int[] tablica intow [0] rząd macierzy, [1] kolumna macierzy
     */
    public static int[] obliczRowCol(int indeks, int cols) {
        int row = indeks / cols;
        int col = indeks % cols;
        return new int[]{row, col};
    }

    public static void print(int[][] array) {
        for (int[] ints : array) {
            ArrayUtil.print(ints);
        }
    }

    public static void printToConsole(int[][] tabl) {
        for (int[] ints : tabl) {
            for (int j = 0; j < tabl[0].length; j++) {
                System.out.print(ints[j] + " ");
            }
            System.out.println();
        }
    }
}
