package org.nevec.rjm;

import java.math.BigInteger;
import java.util.Vector;

import it.cavallium.warppi.util.Error;

/**
 * BigInteger special functions and Number theory.
 *
 * @since 2009-08-06
 * @author Richard J. Mathar
 */
public class BigIntegerMath {

	/**
	 * Evaluate binomial(n,k).
	 *
	 * @param n
	 *            The upper index
	 * @param k
	 *            The lower index
	 * @return The binomial coefficient
	 */
	static public BigInteger binomial(final int n, final int k) {
		if (k == 0) {
			return BigInteger.ONE;
		}
		BigInteger bin = new BigInteger("" + n);
		final BigInteger n2 = bin;
		for (BigInteger i = new BigInteger("" + (k - 1)); i.compareTo(BigInteger.ONE) >= 0; i = i.subtract(BigInteger.ONE)) {
			bin = bin.multiply(n2.subtract(i));
		}
		for (BigInteger i = new BigInteger("" + k); i.compareTo(BigInteger.ONE) == 1; i = i.subtract(BigInteger.ONE)) {
			bin = bin.divide(i);
		}
		return bin;
	} /* binomial */

	/**
	 * Evaluate binomial(n,k).
	 *
	 * @param n
	 *            The upper index
	 * @param k
	 *            The lower index
	 * @return The binomial coefficient
	 * @since 2008-10-15
	 */
	static public BigInteger binomial(final BigInteger n, final BigInteger k) {
		/*
		 * binomial(n,0) =1
		 */
		if (k.compareTo(BigInteger.ZERO) == 0) {
			return BigInteger.ONE;
		}

		BigInteger bin = new BigInteger("" + n);

		/*
		 * the following version first calculates n(n-1)(n-2)..(n-k+1)
		 * in the first loop, and divides this product through k(k-1)(k-2)....2
		 * in the second loop. This is rather slow and replaced by a faster
		 * version
		 * below
		 * BigInteger n2 = bin ;
		 * BigInteger i= k.subtract(BigInteger.ONE) ;
		 * for( ; i.compareTo(BigInteger.ONE) >= 0 ; i =
		 * i.subtract(BigInteger.ONE) )
		 * bin = bin.multiply(n2.subtract(i)) ;
		 * i= new BigInteger(""+k) ;
		 * for( ; i.compareTo(BigInteger.ONE) == 1 ; i =
		 * i.subtract(BigInteger.ONE) )
		 * bin = bin.divide(i) ;
		 */

		/*
		 * calculate n then n(n-1)/2 then n(n-1)(n-2)(2*3) etc up to
		 * n(n-1)..(n-k+1)/(2*3*..k)
		 * This is roughly the best way to keep the individual intermediate
		 * products small
		 * and in the integer domain. First replace C(n,k) by C(n,n-k) if n-k<k.
		 */
		BigInteger truek = new BigInteger(k.toString());
		if (n.subtract(k).compareTo(k) < 0) {
			truek = n.subtract(k);
		}

		/*
		 * Calculate C(num,truek) where num=n and truek is the smaller of n-k
		 * and k.
		 * Have already initialized bin=n=C(n,1) above. Start definining the
		 * factorial
		 * in the denominator, named fden
		 */
		BigInteger i = new BigInteger("2");
		BigInteger num = new BigInteger(n.toString());
		/*
		 * a for-loop (i=2;i<= truek;i++)
		 */
		for (; i.compareTo(truek) <= 0; i = i.add(BigInteger.ONE)) {
			/*
			 * num = n-i+1 after this operation
			 */
			num = num.subtract(BigInteger.ONE);
			/*
			 * multiply by (n-i+1)/i
			 */
			bin = bin.multiply(num).divide(i);
		}
		return bin;
	} /* binomial */

	/**
	 * Evaluate sigma_k(n).
	 *
	 * @param n
	 *            the main argument which defines the divisors
	 * @param k
	 *            the lower index, which defines the power
	 * @return The sum of the k-th powers of the positive divisors
	 */
	static public BigInteger sigmak(final BigInteger n, final int k) {
		return new Ifactor(n.abs()).sigma(k).n;
	} /* sigmak */

