/******************************************************************************
 *  
 * Name     : Scott Kristjanson
 * Number   : 123456789
 * email    : skristja@sfu.ca 
 * Course   : Cmpt135
 * Assmt    : 1
 * Question : C5
 *
 * File     : RationalTest.cpp
 * 
 * This implements the Rational Class test program for Assignment 2 Question C5.
 * For this to work, the student must have created a new header file called
 * rational.h (which this file includes) and have overloaded all the required
 * operators in order to allow this to compile and run, totally unchanged.
 *
 ******************************************************************************/

#include <cstdlib>
#include <iostream>
#include <sstream>    // stringstream
#include <string>
#include <iomanip>    // setw 
#include "Rational.h"

using namespace std;


// Test with Assignment #1 version of main by setting this to 1
// Test with Assignment #2 version of main by setting this to 2
// Automated Test Version based on Assignment #2, set this to 3
#define USE_MAIN_VERSION 3

// For Assignment 2 Bonus Quetion D2, need to define the following
// symbol by uncommenting the following line, or using the compiler
// option -D BONUS_D2
// ****************************************************************
// #define BONUS_D2

struct Results{
    string  testName;
    int     numTests  = 0;
    int     numPassed = 0;
    int     numFailed = 0;
};

const int NUM_TESTS = 11;

struct SummaryResults {
       Results Summary;
       int     numResults;
       Results result[NUM_TESTS];
};


////////////////////////////////////////////////
//
// Global variables.
//
////////////////////////////////////////////////

// The two prompts used by main() start with this string)
string promptBase = "Enter a rational number in the form ";


void reportNonRational(Rational r1, Rational r2) {
    int  a,b,c,d;
    bool bothBad = false;
    
    a = r1.getNumerator  ();
    b = r1.getDenominator();
    c = r2.getNumerator  ();
    d = r2.getDenominator();
    
    if ((b==0) && (d==0))
        bothBad = true;

    if ((b==0) || (d==0)) {
        if (b==0) 
            cout << a << "/" << b;
        if (bothBad)
            cout << " and ";
        if (d==0)
            cout << c << "/" << d;
        if (bothBad)
            cout << " are not Rational numbers";
        else
            cout << " is not a Rational number";
        cout << ". Denominator cannot be zero." << endl;  
    }
}

////////////////////////////////////////////////
//
// Main test function - Assignment 1 version
//
////////////////////////////////////////////////
int maintest_v1() {
    Rational rational1, rational2;
    Rational sum, diff, prod, div, neg;
    int      a,b,c,d;

    // Prompt for and input the two Rational numbers for the test run
    cout << promptBase << "a/b: ";
    rational1.input(cin);
    
    cout << promptBase << "c/d: ";
    rational2.input(cin);
    
    // Use the getters to get the numerators and denominators
    a = rational1.getNumerator  ();
    b = rational1.getDenominator();
    c = rational2.getNumerator  ();
    d = rational2.getDenominator();
    cout << "a=" << a << " b=" << b << " c=" << c << " d=" << d << endl;
    
    // Bonus: check for zero denominators, those are not Rational numbers!
    if ((b == 0) || (d == 0)) {
        reportNonRational(rational1, rational2);
        return -1;        
    }
    
    // Testing the add method
    cout << "\nTesting: a/b + c/d = (a * d + b * c) / (b * d)" << endl;
    rational1.output(cout);
    cout << " + ";
    rational2.output(cout);
    cout <<" = ("<<a<<" * "<<d<<" + "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    sum = rational1.add(rational2);
    sum.output(cout);
    cout << endl;
            
    // Testing the sub method
    cout << "\nTesting: a/b - c/d = (a * d - b * c) / (b * d)" << endl;
    rational1.output(cout);
    cout << " - ";
    rational2.output(cout);
    cout <<" = ("<<a<<" * "<<d<<" - "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    diff = rational1.sub(rational2);
    diff.output(cout);
    cout << endl;   
            
    // Testing the mul method
    cout << "\nTesting: (a/b) * (c/d) = (a * c) / (b * d)" << endl;
    rational1.output(cout);
    cout << " * ";
    rational2.output(cout);
    cout <<" = ("<<a<<" * "<<c<<") / ("<<b<<" * "<<d<<") = ";
    prod = rational1.mul(rational2);
    prod.output(cout);
    cout << endl;
            
                
    // Testing the div method
    cout << "\nTesting: (a/b) / (c/d) = (a * d) / (c * b)" << endl;
    rational1.output(cout);
    cout << " / ";
    rational2.output(cout);
    cout <<" = ("<<a<<" * "<<d<<") / ("<<c<<" * "<<b<<") = ";
    div = rational1.div(rational2);
    div.output(cout);
    cout << endl;
    
                   
    // Testing the neg method
    cout << "\nTesting: -(a/b) = (-a/b)" << endl;
    cout << "-(";
    rational1.output(cout);
    cout <<") = ("<<-a<<" / "<<b<<") = ";
    neg = rational1.neg();
    neg.output(cout);
    cout << endl;

    // Testing the less method
    // Actually given the output spec, it does not actually use less()
    cout << "\nTesting: (a/b) < (c/d) means (a * d) < (c * b)" << endl;
    rational1.output(cout);
    cout << " < ";
    rational2.output(cout);
    cout <<" means ("<<a<<" * "<<d<<") < ("<<c<<" * "<<b<<") means ";
    Rational ad(a*d);
    Rational cb(c*b);
    ad.output(cout);
    cout << " < ";
    cb.output(cout);
    cout << endl;
    
    
    // Testing the equal method
    // Actually given the output spec, it does not actually  use equal()
    cout << "\nTesting: (a/b) == (c/d) means (a * d) == (c * b)" << endl;
    rational1.output(cout);
    cout << " == ";
    rational2.output(cout);
    cout << " means ("<<a<<" * "<<d<<") == ("<<c<<" * "<<b<<") means ";
    ad.output(cout);
    cout << " == ";
    cb.output(cout);
    cout << endl;
}

