package math.kombi;

import math.bigs.BigsUtil;

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

/**
 * Klasa do obliczania symbolu Newtona w wątku
 *
 * @author Jacek Piechota
 */
public class Npok implements Callable<BigInteger> {
    private final BigInteger k;
    private final BigInteger n;

    public Npok(BigInteger n, BigInteger k) {
        this.n = n;
        this.k = k;
    }

    @Override
    public BigInteger call() {
        return npok(this.n, this.k);
    }

    /**
     * Oblicza symbol Newtona (n po k) dla podanych liczb (n!/(k!(n-k)!)
     *
     * @param n - liczba elementów
     * @param k - liczba stanów elementu
     * @return - n po k, czyli n nad k
     */
    public static BigInteger npok(BigInteger n, BigInteger k) {
        if (BigsUtil.wiekszy(k, n)) {
            return BigInteger.ZERO;
        } else if (BigsUtil.rowny(k, BigInteger.ZERO) || BigsUtil.rowny(k, n)) {
            return BigInteger.ONE;
        } else if (BigsUtil.rowny(k, BigInteger.ONE)
                || BigsUtil.rowny(k, n.subtract(BigInteger.ONE))) {
            return n;
        } else {
            BigInteger b = Factorial.factorial(k);
            BigInteger c = Factorial.factorial(n.subtract(k));
            BigInteger a = b;
            BigInteger i = k.add(BigInteger.ONE);
            while (BigsUtil.mniejszy(i, n.add(BigInteger.ONE))) {
                a = a.multiply(i);
                i = i.add(BigInteger.ONE);
            }
            return a.divide(b.multiply(c));
        }
    }

    /**
     * Oblicza symbol Newtona (n po k) dla podanych liczb (n!/(k!(n-k)!)
     *
     * @param n - liczba elementów
     * @param k - liczba stanów elementu
     * @return - n po k, czyli n nad k
     */
    public static long npok(int n, int k) {
        if (k > n) {
            return 0;
        } else if (k == 0 || k == n) {
            return 1;
        } else if (k == 1 || k == (n - 1)) {
            return n;
        } else {
            long b = Factorial.factorial(k);
            long c = Factorial.factorial(n - k);
            long a = b;
            for (long i = k + 1; i < n + 1; i++) {
                a *= i;
            }
            return a / (b * c);
        }
    }

    /**
     * Oblicza symbol Newtona (n po k) dla podanych liczb (n!/(k!(n-k)!)
     *
     * @param n - liczba elementów
     * @param k - liczba stanów elementu
     * @return - n po k, czyli n nad k
     */
    public static BigInteger npok(long n, long k) {
        if (k > n) {
            return BigInteger.ZERO;
        } else if (k == 0 || k == n) {
            return BigInteger.ONE;
        } else if (k == 1 || k == (n - 1)) {
            return new BigInteger(String.valueOf(n));
        } else {
            BigInteger b = Factorial.factorial(k);
            BigInteger c = Factorial.factorial(n - k);
            BigInteger a = b;
            for (long i = k + 1; i < n + 1; i++) {
                a = a.multiply(new BigInteger(String.valueOf(i)));
            }
            return a.divide((b.multiply(c)));
        }
    }
}
