package org.nevec.rjm;

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

import it.cavallium.warppi.util.Error;

/**
 * Factored integers.
 * This class contains a non-negative integer with the prime factor
 * decomposition attached.
 *
 * @since 2006-08-14
 * @since 2012-02-14 The internal representation contains the bases, and becomes
 *        sparser if few
 *        prime factors are present.
 * @author Richard J. Mathar
 */
public class Ifactor implements Cloneable, Comparable<Ifactor> {
	/**
	 * The standard representation of the number
	 */
	public BigInteger n;

	/*
	 * The bases and powers of the prime factorization.
	 * representation n = primeexp[0]^primeexp[1]*primeexp[2]^primeexp[3]*...
	 * The value 0 is represented by an empty vector, the value 1 by a vector of
	 * length 1
	 * with a single power of 0.
	 */
	public Vector<Integer> primeexp;

	final public static Ifactor ONE = new Ifactor(1);

	final public static Ifactor ZERO = new Ifactor(0);

	/**
	 * Constructor given an integer.
	 * constructor with an ordinary integer
	 *
	 * @param number
	 *            the standard representation of the integer
	 */
	public Ifactor(int number) {
		n = new BigInteger("" + number);
		primeexp = new Vector<>();
		if (number > 1) {
			int primindx = 0;
			final Prime primes = new Prime();
			/*
			 * Test division against all primes.
			 */
			while (number > 1) {
				int ex = 0;
				/*
				 * primindx=0 refers to 2, =1 to 3, =2 to 5, =3 to 7 etc
				 */
				final int p = primes.at(primindx).intValue();
				while (number % p == 0) {
					ex++;
					number /= p;
					if (number == 1) {
						break;
					}
				}
				if (ex > 0) {
					primeexp.add(new Integer(p));
					primeexp.add(new Integer(ex));
				}
				primindx++;
			}
		} else if (number == 1) {
			primeexp.add(new Integer(1));
			primeexp.add(new Integer(0));
		}
	} /* Ifactor */

	/**
	 * Constructor given a BigInteger .
	 * Constructor with an ordinary integer, calling a prime factor
	 * decomposition.
	 *
	 * @param number
	 *            the BigInteger representation of the integer
	 */
	public Ifactor(BigInteger number) {
		n = number;
		primeexp = new Vector<>();
		if (number.compareTo(BigInteger.ONE) == 0) {
			primeexp.add(new Integer(1));
			primeexp.add(new Integer(0));
		} else {
			int primindx = 0;
			final Prime primes = new Prime();
			/*
			 * Test for division against all primes.
			 */
			while (number.compareTo(BigInteger.ONE) == 1) {
				int ex = 0;
				final BigInteger p = primes.at(primindx);
				while (number.remainder(p).compareTo(BigInteger.ZERO) == 0) {
					ex++;
					number = number.divide(p);
					if (number.compareTo(BigInteger.ONE) == 0) {
						break;
					}
				}
				if (ex > 0) {
					primeexp.add(new Integer(p.intValue()));
					primeexp.add(new Integer(ex));
				}
				primindx++;
			}
		}
	} /* Ifactor */

	/**
	 * Constructor given a list of exponents of the prime factor decomposition.
	 *
	 * @param pows
	 *            the vector with the sorted list of exponents.
	 *            pows[0] is the exponent of 2, pows[1] the exponent of 3,
	 *            pows[2] the exponent of 5 etc.
	 *            Note that this list does not include the primes, but assumes a
	 *            continuous prime-smooth basis.
	 */
	public Ifactor(final Vector<Integer> pows) {
		primeexp = new Vector<>(2 * pows.size());
		if (pows.size() > 0) {
			n = BigInteger.ONE;
			final Prime primes = new Prime();
			/*
			 * Build the full number by the product of all powers of the primes.
			 */
			for (int primindx = 0; primindx < pows.size(); primindx++) {
				final int ex = pows.elementAt(primindx).intValue();
				final BigInteger p = primes.at(primindx);
				n = n.multiply(p.pow(ex));
				primeexp.add(new Integer(p.intValue()));
				primeexp.add(new Integer(ex));
			}
		} else {
			n = BigInteger.ZERO;
		}
	} /* Ifactor */