////////////////////////////////////////////////
//
// Main test function - Assignment 2 version
//
////////////////////////////////////////////////
int maintest_v2() {
    Rational rational1, rational2;
    int      a,b,c,d;

    // Prompt for and input the two Rational numbers for the test run
    cout << promptBase << "a/b: ";
    cin  >> rational1;
    
    cout << promptBase << "c/d: ";
    cin  >> rational2;
    
    // Use the getters to get the numerators and denominators
    a = rational1.getNumerator  ();
    b = rational1.getDenominator();
    c = rational2.getNumerator  ();
    d = rational2.getDenominator();
    cout << "a=" << a << " b=" << b << " c=" << c << " d=" << d << endl;
    
    // Bonus: check for zero denominators, those are not Rational numbers!
    if ((b == 0) || (d == 0)) {
        reportNonRational(rational1, rational2);
        return -1;        
    }
    
    // Testing the add operator
    cout << "\nTesting: a/b + c/d = (a * d + b * c) / (b * d)" << endl;
    cout << rational1 << " + " << rational2;
    cout <<" = ("<<a<<" * "<<d<<" + "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    cout << rational1 + rational2 << endl;

    // Testing the sub operator
    cout << "\nTesting: a/b - c/d = (a * d - b * c) / (b * d)" << endl;
    cout << rational1 << " - " << rational2;
    cout <<" = ("<<a<<" * "<<d<<" - "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    cout << rational1 - rational2 << endl;   
            
    // Testing the mul operator
    cout << "\nTesting: (a/b) * (c/d) = (a * c) / (b * d)" << endl;
    cout << rational1 << " * " << rational2;
    cout <<" = ("<<a<<" * "<<c<<") / ("<<b<<" * "<<d<<") = ";
    cout << rational1 * rational2 << endl;
            
    // Testing the div method
    cout << "\nTesting: (a/b) / (c/d) = (a * d) / (c * b)" << endl;
    cout << rational1 << " / " << rational2;
    cout <<" = ("<<a<<" * "<<d<<") / ("<<c<<" * "<<b<<") = ";
    cout << rational1 / rational2 << endl;
    
    // Testing the neg method
    cout << "\nTesting: -(a/b) = (-a/b)" << endl;
    cout << "-(" << rational1;
    cout <<") = ("<<-a<<" / "<<b<<") = ";
    cout << -rational1 << endl;

    // Testing the less method
    // Actually given the output spec, it does not actually use less()
    cout << "\nTesting: (a/b) < (c/d) means (a * d) < (c * b)" << endl;
    cout << rational1 << " < "<< rational2;
    cout <<" means ("<<a<<" * "<<d<<") < ("<<c<<" * "<<b<<") means ";
    Rational ad(a*d);
    Rational cb(c*b);
    cout << ad << " < " << cb << endl;
    
    // Testing the equal method
    // Actually given the output spec, it does not actually  use equal()
    cout << "\nTesting: (a/b) == (c/d) means (a * d) == (c * b)" << endl;
    cout << rational1 << " == " << rational2;
    cout << " means ("<<a<<" * "<<d<<") == ("<<c<<" * "<<b<<") means ";
    cout << ad << " == " << cb << endl;
}