	/**
	 * Evaluate sigma(n).
	 *
	 * @param n
	 *            the argument for which divisors will be searched.
	 * @return the sigma function. Sum of the positive divisors of the argument.
	 * @since 2006-08-14
	 * @author Richard J. Mathar
	 */
	static public BigInteger sigma(final int n) {
		return new Ifactor(Math.abs(n)).sigma().n;
	}

	/**
	 * Compute the list of positive divisors.<br>
	 * <b>Deprecated: This function is extremely slow and inefficient!</b>
	 *
	 * @param n
	 *            The integer of which the divisors are to be found.
	 * @return The sorted list of positive divisors.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	@Deprecated
	static public Vector<BigInteger> divisors(final BigInteger n) {
		return new Ifactor(n.abs()).divisors();
	}

	/**
	 * Evaluate sigma(n).
	 *
	 * @param n
	 *            the argument for which divisors will be searched.
	 * @return the sigma function. Sum of the divisors of the argument.
	 * @since 2006-08-14
	 * @author Richard J. Mathar
	 */
	static public BigInteger sigma(final BigInteger n) {
		return new Ifactor(n.abs()).sigma().n;
	}

	/**
	 * Evaluate floor(sqrt(n)).
	 *
	 * @param n
	 *            The non-negative argument.
	 * @return The integer square root. The square root rounded down.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public int isqrt(final int n) {
		if (n < 0) {
			throw new ArithmeticException("Negative argument " + n);
		}
		final double resul = Math.sqrt(n);
		return (int) Math.round(resul);
	}

	/**
	 * Evaluate floor(sqrt(n)).
	 *
	 * @param n
	 *            The non-negative argument.
	 *            Arguments less than zero throw an ArithmeticException.
	 * @return The integer square root, the square root rounded down.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public long isqrt(final long n) {
		if (n < 0) {
			throw new ArithmeticException("Negative argument " + n);
		}
		final double resul = Math.sqrt(n);
		return Math.round(resul);
	}

	/**
	 * Evaluate floor(sqrt(n)).
	 *
	 * @param n
	 *            The non-negative argument.
	 *            Arguments less than zero throw an ArithmeticException.
	 * @return The integer square root, the square root rounded down.
	 * @since 2011-02-12
	 * @author Richard J. Mathar
	 */
	static public BigInteger isqrt(final BigInteger n) {
		if (n.compareTo(BigInteger.ZERO) < 0) {
			throw new ArithmeticException("Negative argument " + n.toString());
		}
		/*
		 * Start with an estimate from a floating point reduction.
		 */
		BigInteger x;
		final int bl = n.bitLength();
		if (bl > 120) {
			x = n.shiftRight(bl / 2 - 1);
		} else {
			final double resul = Math.sqrt(n.doubleValue());
			x = new BigInteger("" + Math.round(resul));
		}

		final BigInteger two = new BigInteger("2");
		while (true) {
			/*
			 * check whether the result is accurate, x^2 =n
			 */
			final BigInteger x2 = x.pow(2);
			BigInteger xplus2 = x.add(BigInteger.ONE).pow(2);
			if (x2.compareTo(n) <= 0 && xplus2.compareTo(n) > 0) {
				return x;
			}
			xplus2 = xplus2.subtract(x.shiftLeft(2));
			if (xplus2.compareTo(n) <= 0 && x2.compareTo(n) > 0) {
				return x.subtract(BigInteger.ONE);
			}
			/*
			 * Newton algorithm. This correction is on the
			 * low side caused by the integer divisions. So the value required
			 * may end up by one unit too large by the bare algorithm, and this
			 * is caught above by comparing x^2, (x+-1)^2 with n.
			 */
			xplus2 = x2.subtract(n).divide(x).divide(two);
			x = x.subtract(xplus2);
		}
	}