	/**
	 * Copy constructor.
	 *
	 * @param oth
	 *            the value to be copied
	 */
	public Ifactor(final Ifactor oth) {
		n = oth.n;
		primeexp = oth.primeexp;
	} /* Ifactor */

	/**
	 * Deep copy.
	 *
	 * @since 2009-08-14
	 */
	@Override
	public Ifactor clone() {
		/*
		 * Line not used:
		 *
		 * Vector<Integer> p = (Vector<Integer>)primeexp.clone();
		 *
		 */
		final Ifactor cl = new Ifactor(0);
		cl.n = new BigInteger("" + n);
		return cl;
	} /* Ifactor.clone */

	/**
	 * Comparison of two numbers.
	 * The value of this method is in allowing the Vector<>.contains() calls
	 * that use the value,
	 * not the reference for comparison.
	 *
	 * @param oth
	 *            the number to compare this with.
	 * @return true if both are the same numbers, false otherwise.
	 */
	public boolean equals(final Ifactor oth) {
		return n.compareTo(oth.n) == 0;
	} /* Ifactor.equals */

	/**
	 * Multiply with another positive integer.
	 *
	 * @param oth
	 *            the second factor.
	 * @return the product of both numbers.
	 */
	public Ifactor multiply(final BigInteger oth) {
		/*
		 * the optimization is to factorize oth _before_ multiplying
		 */
		return multiply(new Ifactor(oth));
	} /* Ifactor.multiply */

	/**
	 * Multiply with another positive integer.
	 *
	 * @param oth
	 *            the second factor.
	 * @return the product of both numbers.
	 */
	public Ifactor multiply(final int oth) {
		/*
		 * the optimization is to factorize oth _before_ multiplying
		 */
		return multiply(new Ifactor(oth));
	} /* Ifactor.multiply */

	/**
	 * Multiply with another positive integer.
	 *
	 * @param oth
	 *            the second factor.
	 * @return the product of both numbers.
	 */
	public Ifactor multiply(final Ifactor oth) {
		/*
		 * This might be done similar to the lcm() implementation by adding
		 * the powers of the components and calling the constructor with the
		 * list of exponents. This here is the simplest implementation, but slow
		 * because
		 * it calls another prime factorization of the product:
		 * return( new Ifactor(n.multiply(oth.n))) ;
		 */
		return multGcdLcm(oth, 0);
	}

	/**
	 * Lowest common multiple of this with oth.
	 *
	 * @param oth
	 *            the second parameter of lcm(this,oth)
	 * @return the lowest common multiple of both numbers. Returns zero
	 *         if any of both arguments is zero.
	 */
	public Ifactor lcm(final Ifactor oth) {
		return multGcdLcm(oth, 2);
	}

	/**
	 * Greatest common divisor of this and oth.
	 *
	 * @param oth
	 *            the second parameter of gcd(this,oth)
	 * @return the lowest common multiple of both numbers. Returns zero
	 *         if any of both arguments is zero.
	 */
	public Ifactor gcd(final Ifactor oth) {
		return multGcdLcm(oth, 1);
	}

