package math.fibo;

import math.bigs.BigsUtil;
import math.fi.FiUtil;
import math.utils.MathUtil;

import java.math.BigInteger;
import java.util.ArrayList;

public class FiboUtil {
    private FiboUtil() {
    }

    /**
     * @param n który wyraz ciagu Fibonnacciego
     * @return liczba Fibonacciego
     */
    public static BigInteger fibonacci(long n) {
        BigInteger[] tabl = {BigInteger.ONE, BigInteger.ONE, BigInteger.ONE,
                BigInteger.ZERO};
        BigInteger[] tablKonc = {BigInteger.ONE, BigInteger.ONE,
                BigInteger.ONE, BigInteger.ZERO};
        if (n == 0) {
            return BigInteger.ZERO;
        }
        if (n == 1) {
            return BigInteger.ONE;
        }
        for (long i = 0; i < n; i++) {
            tablKonc = multi(tablKonc, tabl);
        }
        return tablKonc[3];
    }

    //mnozy dwie macierze 2x2 podane jako tablice BigInteger[4];
    public static BigInteger[] multi(BigInteger[] a, BigInteger[] b) {
        BigInteger[] t = new BigInteger[4];
        t[0] = (a[0].multiply(b[0])).add(a[1].multiply(b[2]));
        t[1] = (a[0].multiply(b[1])).add(a[1].multiply(b[3]));
        t[2] = (a[2].multiply(b[0])).add(a[3].multiply(b[2]));
        t[3] = (a[2].multiply(b[1])).add(a[3].multiply(b[3]));
        return t;
    }

    /**
     * @param liczba - pewna liczba
     * @return - najbliższa liczba Fibonnaciego większa od tej liczby
     */
    public static BigInteger biggerFibo(BigInteger liczba) {
        BigInteger[] tabl = {BigInteger.ONE, BigInteger.ONE, BigInteger.ONE,
                BigInteger.ZERO};
        BigInteger[] tablKonc = {BigInteger.ONE, BigInteger.ONE,
                BigInteger.ONE, BigInteger.ZERO};
        boolean b = true;
        while (b) {
            tablKonc = FiboUtil.multi(tablKonc, tabl);
            b = BigsUtil.mniejszy(tablKonc[3], liczba);
        }
        return tablKonc[3];
    }

    /**
     * @param liczba - pewna liczba
     * @return najbliższa liczba Fibonacciego mniejsza niż ta liczba
     */
    public static BigInteger smallerFibo(BigInteger liczba) {
        BigInteger[] tabl = {BigInteger.ONE, BigInteger.ONE, BigInteger.ONE,
                BigInteger.ZERO};
        BigInteger[] tablKonc = {BigInteger.ONE, BigInteger.ONE,
                BigInteger.ONE, BigInteger.ZERO};
        boolean b = true;
        long i = 0;
        while (b) {
            tablKonc = multi(tablKonc, tabl);
            i++;
            b = BigsUtil.mniejszy(tablKonc[3], liczba);
        }
        return new Fibo(i - 1L).getFibo();
    }

    public static ArrayList<BigInteger> getFibos(BigInteger min, BigInteger max) {
        ArrayList<BigInteger> list = new ArrayList<>();
        for (BigInteger j = min.add(BigInteger.ONE); BigsUtil.mniejszy(j, max); j = j
                .add(BigInteger.ONE)) {
            if (isFibo(j)) {
                list.add(j);
            }
        }
        return list;
    }

    public static boolean isFibo(BigInteger liczba) {
        BigInteger[] tabl = {BigInteger.ONE, BigInteger.ONE, BigInteger.ONE,
                BigInteger.ZERO};
        BigInteger[] tablKonc = {BigInteger.ONE, BigInteger.ONE,
                BigInteger.ONE, BigInteger.ZERO};
        boolean b = true;
        while (b) {
            tablKonc = FiboUtil.multi(tablKonc, tabl);
            b = BigsUtil.mniejszy(tablKonc[3], liczba);
        }
        return tablKonc[3].equals(liczba);
    }

    /**
     * rozklada liczbe na sume liczb Fibonacciego
     *
     * @param liczba do rozlozenia
     * @return arraylista liczb, ktorych suma jest rowna liczbie
     */
    public static ArrayList<BigInteger> rozklad(BigInteger liczba) {
        ArrayList<BigInteger> al = new ArrayList<>();
        if (isFibo(liczba)) {
            al.add(liczba);
            return al;
        }
        BigInteger stara = new BigInteger(liczba.toString());//100,
        boolean a = true;
        while (a) {
            BigInteger smaller = smallerFibo(stara);//89,
            stara = stara.subtract(smaller);//11,
            if (isFibo(stara)) {
                al.add(smaller);
                al.add(stara);
                return al;
            } else {
                al.add(smaller);
            }
        }
        return al;
    }

    public static long numer(long liczba) {
        double x = Math.sqrt(5) * liczba + 0.5;
        return (long) (MathUtil.log(x, FiUtil.fi()));
    }

    public static int numSumaCyfr(String str) {
        int sum = 0;
        int len = str.length();
        for (int i = 0; i < len; i++) {
            String temp = str.substring(i, i + 1);
            sum += Integer.parseInt(temp);
            if (sum > 9) {
                String temp1 = String.valueOf(sum);
                int i1 = Integer.parseInt(temp1.substring(0, 1));
                int i2 = Integer.parseInt(temp1.substring(1, 2));
                sum = i1 + i2;
            }
        }
        return sum;
    }

    public static int[] fibonacciArray(int n) {
        int[] tabl = new int[n + 1];
        tabl[0] = 0;
        tabl[1] = 1;
        for (int i = 2; i < n + 1; i++) {
            tabl[i] = tabl[i - 1] + tabl[i - 2];
        }
        return tabl;
    }

    //Binet
    public static double binet(int n) {
        int i = 5;
        return (int)((1.0 / Math.sqrt(i)
                * Math.pow((1.0 + Math.sqrt(i)) / 2, n))
                - (1.0 / Math.sqrt(i) * Math.pow((1.0 - Math.sqrt(i)) / 2, n)));
    }

}
