package math.kombi;

import math.bigs.BigsUtil;

import java.math.BigInteger;
import java.util.concurrent.*;

public class RXXX implements Callable<BigInteger> {
    private final BigInteger k;
    private final BigInteger n;
    private final String r;

    public RXXX(BigInteger k, BigInteger n, String r) {
        this.k = k;
        this.n = n;
        this.r = r;
    }

    @Override
    public BigInteger call() {
        BigInteger r = null;
        switch (this.r) {
            case "000":
                r = r000(k, n);
                break;
            case "001":
                r = r001(k, n);
                break;
            case "010":
                r = r010(k, n);
                break;
            case "011":
                r = r011(k, n);
                break;
            case "100":
                r = r100(k, n);
                break;
            case "101":
                r = r101(k, n);
                break;
            case "110":
                r = r110(k, n);
                break;
            case "111":
                try {
                    int a = n.intValueExact();
                    r = r111(k, a);
                } catch (ArithmeticException e) {
                    System.out.println(
                            "Wartość 'n' powinna odpowiadać zakresowi 'int'");
                }
                break;
        }
        return r;
    }

    private static BigInteger r000(BigInteger k, BigInteger n) {
        if (BigsUtil.rowny(n, BigInteger.ZERO)
                || BigsUtil.rowny(k, BigInteger.ONE)) {
            return BigInteger.ZERO;
        } else if (BigsUtil.wiekszy(n, BigInteger.ZERO)) {
            if (BigsUtil.rowny(k, BigInteger.ONE)) {
                return BigInteger.ONE;
            } else if (BigsUtil.mniejszy(n, k)) {
                return BigInteger.ZERO;
            } else if (BigsUtil.rowny(n, k)) {
                return BigInteger.ONE;
            } else {
                return (r000(k.subtract(BigInteger.ONE),
                        n.subtract(BigInteger.ONE)))
                        .add(r000(k, n.subtract(k)));
            }
        }
        return null;
    }

    private static BigInteger r001(BigInteger k, BigInteger n) {
        BigInteger sum = BigInteger.ONE;
        BigInteger i = BigInteger.ONE;
        while (BigsUtil.mniejszy(i, k.add(BigInteger.ONE))) {
            sum = sum.add(RXXX.r000(i, n));
            i = i.add(BigInteger.ONE);
        }
        return sum;
    }

    private static BigInteger r010(BigInteger k, BigInteger n) {
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<BigInteger> f = es.submit(new Npok(n.subtract(BigInteger.ONE),
                k.subtract(BigInteger.ONE)));
        BigInteger result = null;
        try {
            result = f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        es.shutdown();
        return result;
    }

    private static BigInteger r011(BigInteger k, BigInteger n) {
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<BigInteger> f = es
                .submit(new Npok(n.add(k).subtract(BigInteger.ONE), n));
        BigInteger result = null;
        try {
            result = f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        es.shutdown();
        return result;
    }

    public static BigInteger r100(BigInteger k, BigInteger n) {
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<BigInteger> f = es.submit(new RecurStir2(k, n));
        BigInteger result = null;
        try {
            result = f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        es.shutdown();
        return result;
    }

    private static BigInteger r101(BigInteger k, BigInteger n) {
        BigInteger sum = BigInteger.ZERO;
        BigInteger i = BigInteger.ONE;
        ExecutorService es;
        Future<BigInteger> f;
        while (BigsUtil.mniejszy(i, k.add(BigInteger.ONE))) {
            es = Executors.newFixedThreadPool(1);
            f = es.submit(new RecurStir2(i, n));
            try {
                sum = sum.add(f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            i = i.add(BigInteger.ONE);
            es.shutdown();
        }
        return sum;
    }

    private static BigInteger r110(BigInteger k, BigInteger n) {
        BigInteger t = Factorial.factorial(k);
        BigInteger stir = null;
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<BigInteger> f = es.submit(new RecurStir2(k, n));
        try {
            stir = f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        es.shutdown();
        return t.multiply(stir);
    }

    private static BigInteger r111(BigInteger k, int n) {
        return k.pow(n);
    }

    //Kule nierozroznialne
    //urny nierozroznialne
    //urna nie moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r7
    public static int r000(int k, int n) {
        if (n == 0 || k == 0) {
            return 0;
        } else if (n > 0) {
            if (k == 1) {
                return 1;
            } else if (n < k) {
                return 0;
            } else if (n == k) {
                return 1;
            } else {
                return r000(k - 1, n - 1) + r000(k, n - k);
            }
        }
        return -1;
    }

    public static long r000(long k, long n) {
        if (n == 0L || k == 0L) {
            return 0L;
        } else if (n > 0L) {
            if (k == 1L) {
                return 1L;
            } else if (n < k) {
                return 0L;
            } else if (n == k) {
                return 1L;
            } else {
                return r000(k - 1, n - 1) + r000(k, n - k);
            }
        }
        return -1L;
    }

    //Kule nierozroznialne
    //urny nierozroznialne
    //urna moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r8
    //zwroc uwage, ze pierwszym argumentem jest k
    public static int r001(int k, int n) {
        int sum = 0;
        for (int i = 1; i < k + 1; i++) {
            sum += r000(i, n);
        }
        return sum;
    }

    public static long r001(long k, long n) {
        long sum = 0L;
        for (long i = 1; i < k + 1; i++) {
            sum += r000(i, n);
        }
        return sum;
    }

    //Kule nierozroznialne
    //urny rozroznialne
    //urna nie moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r6
    public static int r010(int k, int n) {
        return (int) Npok.npok(n - 1, k - 1);
    }

    public static BigInteger r010(long k, long n) {
        return Npok.npok(n - 1, k - 1);
    }

    //Kule nierozroznialne
    //urny rozroznialne
    //urna moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r5
    public static int r011(int k, int n) {
        return (int) Npok.npok(n + k - 1, n);
    }

    public static BigInteger r011(long k, long n) {
        return Npok.npok(n + k - 1, n);
    }

    //Kule rozroznialne
    //urny nierozroznialne
    //urna nie moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r4
    public static int r100(int k, int n) {
        return RecurStir2.recurStir2(k, n);
    }

    public static long r100(long k, long n) {
        return RecurStir2.recurStir2(k, n);
    }

    //Kule rozroznialne
    //urny nierozroznialne
    //urna moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r2
    public static int r101(int k, int n) {
        int sum = 0;
        for (int i = 1; i < k + 1; i++) {
            sum += RecurStir2.recurStir2(i, n);
        }
        return sum;
    }

    public static long r101(long k, long n) {
        long sum = 0;
        for (long i = 1; i < k + 1; i++) {
            sum += RecurStir2.recurStir2(i, n);
        }
        return sum;
    }

    //Kule rozroznialne
    //urny rozroznialne
    //urna nie moze byc pusta
    //n - liczba kul, k - liczba urn
    //przyklad: r3
    public static int r110(int k, int n) {
        int t = (int) Factorial.factorial(k);
        int stir = RecurStir2.recurStir2(k, n);
        return t * stir;
    }

    public static BigInteger r110(long k, long n) {
        BigInteger t = Factorial.factorial(k);
        long stir = RecurStir2.recurStir2(k, n);
        return t.multiply(new BigInteger(String.valueOf(stir)));
    }

    //Kule rozroznialne
    //urny rozroznialne
    //urny moga byc puste
    //n liczba kul, k - liczba urn
    //przyklad: r1
    public static int r111(int k, int n) {
        return (int) Math.pow(k, n);
    }

    public static long r111(long k, int n) {
        return (long) Math.pow(k, n);
    }
}