	/**
	 * Multiply with another positive integer.
	 *
	 * @param oth
	 *            the second factor.
	 * @param type
	 *            0 to multiply, 1 for gcd, 2 for lcm
	 * @return the product, gcd or lcm of both numbers.
	 */
	protected Ifactor multGcdLcm(final Ifactor oth, final int type) {
		final Ifactor prod = new Ifactor(0);
		/*
		 * skip the case where 0*something =0, falling thru to the empty
		 * representation for 0
		 */
		if (primeexp.size() != 0 && oth.primeexp.size() != 0) {
			/*
			 * Cases of 1 times something return something.
			 * Cases of lcm(1, something) return something.
			 * Cases of gcd(1, something) return 1.
			 */
			if (primeexp.firstElement().intValue() == 1 && type == 0) {
				return oth;
			} else if (primeexp.firstElement().intValue() == 1 && type == 2) {
				return oth;
			} else if (primeexp.firstElement().intValue() == 1 && type == 1) {
				return this;
			} else if (oth.primeexp.firstElement().intValue() == 1 && type == 0) {
				return this;
			} else if (oth.primeexp.firstElement().intValue() == 1 && type == 2) {
				return this;
			} else if (oth.primeexp.firstElement().intValue() == 1 && type == 1) {
				return oth;
			} else {
				int idxThis = 0;
				int idxOth = 0;
				switch (type) {
					case 0:
						prod.n = n.multiply(oth.n);
						break;
					case 1:
						prod.n = n.gcd(oth.n);
						break;
					case 2:
						/*
						 * the awkward way, lcm = product divided by gcd
						 */
						prod.n = n.multiply(oth.n).divide(n.gcd(oth.n));
						break;
				}

				/*
				 * scan both representations left to right, increasing prime
				 * powers
				 */
				while (idxOth < oth.primeexp.size() || idxThis < primeexp.size()) {
					if (idxOth >= oth.primeexp.size()) {
						/*
						 * exhausted the list in oth.primeexp; copy over the
						 * remaining 'this'
						 * if multiplying or lcm, discard if gcd.
						 */
						if (type == 0 || type == 2) {
							prod.primeexp.add(primeexp.elementAt(idxThis));
							prod.primeexp.add(primeexp.elementAt(idxThis + 1));
						}
						idxThis += 2;
					} else if (idxThis >= primeexp.size()) {
						/*
						 * exhausted the list in primeexp; copy over the
						 * remaining 'oth'
						 */
						if (type == 0 || type == 2) {
							prod.primeexp.add(oth.primeexp.elementAt(idxOth));
							prod.primeexp.add(oth.primeexp.elementAt(idxOth + 1));
						}
						idxOth += 2;
					} else {
						Integer p;
						int ex;
						switch (primeexp.elementAt(idxThis).compareTo(oth.primeexp.elementAt(idxOth))) {
							case 0:
								/* same prime bases p in both factors */
								p = primeexp.elementAt(idxThis);
								switch (type) {
									case 0:
										/* product means adding exponents */
										ex = primeexp.elementAt(idxThis + 1).intValue() + oth.primeexp.elementAt(idxOth + 1).intValue();
										break;
									case 1:
										/* gcd means minimum of exponents */
										ex = Math.min(primeexp.elementAt(idxThis + 1).intValue(), oth.primeexp.elementAt(idxOth + 1).intValue());
										break;
									default:
										/* lcm means maximum of exponents */
										ex = Math.max(primeexp.elementAt(idxThis + 1).intValue(), oth.primeexp.elementAt(idxOth + 1).intValue());
										break;
								}
								prod.primeexp.add(p);
								prod.primeexp.add(new Integer(ex));
								idxOth += 2;
								idxThis += 2;
								break;
							case 1:
								/*
								 * this prime base bigger than the other and
								 * taken later
								 */
								if (type == 0 || type == 2) {
									prod.primeexp.add(oth.primeexp.elementAt(idxOth));
									prod.primeexp.add(oth.primeexp.elementAt(idxOth + 1));
								}
								idxOth += 2;
								break;
							default:
								/*
								 * this prime base smaller than the other and
								 * taken now
								 */
								if (type == 0 || type == 2) {
									prod.primeexp.add(primeexp.elementAt(idxThis));
									prod.primeexp.add(primeexp.elementAt(idxThis + 1));
								}
								idxThis += 2;
						}
					}
				}
			}
		}
		return prod;
	} /* Ifactor.multGcdLcm */

	/**
	 * Integer division through another positive integer.
	 *
	 * @param oth
	 *            the denominator.
	 * @return the division of this through the oth, discarding the remainder.
	 */
	public Ifactor divide(final Ifactor oth) {
		/*
		 * todo: it'd probably be faster to cancel the gcd(this,oth) first in
		 * the prime power
		 * representation, which would avoid a more strenous factorization of
		 * the integer ratio
		 */
		return new Ifactor(n.divide(oth.n));
	} /* Ifactor.divide */

	/**
	 * Summation with another positive integer
	 *
	 * @param oth
	 *            the other term.
	 * @return the sum of both numbers
	 */
	public Ifactor add(final BigInteger oth) {
		/*
		 * avoid refactorization if oth is zero...
		 */
		if (oth.compareTo(BigInteger.ZERO) != 0) {
			return new Ifactor(n.add(oth));
		} else {
			return this;
		}
	} /* Ifactor.add */