////////////////////////////////////////////////
//
// Main test function - Assignment 2 version
//
////////////////////////////////////////////////
int maintest_v3(ostream& outStream, Rational rational1, Rational rational2) {
    int      a,b,c,d;
    
    // Use the getters to get the numerators and denominators
    a = rational1.getNumerator  ();
    b = rational1.getDenominator();
    c = rational2.getNumerator  ();
    d = rational2.getDenominator();
    outStream << "a=" << a << " b=" << b << " c=" << c << " d=" << d << endl;
    
    // Bonus: check for zero denominators, those are not Rational numbers!
    if ((b == 0) || (d == 0)) {
        reportNonRational(rational1, rational2);
        return -1;        
    }
    
    // Testing the add operator
    outStream << "\nTesting: a/b + c/d = (a * d + b * c) / (b * d)" << endl;
    outStream << rational1 << " + " << rational2;
    outStream <<" = ("<<a<<" * "<<d<<" + "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    outStream << rational1 + rational2 << endl;

    // Testing the sub operator
    outStream << "\nTesting: a/b - c/d = (a * d - b * c) / (b * d)" << endl;
    outStream << rational1 << " - " << rational2;
    outStream <<" = ("<<a<<" * "<<d<<" - "<<b<<" * "<<c<<")/("<<b<<" * "<<d<<") = ";
    outStream << rational1 - rational2 << endl;   
            
    // Testing the mul operator
    outStream << "\nTesting: (a/b) * (c/d) = (a * c) / (b * d)" << endl;
    outStream << rational1 << " * " << rational2;
    outStream <<" = ("<<a<<" * "<<c<<") / ("<<b<<" * "<<d<<") = ";
    outStream << rational1 * rational2 << endl;
            
    // Testing the div method
    outStream << "\nTesting: (a/b) / (c/d) = (a * d) / (c * b)" << endl;
    outStream << rational1 << " / " << rational2;
    outStream <<" = ("<<a<<" * "<<d<<") / ("<<c<<" * "<<b<<") = ";
    outStream << rational1 / rational2 << endl;
    
    // Testing the neg method
    outStream << "\nTesting: -(a/b) = (-a/b)" << endl;
    outStream << "-(" << rational1;
    outStream <<") = ("<<-a<<" / "<<b<<") = ";
    outStream << -rational1 << endl;

    // Testing the less method
    // Actually given the output spec, it does not actually use less()
    outStream << "\nTesting: (a/b) < (c/d) means (a * d) < (c * b)" << endl;
    outStream << rational1 << " < "<< rational2;
    outStream <<" means ("<<a<<" * "<<d<<") < ("<<c<<" * "<<b<<") means ";
    Rational ad(a*d);
    Rational cb(c*b);
    outStream << ad << " < " << cb << endl;
    
    // Testing the equal method
    // Actually given the output spec, it does not actually  use equal()
    outStream << "\nTesting: (a/b) == (c/d) means (a * d) == (c * b)" << endl;
    outStream << rational1 << " == " << rational2;
    outStream << " means ("<<a<<" * "<<d<<") == ("<<c<<" * "<<b<<") means ";
    outStream << ad << " == " << cb << endl;
}


/******************************************************************************
 *
 * 
 *                Version 3 of the test program for marking
 *                =========================================
 *
 * 
 *******************************************************************************/

/******************************************************************************
 *
 * The Gold Standard
 * =================
 * When testing student code, we need to start from a known "Good" solution
 * for such operators as equality and reduce. These functions are taken from
 * the Solution and renamed with the prefix "golden_" so we can tell when we
 * are invoking verified functions versus student functions.
 * 
 * 
 *******************************************************************************/


/******************************************************************************
 *
 * golden_gcd
 * ==========
 * Greatest Common Divisor computed using Euclid's method. This helper function
 * is needed by reduce to simplify Rational numbers.
 *
 * Inputs:
 *    a,b  - two ints we want the gcd of
 * 
 * Returns:
 *    int  - the greatest common divisor between a and b
 *
 * Source: 
 * http://stackoverflow.com/questions/28267611/using-euclid-algorithm-to-find-gcfgcd
 * 
 ******************************************************************************/