	/**
	 * Evaluate core(n).
	 * Returns the smallest positive integer m such that n/m is a perfect
	 * square.
	 *
	 * @param n
	 *            The non-negative argument.
	 * @return The square-free part of n.
	 * @since 2011-02-12
	 * @author Richard J. Mathar
	 */
	static public BigInteger core(final BigInteger n) {
		if (n.compareTo(BigInteger.ZERO) < 0) {
			throw new ArithmeticException("Negative argument " + n);
		}
		final Ifactor i = new Ifactor(n);
		return i.core();
	}

	/**
	 * Minor of an integer matrix.
	 *
	 * @param A
	 *            The matrix.
	 * @param r
	 *            The row index of the row to be removed (0-based).
	 *            An exception is thrown if this is outside the range 0 to the
	 *            upper row index of A.
	 * @param c
	 *            The column index of the column to be removed (0-based).
	 *            An exception is thrown if this is outside the range 0 to the
	 *            upper column index of A.
	 * @return The depleted matrix. This is not a deep copy but contains
	 *         references to the original.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public BigInteger[][] minor(final BigInteger[][] A, final int r, final int c) throws ArithmeticException {
		/* original row count */
		final int rL = A.length;
		if (rL == 0) {
			throw new ArithmeticException("zero row count in matrix");
		}
		if (r < 0 || r >= rL) {
			throw new ArithmeticException("row number " + r + " out of range 0.." + (rL - 1));
		}
		/* original column count */
		final int cL = A[0].length;
		if (cL == 0) {
			throw new ArithmeticException("zero column count in matrix");
		}
		if (c < 0 || c >= cL) {
			throw new ArithmeticException("column number " + c + " out of range 0.." + (cL - 1));
		}
		final BigInteger M[][] = new BigInteger[rL - 1][cL - 1];
		int imrow = 0;
		for (int row = 0; row < rL; row++) {
			if (row != r) {
				int imcol = 0;
				for (int col = 0; col < cL; col++) {
					if (col != c) {
						M[imrow][imcol] = A[row][col];
						imcol++;
					}
				}
				imrow++;
			}
		}
		return M;
	}

	/**
	 * Replace column of a matrix with a column vector.
	 *
	 * @param A
	 *            The matrix.
	 * @param c
	 *            The column index of the column to be substituted (0-based).
	 * @param v
	 *            The column vector to be inserted.
	 *            With the current implementation, it must be at least as long
	 *            as the row count, and
	 *            its elements that exceed that count are ignored.
	 * @return The modified matrix. This is not a deep copy but contains
	 *         references to the original.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	@SuppressWarnings("unused")
	static private BigInteger[][] colSubs(final BigInteger[][] A, final int c, final BigInteger[] v)
			throws ArithmeticException {
		/* original row count */
		final int rL = A.length;
		if (rL == 0) {
			throw new ArithmeticException("zero row count in matrix");
		}
		/* original column count */
		final int cL = A[0].length;
		if (cL == 0) {
			throw new ArithmeticException("zero column count in matrix");
		}
		if (c < 0 || c >= cL) {
			throw new ArithmeticException("column number " + c + " out of range 0.." + (cL - 1));
		}
		final BigInteger M[][] = new BigInteger[rL][cL];
		for (int row = 0; row < rL; row++) {
			for (int col = 0; col < cL; col++) {
				/*
				 * currently, v may just be longer than the row count, and
				 * surplus
				 * elements will be ignored. Shorter v lead to an exception.
				 */
				if (col != c) {
					M[row][col] = A[row][col];
				} else {
					M[row][col] = v[row];
				}
			}
		}
		return M;
	}

	/**
	 * Determinant of an integer square matrix.
	 *
	 * @param A
	 *            The square matrix.
	 *            If column and row dimensions are unequal, an
	 *            ArithmeticException is thrown.
	 * @return The determinant.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public BigInteger det(final BigInteger[][] A) throws ArithmeticException {
		BigInteger d = BigInteger.ZERO;
		/* row size */
		final int rL = A.length;
		if (rL == 0) {
			throw new ArithmeticException("zero row count in matrix");
		}
		/* column size */
		final int cL = A[0].length;
		if (cL != rL) {
			throw new ArithmeticException("Non-square matrix dim " + rL + " by " + cL);
		}

		/*
		 * Compute the low-order cases directly.
		 */
		if (rL == 1) {
			return A[0][0];
		} else if (rL == 2) {
			d = A[0][0].multiply(A[1][1]);
			return d.subtract(A[0][1].multiply(A[1][0]));
		} else {
			/* Work arbitrarily along the first column of the matrix */
			for (int r = 0; r < rL; r++) {
				/*
				 * Do not consider minors that do no contribute anyway
				 */
				if (A[r][0].compareTo(BigInteger.ZERO) != 0) {
					final BigInteger M[][] = BigIntegerMath.minor(A, r, 0);
					final BigInteger m = A[r][0].multiply(BigIntegerMath.det(M));
					/* recursive call */
					if (r % 2 == 0) {
						d = d.add(m);
					} else {
						d = d.subtract(m);
					}
				}
			}
		}
		return d;
	}

	/**
	 * Solve a linear system of equations.
	 *
	 * @param A
	 *            The square matrix.
	 *            If it is not of full rank, an ArithmeticException is thrown.
	 * @param rhs
	 *            The right hand side. The length of this vector must match the
	 *            matrix size;
	 *            else an ArithmeticException is thrown.
	 * @return The vector of x in A*x=rhs.
	 * @since 2010-08-28
	 * @author Richard J. Mathar
	 * @throws Error
	 */
	static public Rational[] solve(final BigInteger[][] A, final BigInteger[] rhs) throws ArithmeticException, Error {

		final int rL = A.length;
		if (rL == 0) {
			throw new ArithmeticException("zero row count in matrix");
		}

		/* column size */
		final int cL = A[0].length;
		if (cL != rL) {
			throw new ArithmeticException("Non-square matrix dim " + rL + " by " + cL);
		}
		if (rhs.length != rL) {
			throw new ArithmeticException("Right hand side dim " + rhs.length + " unequal matrix dim " + rL);
		}

		/*
		 * Gauss elimination
		 */
		final Rational x[] = new Rational[rL];

		/*
		 * copy of r.h.s ito a mutable Rationalright hand side
		 */
		for (int c = 0; c < cL; c++) {
			x[c] = new Rational(rhs[c]);
		}

		/*
		 * Create zeros downwards column c by linear combination of row c and
		 * row r.
		 */
		for (int c = 0; c < cL - 1; c++) {
			/*
			 * zero on the diagonal? swap with a non-zero row, searched with
			 * index r
			 */
			if (A[c][c].compareTo(BigInteger.ZERO) == 0) {
				boolean swpd = false;
				for (int r = c + 1; r < rL; r++) {
					if (A[r][c].compareTo(BigInteger.ZERO) != 0) {
						for (int cpr = c; cpr < cL; cpr++) {
							final BigInteger tmp = A[c][cpr];
							A[c][cpr] = A[r][cpr];
							A[r][cpr] = tmp;
						}
						final Rational tmp = x[c];
						x[c] = x[r];
						x[r] = tmp;
						swpd = true;
						break;
					}
				}
				/*
				 * not swapped with a non-zero row: determinant zero and no
				 * solution
				 */
				if (!swpd) {
					throw new ArithmeticException("Zero determinant of main matrix");
				}
			}
			/* create zero at A[c+1..cL-1][c] */
			for (int r = c + 1; r < rL; r++) {
				/*
				 * skip the cpr=c which actually sets the zero: this element is
				 * not visited again
				 */
				for (int cpr = c + 1; cpr < cL; cpr++) {
					final BigInteger tmp = A[c][c].multiply(A[r][cpr]).subtract(A[c][cpr].multiply(A[r][c]));
					A[r][cpr] = tmp;
				}
				final Rational tmp = x[r].multiply(A[c][c]).subtract(x[c].multiply(A[r][c]));
				x[r] = tmp;
			}
		}
		if (A[cL - 1][cL - 1].compareTo(BigInteger.ZERO) == 0) {
			throw new ArithmeticException("Zero determinant of main matrix");
		}
		/* backward elimination */
		for (int r = cL - 1; r >= 0; r--) {
			x[r] = x[r].divide(A[r][r]);
			for (int rpr = r - 1; rpr >= 0; rpr--) {
				x[rpr] = x[rpr].subtract(x[r].multiply(A[rpr][r]));
			}
		}

		return x;
	}

	/**
	 * The lowest common multiple
	 *
	 * @param a
	 *            The first argument
	 * @param b
	 *            The second argument
	 * @return lcm(|a|,|b|)
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public BigInteger lcm(final BigInteger a, final BigInteger b) {
		final BigInteger g = a.gcd(b);
		return a.multiply(b).abs().divide(g);
	}

	/**
	 * Evaluate the value of an integer polynomial at some integer argument.
	 *
	 * @param c
	 *            Represents the coefficients c[0]+c[1]*x+c[2]*x^2+.. of the
	 *            polynomial
	 * @param x
	 *            The abscissa point of the evaluation
	 * @return The polynomial value.
	 * @since 2010-08-27
	 * @author Richard J. Mathar
	 */
	static public BigInteger valueOf(final Vector<BigInteger> c, final BigInteger x) {
		if (c.size() == 0) {
			return BigInteger.ZERO;
		}
		BigInteger res = c.lastElement();
		for (int i = c.size() - 2; i >= 0; i--) {
			res = res.multiply(x).add(c.elementAt(i));
		}
		return res;
	}

	/**
	 * The central factorial number t(n,k) number at the indices provided.
	 *
	 * @param n
	 *            the first parameter, non-negative.
	 * @param k
	 *            the second index, non-negative.
	 * @return t(n,k)
	 * @since 2009-08-06
	 * @author Richard J. Mathar
	 * @throws Error
	 * @see <a href="https://doi.org/10.1080/01630568908816313">P. L. Butzer
	 *      et al, Num. Funct. Anal. Opt. 10 (5)( 1989) 419-488</a>
	 */
	static public Rational centrlFactNumt(final int n, final int k) throws Error {
		if (k > n || k < 0 || k % 2 != n % 2) {
			return Rational.ZERO;
		} else if (k == n) {
			return Rational.ONE;
		} else {
			/* Proposition 6.2.6 */
			final Factorial f = new Factorial();
			Rational jsum = new Rational(0, 1);
			final int kprime = n - k;
			for (int j = 0; j <= kprime; j++) {
				Rational nusum = new Rational(0, 1);
				for (int nu = 0; nu <= j; nu++) {
					Rational t = new Rational(j - 2 * nu, 2);
					t = t.pow(kprime + j);
					t = t.multiply(BigIntegerMath.binomial(j, nu));
					if (nu % 2 != 0) {
						nusum = nusum.subtract(t);
					} else {
						nusum = nusum.add(t);
					}
				}
				nusum = nusum.divide(f.at(j)).divide(n + j);
				nusum = nusum.multiply(BigIntegerMath.binomial(2 * kprime, kprime - j));
				if (j % 2 != 0) {
					jsum = jsum.subtract(nusum);
				} else {
					jsum = jsum.add(nusum);
				}
			}
			return jsum.multiply(k).multiply(BigIntegerMath.binomial(n + kprime, k));
		}
	} /* CentralFactNumt */

	/**
	 * The central factorial number T(n,k) number at the indices provided.
	 *
	 * @param n
	 *            the first parameter, non-negative.
	 * @param k
	 *            the second index, non-negative.
	 * @return T(n,k)
	 * @since 2009-08-06
	 * @author Richard J. Mathar
	 * @see <a href="https://doi.org/10.1080/01630568908816313">P. L. Butzer
	 *      et al, Num. Funct. Anal. Opt. 10 (5)( 1989) 419-488</a>
	 */
	static public Rational centrlFactNumT(final int n, final int k) {
		if (k > n || k < 0 || k % 2 != n % 2) {
			return Rational.ZERO;
		} else if (k == n) {
			return Rational.ONE;
		} else {
			/* Proposition 2.1 */
			return BigIntegerMath.centrlFactNumT(n - 2, k - 2).add(BigIntegerMath.centrlFactNumT(n - 2, k).multiply(new Rational(k * k, 4)));
		}
	} /* CentralFactNumT */

} /* BigIntegerMath */