	/**
	 * Exponentiation with a positive integer.
	 *
	 * @param exponent
	 *            the non-negative exponent
	 * @return n^exponent. If exponent=0, the result is 1.
	 */
	public Ifactor pow(final int exponent) throws ArithmeticException {
		/*
		 * three simple cases first
		 */
		if (exponent < 0) {
			throw new ArithmeticException("Cannot raise " + toString() + " to negative " + exponent);
		} else if (exponent == 0) {
			return new Ifactor(1);
		} else if (exponent == 1) {
			return this;
		}

		/*
		 * general case, the vector with the prime factor powers, which are
		 * component-wise
		 * exponentiation of the individual prime factor powers.
		 */
		final Ifactor pows = new Ifactor(0);
		for (int i = 0; i < primeexp.size(); i += 2) {
			final Integer p = primeexp.elementAt(i);
			final int ex = primeexp.elementAt(i + 1).intValue();
			pows.primeexp.add(p);
			pows.primeexp.add(new Integer(ex * exponent));
		}
		return pows;
	} /* Ifactor.pow */

	/**
	 * Pulling the r-th root.
	 *
	 * @param r
	 *            the positive or negative (nonzero) root.
	 * @return n^(1/r).
	 *         The return value falls into the Ifactor class if r is positive,
	 *         but if r is negative
	 *         a Rational type is needed.
	 * @throws Error
	 * @since 2009-05-18
	 */
	public Rational root(final int r) throws ArithmeticException, Error {
		if (r == 0) {
			throw new ArithmeticException("Cannot pull zeroth root of " + toString());
		} else if (r < 0) {
			/*
			 * a^(-1/b)= 1/(a^(1/b))
			 */
			final Rational invRoot = root(-r);
			return Rational.ONE.divide(invRoot);
		} else {
			final BigInteger pows = BigInteger.ONE;
			for (int i = 0; i < primeexp.size(); i += 2) {
				/*
				 * all exponents must be multiples of r to succeed (that is, to
				 * stay in the range of rational results).
				 */
				final int ex = primeexp.elementAt(i + 1).intValue();
				if (ex % r != 0) {
					throw new ArithmeticException("Cannot pull " + r + "th root of " + toString());
				}

				pows.multiply(new BigInteger("" + primeexp.elementAt(i)).pow(ex / r));
			}
			/*
			 * convert result to a Rational; unfortunately this will loose the
			 * prime factorization
			 */
			return new Rational(pows);
		}
	} /* Ifactor.root */

	/**
	 * The set of positive divisors.
	 *
	 * @return the vector of divisors of the absolute value, sorted.
	 * @since 2010-08-27
	 */
	public Vector<BigInteger> divisors() {
		/*
		 * Recursive approach: the divisors of p1^e1*p2^e2*..*py^ey*pz^ez are
		 * the divisors that don't contain the factor pz, and the
		 * the divisors that contain any power of pz between 1 and up to ez
		 * multiplied
		 * by 1 or by a product that contains the factors p1..py.
		 */
		final Vector<BigInteger> d = new Vector<>();
		if (n.compareTo(BigInteger.ZERO) == 0) {
			return d;
		}
		d.add(BigInteger.ONE);
		if (n.compareTo(BigInteger.ONE) > 0) {
			/* Computes sigmaIncopml(p1^e*p2^e2...*py^ey) */
			final Ifactor dp = dropPrime();

			/* get ez */
			final int ez = primeexp.lastElement().intValue();

			final Vector<BigInteger> partd = dp.divisors();

			/* obtain pz by lookup in the prime list */
			final BigInteger pz = new BigInteger(primeexp.elementAt(primeexp.size() - 2).toString());

			/*
			 * the output contains all products of the form partd[]*pz^ez, ez>0,
			 * and with the exception of the 1, all these are appended.
			 */
			for (int i = 1; i < partd.size(); i++) {
				d.add(partd.elementAt(i));
			}
			for (int e = 1; e <= ez; e++) {
				final BigInteger pzez = pz.pow(e);
				for (int i = 0; i < partd.size(); i++) {
					d.add(partd.elementAt(i).multiply(pzez));
				}
			}
		}
		Collections.sort(d);
		return d;
	} /* Ifactor.divisors */

	/**
	 * Sum of the divisors of the number.
	 *
	 * @return the sum of all divisors of the number, 1+....+n.
	 */
	public Ifactor sigma() {
		return sigma(1);
	} /* Ifactor.sigma */