int golden_gcd(int a, int b) {     
    // The loop below goes into an infinite loop if a == 0 or b == 0
    // Let's avoid an infinite loop by returning the non-zero parameter.
    if (a == 0)
        return b;
    if (b == 0)
        return a;
    
    // This implementation of Euclid's algorithm assumes a and b are positive
    a = abs(a);
    b = abs(b);
       
    // Euclid's algorithm from StackOverflow (see citation above)
    while (a != b) {
        if (a > b) {
            a -= b;
        }
        else {
            b -= a;
        }
    }
    return a;
}


// Golden_reduce depends on the student's Rational Constructor working
Rational golden_reduce(const Rational r) 
{
    int numerator   = r.getNumerator  ();
    int denominator = r.getDenominator();
    int GreatestCommonDivisor = golden_gcd(numerator, denominator);
        
    numerator   /= GreatestCommonDivisor;
    denominator /= GreatestCommonDivisor;
    
    Rational reducedR(numerator, denominator);
    return   reducedR;
}

bool golden_equal(Rational a, Rational b) {
    a = golden_reduce(a);
    b = golden_reduce(b);
    if ((a.getNumerator() == b.getNumerator()) && (a.getDenominator() == b.getDenominator()))
        return true;
    else
        return false;
    }






void displayResult(ostream& outStream, bool passed, Results results) {
    if (!passed)
       outStream << endl << "*** " << "Testcase " << results.numTests;
    outStream << ": ";
    outStream << (passed ? "Passed! " : "Failed! ");
    outStream << endl;
}


void displayResults(ostream& outStream, Results& results) {
    outStream << setw(2)  << results.numTests << " test";
    outStream << ((results.numTests > 1) ? "s, " : ", ");  
    if (results.numPassed > 0) {
        if (results.numPassed == results.numTests)
           outStream << "All ";
       else
           outStream << "*** ";
       outStream << setw(2)  << results.numPassed << " passed ";
    }
    if (results.numFailed > 0) {
       if (results.numTests == results.numFailed )
           outStream << "All ";
       else
           outStream << "*** ";
       outStream << setw(2)  << results.numFailed << " failed ";
    }
    outStream << endl;
}

const string expOutput_15over3_10over2[] = {
"a=15 b=3 c=10 d=2","",
"Testing: a/b + c/d = (a * d + b * c) / (b * d)",
"15/3 + 10/2 = (15 * 2 + 3 * 10)/(3 * 2) = 10/1","",
"Testing: a/b - c/d = (a * d - b * c) / (b * d)",
"15/3 - 10/2 = (15 * 2 - 3 * 10)/(3 * 2) = 0/1","",
"Testing: (a/b) * (c/d) = (a * c) / (b * d)",
"15/3 * 10/2 = (15 * 10) / (3 * 2) = 25/1","",
"Testing: (a/b) / (c/d) = (a * d) / (c * b)",
"15/3 / 10/2 = (15 * 2) / (10 * 3) = 1/1","",
"Testing: -(a/b) = (-a/b)",
"-(15/3) = (-15 / 3) = -5/1","",
"Testing: (a/b) < (c/d) means (a * d) < (c * b)",
"15/3 < 10/2 means (15 * 2) < (10 * 3) means 30/1 < 30/1","",
"Testing: (a/b) == (c/d) means (a * d) == (c * b)",
"15/3 == 10/2 means (15 * 2) == (10 * 3) means 30/1 == 30/1","",
"END"};

const string expOutput_1over3_1over2[] = {
"a=1 b=3 c=1 d=2","",
"Testing: a/b + c/d = (a * d + b * c) / (b * d)",
"1/3 + 1/2 = (1 * 2 + 3 * 1)/(3 * 2) = 5/6","",
"Testing: a/b - c/d = (a * d - b * c) / (b * d)",
"1/3 - 1/2 = (1 * 2 - 3 * 1)/(3 * 2) = -1/6","",
"Testing: (a/b) * (c/d) = (a * c) / (b * d)",
"1/3 * 1/2 = (1 * 1) / (3 * 2) = 1/6","",
"Testing: (a/b) / (c/d) = (a * d) / (c * b)",
"1/3 / 1/2 = (1 * 2) / (1 * 3) = 2/3","",
"Testing: -(a/b) = (-a/b)",
"-(1/3) = (-1 / 3) = -1/3","",
"Testing: (a/b) < (c/d) means (a * d) < (c * b)",
"1/3 < 1/2 means (1 * 2) < (1 * 3) means 2/1 < 3/1","",
"Testing: (a/b) == (c/d) means (a * d) == (c * b)",
"1/3 == 1/2 means (1 * 2) == (1 * 3) means 2/1 == 3/1","",
"END"};


