import java.util.*;

class RationalTester {

	public static void main(String [] args) throws Exception {

	    System.err.println("Initial tests...");

	    //---------- Testing getNumerator/getDenominator on simple fractions -- no normalization needed

	    Rational r = new Rational(1, 2);
	    if (r.getNumerator() != 1 || r.getDenominator() != 2)
		   getError(r, 1, 2);

	    normalizationCheck(r, 1, 2);      // Check for normalization of rationals

	    toStringCheck(r);          // Check toString method

	    r = new Rational(2, 4);
	    normalizationCheck(r, 2, 4);      // Check for normalization of rationals

	    toStringCheck(r);          // Check toString method

	    // Check 1-arg ructor

	    r = new Rational(3);
	    if (r.getNumerator() != 3 || r.getDenominator() != 1)
		   getError(r, 3, 1);

	    // Check that 0 denominator throws exception
	    try {
		   r = new Rational(5, 0);
		   noExceptionError("Rational(int, int)", r, 5, 0);
	    } catch(RationalException re) {
	    }

	    try {
		   r = new Rational(0, 3);
	    } catch(RationalException re) {
		   exceptionError();
	    }

	    System.err.println("done!");

	    System.err.println("Testing on some simple rationals...");

	    for (int num1 = 0; num1 < 4; num1++)
		   for (int den1 = 0; den1 < 4; den1++)
			  for (int num2 = 0; num2 < 4; num2++)
				 for (int den2 = 0; den2 < 4; den2++)
					doOneTest(num1, den1, num2, den2);

	    System.err.println("done!");

	    System.err.println("Testing on 10,000 random rationals...");

	    Random rand = new Random(new Date().getTime());

	    for (int i = 0; i < 10000; i++) {
		   int
			  num1 = rand.nextInt(1000),
			  denom1 = rand.nextInt(1000),
			  num2 = rand.nextInt(1000),
			  denom2 = rand.nextInt(1000);

		   doOneTest(num1, denom1, num2, denom2);
	    }

	   System.err.println("done!");

	   System.err.println("**** Success ****");
	}

	static void doOneTest(int num1, int denom1, int num2, int denom2) {
	    boolean expectingException = false;

	    try {
		  if (!ALLOW_ZERO_ZERO) {
			 if (num1 == 0 && denom1 == 0 ||
				 num2 == 0 && denom2 == 0) {
				 //System.err.println("Ignoring Rational(0, 0)");
				 return;
			 }
		  }

		  expectingException = denom1 == 0;

		  Rational opd1 = new Rational(num1, denom1);

		  if (expectingException) noExceptionError("Rational(int, int)", opd1, num1, denom1);

		  normalizationCheck(opd1, num1, denom1);

		  toStringCheck(opd1);

		  expectingException = denom2 == 0;

		  Rational opd2 = new Rational(num2, denom2);

		  if (expectingException) noExceptionError("Rational(int, int)", opd2, num2, denom2);

		  normalizationCheck(opd2, num2, denom2);
		  toStringCheck(opd2);

		   Rational result = opd1.add(opd2);
		   Rational myResult = new Rational(opd1.getNumerator() * opd2.getDenominator() + opd2.getNumerator() * opd1.getDenominator(),
							  opd1.getDenominator() * opd2.getDenominator());
		   if (result.getNumerator() != myResult.getNumerator() ||
				 result.getDenominator() != myResult.getDenominator())
			  binopError("+", opd1, opd2, result);

		   if (opd1.getNumerator() == 0) expectingException = true;

		   result = opd1.inv();
		   if (result.getNumerator() != opd1.getDenominator() ||
			  result.getDenominator() != opd1.getNumerator())
			  unopError("inv", opd1, result);

		   if (expectingException)
			  noExceptionError("inv", opd2, opd2.getNumerator(), opd2.getDenominator());

		   expectingException = opd2.getNumerator() == 0;

		   result = opd2.inv();
		   if (result.getNumerator() != opd2.getDenominator() ||
			  result.getDenominator() != opd2.getNumerator())
			  unopError("inv", opd2, result);

		   if (expectingException)
			  noExceptionError("inv", opd2, opd2.getNumerator(), opd2.getDenominator());

		   expectingException = false;

	    } catch (RationalException e) {
		 if (!expectingException) exceptionError();
	    }
	}

	static void toStringCheck(Rational r) {
	    if (!myToString(r).equals(r.toString())) {
		   System.err.println("\n****** Error !!!!! *****");
		   System.err.println("toString produces incorrect result of '" + r + "' for the rational " + myToString(r));
		   System.exit(1);
	    }
	}

	static String myToString(Rational r) {return r.getNumerator() + (r.getDenominator() != 1 ? "/" + r.getDenominator() : "");}

	static void normalizationCheck(Rational r, int num, int denom) {
	    if (gcd(r.getNumerator(), r.getDenominator()) != 1) {
		   System.err.println("\n****** Error !!!!! *****");
		   System.err.println(r + " is not in normalized (simplest) form");
		   System.exit(1);
	    }
	}

	static int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}

	static void getError(Rational r, int num, int den) {
	    System.err.println("\n****** Error !!!!! *****");
	    System.err.println("Error in constructor or 'get' functions");
	    System.err.println("\t'getNumerator' returns: " + r.getNumerator());
	    System.err.println("\t'getDenominator' returns: " + r.getNumerator());
	    System.err.println("\tThe numerator used to construct the Rational is " + num);
	    System.err.println("\tThe denominator used to construct the Rational is " + den);
	    System.exit(1);
	}

	static void binopError(String theOperator,  Rational opd1,  Rational opd2,  Rational result) {
	    System.err.println("\n****** Error !!!!! *****");
	    System.err.println("Incorrect result for Rational::operator " + theOperator);
	    System.err.println("\topd1: " + opd1);
	    System.err.println("\topd2: " + opd2);
	    System.err.println("\tresult: " + result);
	    System.exit(1);
	}

	static void unopError(String theOperator,  Rational opd,  Rational result) {
	    System.err.println("\n****** Error !!!!! *****");
	    System.err.println("Incorrect result for Rational::" + theOperator);
	    System.err.println("\topd1: " + opd);
	    System.err.println("\tresult: " + result);
	    System.exit(1);
	}

	static void exceptionError() {
	    System.err.println("\n****** Error !!!!! *****");
	    System.err.println("You are throwing an exception when none should be thrown");
	    System.exit(1);
	}

	static void noExceptionError(String optr,  Rational r, int num, int denom) {
	    System.err.println("\n****** Error !!!!! *****");
	    System.err.println("You are not throwing an exception when one should be thrown");
	    System.err.println("\tThe operation being performed was " + optr);
	    System.err.println("\tThe offending Rational is " + r);
	    System.err.println("\tThe numerator used to construct the Rational is " + num);
	    System.err.println("\tThe denominator used to construct the Rational is " + denom);
	    System.exit(1);
	}

	final static boolean ALLOW_ZERO_ZERO = false;
}