	/**
	 * Sum of the k-th powers of divisors of the number.
	 *
	 * @return the sum of all divisors of the number, 1^k+....+n^k.
	 */
	public Ifactor sigma(final int k) {
		/*
		 * the question is whether keeping a factorization is worth the effort
		 * or whether one should simply multiply these to return a BigInteger...
		 */
		if (n.compareTo(BigInteger.ONE) == 0) {
			return Ifactor.ONE;
		} else if (n.compareTo(BigInteger.ZERO) == 0) {
			return Ifactor.ZERO;
		} else {
			/*
			 * multiplicative: sigma_k(p^e) = [p^(k*(e+1))-1]/[p^k-1]
			 * sigma_0(p^e) = e+1.
			 */
			Ifactor resul = Ifactor.ONE;
			for (int i = 0; i < primeexp.size(); i += 2) {
				final int ex = primeexp.elementAt(i + 1).intValue();
				if (k == 0) {
					resul = resul.multiply(ex + 1);
				} else {
					final Integer p = primeexp.elementAt(i);
					final BigInteger num = new BigInteger(p.toString()).pow(k * (ex + 1)).subtract(BigInteger.ONE);
					final BigInteger deno = new BigInteger(p.toString()).pow(k).subtract(BigInteger.ONE);
					/*
					 * This division is of course exact, no remainder
					 * The costly prime factorization is hidden here.
					 */
					final Ifactor f = new Ifactor(num.divide(deno));
					resul = resul.multiply(f);
				}
			}
			return resul;
		}
	} /* Ifactor.sigma */

	/**
	 * Divide through the highest possible power of the highest prime.
	 * If the current number is the prime factor product p1^e1 * p2*e2*
	 * p3^e3*...*py^ey * pz^ez,
	 * the value returned has the final factor pz^ez eliminated, which gives
	 * p1^e1 * p2*e2* p3^e3*...*py^ey.
	 *
	 * @return the new integer obtained by removing the highest prime power.
	 *         If this here represents 0 or 1, it is returned without change.
	 * @since 2006-08-20
	 */
	public Ifactor dropPrime() {
		/*
		 * the cases n==1 or n ==0
		 */
		if (n.compareTo(BigInteger.ONE) <= 0) {
			return this;
		}

		/*
		 * The cases n>1
		 * Start empty. Copy all but the last factor over to the result
		 * the vector with the new prime factor powers, which contain the
		 * old prime factor powers up to but not including the last one.
		 */
		final Ifactor pows = new Ifactor(0);
		pows.n = BigInteger.ONE;
		for (int i = 0; i < primeexp.size() - 2; i += 2) {
			pows.primeexp.add(primeexp.elementAt(i));
			pows.primeexp.add(primeexp.elementAt(i + 1));
			final BigInteger p = new BigInteger(primeexp.elementAt(i).toString());
			final int ex = primeexp.elementAt(i + 1).intValue();
			pows.n = pows.n.multiply(p.pow(ex));
		}
		return pows;
	} /* Ifactor.dropPrime */

	/**
	 * Test whether this is a square of an integer (perfect square).
	 *
	 * @return true if this is an integer squared (including 0), else false
	 */
	public boolean issquare() {
		/*
		 * check the exponents, located at the odd-indexed positions
		 */
		for (int i = 1; i < primeexp.size(); i += 2) {
			if (primeexp.elementAt(i).intValue() % 2 != 0) {
				return false;
			}
		}
		return true;
	} /* Ifactor.issquare */

	/**
	 * The sum of the prime factor exponents, with multiplicity.
	 *
	 * @return the sum over the primeexp numbers
	 */
	public int bigomega() {
		int resul = 0;
		for (int i = 1; i < primeexp.size(); i += 2) {
			resul += primeexp.elementAt(i).intValue();
		}
		return resul;
	} /* Ifactor.bigomega */

	/**
	 * The sum of the prime factor exponents, without multiplicity.
	 *
	 * @return the number of distinct prime factors.
	 * @since 2008-10-16
	 */
	public int omega() {
		return primeexp.size() / 2;
	} /* Ifactor.omega */