bool strDiff(string s1, string s2, int& firstMismatch) {
    bool matches  = true;
    int  len      = min(s1.length(), s2.length());
    
    for(int i=0; (matches==true) and (i<len) ; i++){ 
        if (s1[i] != s2[i]) {
            firstMismatch = i;
            matches       = false;
        }
    }
    
    // Check len if all matches so far
    if (matches) {
        if (s1.length() != s2.length()) {
            matches       = false;
            firstMismatch = len;
        }
    }
    
}

bool compareOutputs(
         stringstream& output, 
        const string   expOutput[], 
        bool           verbose=false) 
{
    string outputLine;
    int   lineNum     = 0;
    int   lastDisp    = 0;
    bool  mismatch    = false;
    bool  pastExpEnd  = false;
    int   errCount    = 0;
    int   errLocation = 0;
    while (getline( output, outputLine )) {
        if (verbose)
           cout << setw(3) << lineNum+1 << ": " << outputLine << endl;
        if ((!pastExpEnd) && (expOutput[lineNum].compare("END")) == 0) {
            cout << "*** Error: Output exceeds expected length = " << lineNum << endl;
            pastExpEnd = true;
            mismatch   = true;
        }
        
        if (!pastExpEnd)
           if (strDiff(outputLine, expOutput[lineNum], errLocation)) {
               if (!verbose)
                  cout << setw(3) << lineNum+1 << ": " << outputLine << endl;
               cout << "ERR: " << setw(errLocation+1) << "^" << endl;
               cout << "***: " << expOutput[lineNum]  << endl;
               errCount++;
           }
        
        lineNum++;
    }
    
    if (errCount > 0)
        cout << "**** Fail! " << errCount << " mismatch" << (errCount>1 ? "es" : "") << " detected!" << endl;
    else  if (expOutput[lineNum+1].compare("END") == 0) {
        if (verbose)
        cout << "Test Pass! Generated output matches expected output!" << endl;
    }
    else {
        int missing = 0;
        cout << "***  Fail! Generated output incomplete! ";
        while (expOutput[lineNum+missing+1].compare("END") != 0) {
            missing++;
            errCount++;
        }
        cout << missing << " lines missing!" << endl;
        for(int i=0; i<missing; i++)
            cout << "missed :" << expOutput[lineNum+i+1] << endl;
    }
        
    // Return true if no errors found
    return (errCount == 0);
}


bool TestOutput(Results& results) {
    bool passed;

    cout << "Testcase " << ++results.numTests << ": ";
    cout << "Testing Rational Class Constructors and << Operator" << endl;
    Rational r1(15,3), r2(10,2);
    Rational r3(1,3), r4(1,2);
    stringstream outStream;
    maintest_v3(outStream, r1, r2);
    passed = compareOutputs(outStream, expOutput_15over3_10over2, true);
  
    stringstream outStream2;
    maintest_v3(outStream2, r3, r4);
    passed &= compareOutputs(outStream2, expOutput_1over3_1over2);
    
    cout << endl << endl;
    cout << "Testcase  " << 1 << ": ";
    //cout << "Testing Rational Class Constructors and << Operator" << endl;
    cout << "Example Test Run       ";

    
     // return the results
    if (passed) 
        results.numPassed++;
    else
        results.numFailed++;

    return passed;
}


void displayTestCase(ostream& outStream, int a, int b, string op, bool verbose=false) 
{
    if (verbose) {
        outStream << "Testing: ";
        outStream << op << "(" << a << "/" << b << ")";
        outStream << endl;
    }
}


void displayTestCase(ostream& outStream, int a, int b, int c, int d, string op, bool verbose=false) 
{
    if (verbose) {
        outStream << "Testing: ";
        outStream << a << "/" << b;
        outStream << "  " << op << "  ";
        outStream << c << "/" << d;

        // Special case for < or ==
        if ((op == "<") || (op == "==")) {
            outStream << " is true if and only if this is also true: " << (a*d) << " " << op << " " << (c*b);
        }

        outStream << endl;
    }
}




void reportFailedTestCase(
        ostream& outStream, 
        Rational r1, 
        string   op, 
        int      expNum, 
        int      expDenom, 
        Rational result) 
{
    outStream << "*** Failed: " << op << "(" << r1 << ")" ;
    outStream << ", Expected:" << expNum << "/" << expDenom;
    outStream << ", Result=" << result;
    outStream << endl;
}



void reportFailedTestCase(
        ostream& outStream, 
        Rational r1, 
        Rational r2, 
        string   op, 
        bool     exp, 
        bool     result) 
{
    outStream << "*** Failed: " << r1 << "  " << op << "  " << r2;
    outStream << ", Expected: " << (exp ? "true" : "false");
    outStream << ", Result=" << (result ? "true" : "false");
    outStream << endl;
}



void reportFailedTestCase(
        ostream& outStream, 
        Rational r1, 
        Rational r2, 
        string   op, 
        int      expNum, 
        int      expDenom, 
        Rational result) 
{
    outStream << "*** Failed: " << r1 << "  " << op << "  " << r2;
    outStream << ", Expected:" << expNum << "/" << expDenom;
    outStream << ", Result=" << result;
    outStream << endl;
}


void reportFailedTestCase(
        ostream& outStream, 
        Rational r1, 
        Rational r2, 
        string   op, 
        Rational exp, 
        Rational result) 
{
 reportFailedTestCase(outStream, r1, r2, op, exp.getNumerator(), exp.getDenominator(), result);
}




bool TestPlus(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    cout << "Overloaded +  operator ";
    cout.flush();
    // cout << "Testing a/b + c/d = (a * d + b * c) / (b * d)" << endl;
    
    // Test Addition through a whole range of numerators and denominators
    for(int a= -100; a<100; a+=10)
        for(int b=1; b<100; b++)
            for(int c= -100; c<10; c+=10)
                for(int d=1; d<10; d++) {
                    // "Testing: a/b + c/d = (a * d + b * c) / (b * d)"
                    displayTestCase(cout, a,b,c,d, "+",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * d + b * c,b * d);
                    r3 = golden_reduce(r3);
                    
                    if (!(golden_equal(r1+r2,r3))) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"+",r3,r1+r2);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}



bool TestMinus(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: a/b - c/d = (a * d - b * c) / (b * d)" << endl;
    cout << "Overloaded -  operator ";
    
    // Test through a whole range of numerators and denominators
    for(int a= -100; a<100; a+=10)
        for(int b=1; b<100; b++)
            for(int c= -100; c<10; c+=10)
                for(int d=1; d<10; d++) {
                    // "Testing: a/b - c/d = (a * d - b * c) / (b * d)"
                    displayTestCase(cout, a,b,c,d, "-",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * d - b * c,b * d);
                    r3 = golden_reduce(r3);
                    
                    
                    if (!golden_equal(r1-r2,r3)) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"-",r3,r1-r2);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}


bool TestMult(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: (a/b) * (c/d) = (a * c) / (b * d)" << endl;
    cout << "Overloaded *  Operator ";
    
    // Test through a whole range of numerators and denominators
    for(int a= -100; a<100; a+=10)
        for(int b=1; b<100; b++)
            for(int c= -100; c<10; c+=10)
                for(int d=1; d<10; d++) {
                    // "Testing: (a/b) * (c/d) = (a * c) / (b * d)"
                    displayTestCase(cout, a,b,c,d, "*",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * c, b * d);
                    r3 = golden_reduce(r3);
                    
                    if (!golden_equal(r1*r2,r3)) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"*",r3,r1*r2);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}


bool TestDiv(Results& results, bool verbose=false) {
	int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: (a/b) / (c/d) = (a * d) / (c * b)" << endl;
    cout << "Overloaded /  Operator ";
    
    // Test through a whole range of numerators and denominators
    for(int a= -100; a<100; a+=10)
        for(int b=1; b<100; b++)
            for(int c= 1; c<20; c+=1)
                for(int d=1; d<10; d++) {
                    // Testing: (a/b) / (c/d) = (a * d) / (c * b)
                    displayTestCase(cout, a,b,c,d, "*",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * d, c * b);
                    r3 = golden_reduce(r3);

                    
                    if (!golden_equal(r1/r2,r3)) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"*",r3, r1/r2);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}


bool TestNeg(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: -(a/b) = (-a/b)" << endl;
    cout << "Overloaded -  negation ";
    
    // Test through a whole range of numerators and denominators
    for(int a= -100; a<100; a+=10)
        for(int b=1; b<100; b++) {
            // Testing: -(a/b) = (-a/b)
            displayTestCase(cout, a,b, "-",verbose);

            Rational r1( a, b);
            Rational r3(-a, b);
            r3 = golden_reduce(r3);
                    
            if (!golden_equal(-r1,r3)) {
                passed = false;
                numFailed++;
                if (numFailed < maxFails) 
                    reportFailedTestCase(cout,r1,"-",r3.getNumerator(),r3.getDenominator(),-r1);
                else
                    failed = true;
            }
        }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}