	/**
	 * The square-free part.
	 *
	 * @return the minimum m such that m times this number is a square.
	 * @since 2008-10-16
	 */
	public BigInteger core() {
		BigInteger resul = BigInteger.ONE;
		for (int i = 0; i < primeexp.size(); i += 2) {
			if (primeexp.elementAt(i + 1).intValue() % 2 != 0) {
				resul = resul.multiply(new BigInteger(primeexp.elementAt(i).toString()));
			}
		}
		return resul;
	} /* Ifactor.core */

	/**
	 * The Moebius function.
	 * 1 if n=1, else, if k is the number of distinct prime factors, return
	 * (-1)^k,
	 * else, if k has repeated prime factors, return 0.
	 *
	 * @return the moebius function.
	 */
	public int moebius() {
		if (n.compareTo(BigInteger.ONE) <= 0) {
			return 1;
		}
		/* accumulate number of different primes in k */
		int k = 1;
		for (int i = 0; i < primeexp.size(); i += 2) {
			final int e = primeexp.elementAt(i + 1).intValue();
			if (e > 1) {
				return 0;
			} else if (e == 1) {
				/* accumulates (-1)^k */
				k *= -1;
			}

		}
		return k;
	} /* Ifactor.moebius */

	/**
	 * Maximum of two values.
	 *
	 * @param oth
	 *            the number to compare this with.
	 * @return the larger of the two values.
	 */
	public Ifactor max(final Ifactor oth) {
		if (n.compareTo(oth.n) >= 0) {
			return this;
		} else {
			return oth;
		}
	} /* Ifactor.max */

	/**
	 * Minimum of two values.
	 *
	 * @param oth
	 *            the number to compare this with.
	 * @return the smaller of the two values.
	 */
	public Ifactor min(final Ifactor oth) {
		if (n.compareTo(oth.n) <= 0) {
			return this;
		} else {
			return oth;
		}
	} /* Ifactor.min */

	/**
	 * Maximum of a list of values.
	 *
	 * @param set
	 *            list of numbers.
	 * @return the largest in the list.
	 */
	public static Ifactor max(final Vector<Ifactor> set) {
		Ifactor resul = set.elementAt(0);
		for (int i = 1; i < set.size(); i++) {
			resul = resul.max(set.elementAt(i));
		}
		return resul;
	} /* Ifactor.max */

	/**
	 * Minimum of a list of values.
	 *
	 * @param set
	 *            list of numbers.
	 * @return the smallest in the list.
	 */
	public static Ifactor min(final Vector<Ifactor> set) {
		Ifactor resul = set.elementAt(0);
		for (int i = 1; i < set.size(); i++) {
			resul = resul.min(set.elementAt(i));
		}
		return resul;
	} /* Ifactor.min */

	/**
	 * Compare value against another Ifactor
	 *
	 * @param oth
	 *            The value to be compared agains.
	 * @return 1, 0 or -1 according to being larger, equal to or smaller than
	 *         oth.
	 * @since 2012-02-15
	 */
	@Override
	public int compareTo(final Ifactor oth) {
		return n.compareTo(oth.n);
	} /* compareTo */

	/**
	 * Convert to printable format
	 *
	 * @return a string of the form n:prime^pow*prime^pow*prime^pow...
	 */
	@Override
	public String toString() {
		String resul = new String(n.toString() + ":");
		if (n.compareTo(BigInteger.ONE) == 0) {
			resul += "1";
		} else {
			boolean firstMul = true;
			for (int i = 0; i < primeexp.size(); i += 2) {
				if (!firstMul) {
					resul += "*";
				}
				if (primeexp.elementAt(i + 1).intValue() > 1) {
					resul += primeexp.elementAt(i).toString() + "^" + primeexp.elementAt(i + 1).toString();
				} else {
					resul += primeexp.elementAt(i).toString();
				}
				firstMul = false;
			}
		}
		return resul;
	} /* Ifactor.toString */

	/**
	 * Test program.
	 * It takes a single argument n and prints the integer factorizaton.<br>
	 * java -cp . org.nevec.rjm.Ifactor n<br>
	 */
	public static void main(final String[] args) throws Exception {
		final BigInteger n = new BigInteger(args[0]);
		System.out.println(new Ifactor(n));
	} /* Ifactor.main */
} /* Ifactor */