bool TestEqual(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: ((a/b) == (c/d)) == ((a * d) == (c * b))" << endl;
    cout << "Overloaded == operator ";
    
    // Test through a whole range of numerators and denominators
    for(int a= -10; a<10; a+=10)
        for(int b=1; b<10; b++)
            for(int c= -5; c<5; c++)
                for(int d=1; d<5; d++) {
                    // Testing: ((a/b) == (c/d)) == ((a * d) == (c * b))
                    displayTestCase(cout, a,b,c,d, "==",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    bool     exp = ((a*d) == (c*b));
                    
                    if (!((r1==r2) == exp)) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"==",exp,r1==r2);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}


bool TestMore(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: (a/b) < (c/d) means (a * d) < (c * b)" << endl;
    cout << "Overloaded >  operator ";
    
    Rational oneHalf(-4,-8);
    Rational negOne (-1);
            
    // Start off with a simple hard-coded test.
    // If this returns true, this is a big FAIL!
    failed = (negOne > oneHalf);        
    
    // Test through a whole range of numerators and denominators
    for(int a= -10; a<10; a+=10)
        for(int b=1; b<10; b++)
            for(int c= -5; c<5; c++)
                for(int d=1; d<5; d++) {
                    // Testing: (a/b) < (c/d) means (a * d) < (c * b)
                    displayTestCase(cout, a,b,c,d, ">",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * d, c * b);
                    
                    
                    if (!((r1>r2) == ((a*d)>(c*b)))) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,">",(a * d),(c * b),r3);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}



bool TestLess(Results& results, bool verbose=false) {
    int        numFailed = 0;
    const int  maxFails  = 10;    // Give up after this many fails
    bool       failed    = false; // Set true if fail count exceeds maximum
    bool       passed    = true;  // All must pass to return passed==true

    cout << "Testcase  " << ++results.numTests << ": ";
    // cout << "Testing: (a/b) < (c/d) means (a * d) < (c * b)" << endl;
    cout << "Overloaded <  operator ";
    
    Rational oneHalf(-4,-8);
    Rational negOne (-1);
            
    // Start off with a simple hard-coded test.
    // If this returns true, this is a big FAIL!
    failed = (oneHalf < negOne);        
    
    // Test through a whole range of numerators and denominators
    for(int a= -10; a<10; a+=10)
        for(int b=1; b<10; b++)
            for(int c= -5; c<5; c++)
                for(int d=1; d<5; d++) {
                    // Testing: (a/b) < (c/d) means (a * d) < (c * b)
                    displayTestCase(cout, a,b,c,d, "<",verbose);

                    Rational r1(a, b);
                    Rational r2(c, d);
                    Rational r3(a * d, c * b);
                    
                    
                    if (!((r1<r2) == ((a*d)<(c*b)))) {
                        passed = false;
                        numFailed++;
                        if (numFailed < maxFails) 
                            reportFailedTestCase(cout,r1,r2,"<",(a * d),(c * b),r3);
                        else
                            failed = true;
                    }
                }
    
    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}

bool testRational(Rational& r, int numerator, int denominator, Results& results, bool verbose=false) 
{
 bool passed = false;
 
 if ((r.getNumerator()==numerator) and (r.getDenominator()==denominator))
     passed = true;
 
 if (!passed)
     cout << "*** Fail *** ";
 if (verbose or !passed) {
     cout << "Testing if Rational " << r.getNumerator() << "/" << r.getDenominator();
     cout << " matches expected value: ";
     cout << numerator << "/" << denominator;
     cout << ", passed=" << (passed ? "true" : "false") << endl;
 }
 
 return passed;
}

bool TestExtraction(Results& results, bool verbose=false) {
    bool passed = true;  // All must pass to return passed==true

    cout << "Testcase " << ++results.numTests << ": ";
    cout << "Overloaded >> operator ";
    
    // Create an input buffer with the right tokens and see if we can exrtract them
        stringstream inputBuffer;
    inputBuffer << "1/2 10/2 2/10 10/1 -4/8 -1/1 1/2 3/2 ";
    
    Rational oneHalf, tenOver2, twoOver10, ten, minus4Over8, minus1, point5, onepoint5;        
    string s;
    
    inputBuffer >> oneHalf;
    if (!testRational(oneHalf, 1, 2, results, verbose))
        passed = false;  
    
    inputBuffer >> tenOver2;
    if (!testRational(tenOver2, 10, 2, results, verbose))
        passed = false;
    
    inputBuffer >> twoOver10;
    if (!testRational(twoOver10, 2 , 10, results, verbose))
        passed = false;

    inputBuffer >> ten;
    if (!testRational(ten, 10, 1, results, verbose))
        passed = false;

    inputBuffer >> minus4Over8;
    if (!testRational(minus4Over8, -4, 8, results, verbose))
        passed = false;
          
    inputBuffer >> minus1;
    if (!testRational(minus1, -1, 1, results, verbose))
        passed = false;

    inputBuffer >> point5;
    point5 = point5.add(0); // Force a reduce() call
    if (!testRational(point5, 1, 2, results, verbose))
        passed = false;

    inputBuffer >> onepoint5;
    onepoint5 = onepoint5.add(0); // Force a reduce() call
    if (!testRational(onepoint5, 3, 2, results, verbose))
        passed = false;

    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}

bool testToken(Rational r, string inpVal, string expVal, Results& results, bool verbose=false) 
{
 bool passed = false;
 
 if (inpVal == expVal)
     passed = true;
 
 if (!passed)
     cout << "*** Fail *** ";
 if (verbose or !passed) {
     cout << "Testing <<  " << r.getNumerator() << "/" << r.getDenominator();
     cout << " matches expected value: " << expVal << ", received= " << inpVal;
     cout << ", passed=" << (passed ? "true" : "false") << endl;
 }
 
 return passed;
}


bool TestInsertion(Results& results, bool verbose=false) {
    string token;
    bool passed = true;  // All must pass to return passed==true

    cout << "Testcase " << ++results.numTests << ": ";
    cout << "Overloaded << operator ";
    
    
    Rational oneHalf(1,2), tenOver2(10,2), twoOver10(2,10), ten(10);
    Rational minus4Over8(-4,8), minus1(1,-1), onepoint5(3,2); 
    
    // Create an input buffer with the right tokens and see if we can extract them
    stringstream outputBuffer;

    outputBuffer << oneHalf << " " << tenOver2 << " " << twoOver10;
    outputBuffer << " "  << ten << " " << minus4Over8;
    outputBuffer  << " " << minus1 << " " << onepoint5 << " " ;
    
    // Test if we get back what we put in
    outputBuffer >> token;
    if (!testToken(oneHalf, token, "1/2", results, verbose))
        passed = false;  
    
    outputBuffer >> token;
    if (!testToken(tenOver2, token, "10/2", results, verbose))
        passed = false;  
    
    outputBuffer >> token;
    if (!testToken(twoOver10, token, "2/10", results, verbose))
        passed = false;  

    outputBuffer >> token;
    if (!testToken(ten, token, "10/1", results, verbose))
        passed = false;  

    outputBuffer >> token;
    if (!testToken(minus4Over8, token, "-4/8", results, verbose))
        passed = false;  

    outputBuffer >> token;
    if (!testToken(minus1, token, "1/-1", results, verbose))
        passed = false;  
    
    outputBuffer >> token;
    if (!testToken(onepoint5, token, "3/2", results, verbose))
        passed = false;  


    if (passed)
        results.numPassed++;
    else
        results.numFailed++;
    
    return passed;
}




int main() {
    // To ensure backwards compatibility, this file contains all three versions
    #if (USE_MAIN_VERSION == 1)
       maintest_v1();
    #endif

    #if (USE_MAIN_VERSION == 2)
       maintest_v2();
    #endif

    #if (USE_MAIN_VERSION == 3)
    {
        Results results;
        bool    passed;
       
        passed = TestOutput(results);
        displayResult(cout, passed, results);

       
        passed = TestPlus(results);
        displayResult(cout, passed, results);
       
        passed = TestMinus(results);
        displayResult(cout, passed, results);

        passed = TestMult(results);
        displayResult(cout, passed, results);

        passed = TestDiv(results);
        displayResult(cout, passed, results);

        passed = TestNeg(results);
        displayResult(cout, passed, results);
        
        passed = TestEqual(results);
        displayResult(cout, passed, results);

        passed = TestMore(results);
        displayResult(cout, passed, results);

        passed = TestLess(results);
        displayResult(cout, passed, results);

        passed = TestExtraction(results);
        displayResult(cout, passed, results);
        
        passed = TestInsertion(results);
        displayResult(cout, passed, results);

        cout << "\nSummary: " ;
        displayResults(cout, results);

    }
    #endif
}