/* Basic test harness for the Variant class.  This does NOT test all cases,
 * but should be a good starting point for the rest of your code.
 * This test assumes that there is a file called variant.h containing the
 * Variant class definition; the implementation should be in its own .cpp
 * file (probably variant.cpp)
 *
 * Good luck, and happy testing!
 */

#include <iostream>
#include <string>
#include <set>
#include <sstream>
#include <iomanip>
#include <vector>
#include <ctime>     // For time
#include <cstdlib>   // For rand, srand
#include <algorithm> // For sort, next_permutation, replace
#include "variant.h"
#include "variant.h" // Check for multiple-inclusion problems; see Handout #7
using namespace std;

/* Helper function that checks that a given condition is true, failing
 * if unable to do so.
 */
const int WRAP_COUNTER = 23;
void DoCheckCondition(const bool shouldBeTrue, const string& message, const int lineNumber)
{
	static int numLines = 0;
	if(!shouldBeTrue)
	{
		cout << "FAILED: " << message << endl;
		cout << "        (Test failed at line #" << lineNumber << ')' << endl;
	}
	else
		cout << "PASS:   " << message << endl;

	if(++numLines % WRAP_COUNTER == 0)
	{
		cout << "Press any key to continue..." << endl;
		string line;
		getline(cin, line);
	}
}

/* Macro that invokes DoCheckCondition with the current line number. */
#define CheckCondition(cond, msg) DoCheckCondition((cond), (msg), __LINE__)

/* Checks the functionality exported by Task 0. */
void BasicFunctionalityTest()
{
	/* Default ctor testing. */
	{
		// New scope keeps all of these variables local to this block.
		Variant one;
		CheckCondition(one.getType() == Variant::IntType, "Default ctor should set Variant to hold an integer.");
		CheckCondition(one.asInt() == 0, "Default ctor should set Variant to hold the value 0.");
	}

	/* Integer ctor testing. */
	{
		Variant two = 137;
		CheckCondition(two.getType() == Variant::IntType, "Integer ctor should set Variant to hold an integer.");
		CheckCondition(two.asInt() == 137, "Storing 137 should return value 137.");
	}

	/* C string ctor testing. */
	{
		Variant three = "Check me!";
		CheckCondition(three.getType() == Variant::StringType, "C string ctor should set Variant to hold a string.");
		CheckCondition(three.asString() == "Check me!", "Stored \"Check me!\", should get back \"Check me!\"");
	}

	/* C++ string ctor testing. */
	{
		Variant four = string("Check me!");
		CheckCondition(four.getType() == Variant::StringType, "C++ string ctor should set Variant to hold a string.");
		CheckCondition(four.asString() == "Check me!", "Stored \"Check me!\", should get back \"Check me!\"");
	}
}

/* Checking the copying behavior (Task 1) */
void CopyingBehaviorTest()
{
	/* Checking copy constructor. */
	{
		Variant one = 137, two = "Check me!";
		Variant three = one;
		Variant four = two;

		CheckCondition(three.getType() == Variant::IntType, "Copying an integer should make copy have integer type.");
		CheckCondition(three.asInt() == 137, "Copying an integer should copy the correct value.");
		CheckCondition(one.getType() == Variant::IntType, "Copying an integer should not change type of original.");
		CheckCondition(one.asInt() == 137, "Copying an integer should not change the original value.");

		CheckCondition(four.getType() == Variant::StringType, "Copying a string should make copy have string type.");
		CheckCondition(four.asString() == "Check me!", "Copying an string should copy the correct value.");
		CheckCondition(two.getType() == Variant::StringType, "Copying an string should not change type of original.");
		CheckCondition(two.asString() == "Check me!", "Copying an string should not change the original value.");
	}

	/* Checking assignment operator. */
	{
		Variant one = 137, two = "Check me!", three, four;
		three = one;
		four = two;

		CheckCondition(three.getType() == Variant::IntType, "Copying an integer with op= should make copy have integer type.");
		CheckCondition(three.asInt() == 137, "Copying an integer with op= should copy the correct value.");
		CheckCondition(one.getType() == Variant::IntType, "Copying an integer with op= should not change type of original.");
		CheckCondition(one.asInt() == 137, "Copying an integer with op= should not change the original value.");

		CheckCondition(four.getType() == Variant::StringType, "Copying a string with op= should make copy have string type.");
		CheckCondition(four.asString() == "Check me!", "Copying an string with op= should copy the correct value.");
		CheckCondition(two.getType() == Variant::StringType, "Copying an string with op= should not change type of original.");
		CheckCondition(two.asString() == "Check me!", "Copying an string with op= should not change the original value.");
	}

	/* Checking self-assignment. */
	{
		Variant one = 137;
		one = one = one = one;
		CheckCondition(one.getType() == Variant::IntType, "Self-assigning an integer should not change its type.");
		CheckCondition(one.asInt() == 137, "Self-assigning an integer should not change its value.");
	}
	
	{
		Variant one = "Check me!";
		one = one = one = one;
		CheckCondition(one.getType() == Variant::StringType, "Self-assigning a string should not change its type.");
		CheckCondition(one.asString() == "Check me!", "Self-assigning a string should not change its value.");
	}
}

/* Checking the stream insertion operator (Task 2).
 *
 * If you see random output printed to the screen in addition to
 * failing tests, check to see that your stream insertion operator
 * is printing to the parameter of the operator << function rather
 * than directly to cout.
 */
void StreamInsertionTest()
{
	/* See if integers print correctly. */
	{
		ostringstream output;
		output << Variant(137);
		CheckCondition(output.str() == "137", "Writing a variant of value 137 into a stream should yield string 137.");
	}

	/* See if strings print correctly. */
	{
		ostringstream output;
		output << Variant("Hello, world!");
		CheckCondition(output.str() == "Hello, world!", "Writing a string variant should preserve the string.");
	}

	/* Check if stream manipulators still work.  If this test begins failing,
	 * don't worry about it too much, but it might indicate that your
	 * insertion operator is too complicated.
	 */
	{
		ostringstream output;
		output << oct << Variant(137);
		CheckCondition(output.str() == "211", "Stream insertion should respect oct.");
	}

	/* Same, except for more complicated manipulators. */
	{
		ostringstream output;
		output << setw(5) << setfill('A') << Variant(137);
		CheckCondition(output.str() == "AA137", "Stream insertion should respect fill and width.");
	}
}

/* Helper function which generates a random variant. */
Variant GetRandomVariant()
{
	/* 50% chance to return an int. */
	if(rand() % 2 == 0)
		return rand();

	/* Otherwise, generate a random string of alphanumeric characters. */
	const int MAX_STRING_LENGTH = 10;
	string result(rand() % MAX_STRING_LENGTH, ' ');

	/* Fill with random values. */
	for(string::iterator itr = result.begin(); itr != result.end(); ++itr)
		*itr = (rand() % ('z' - 'A')) + 'A';

	/* To make debugging easier, replace the [ and ] characters. */
	replace(result.begin(), result.end(), ']', '*');
	replace(result.begin(), result.end(), '[', '^');

	return result;
}

/* Helper function which checks if two Variants are comparable. */
void CheckComparability(const Variant& one, const Variant& two)
{
	Variant::Comparator comp;
	if(!comp(one, two) && !comp(two, one) &&
		((one.getType() == Variant::IntType && two.getType() == Variant::IntType && one.asInt() != two.asInt()) ||
		 (one.getType() == Variant::StringType && two.getType() == Variant::StringType && one.asString() != two.asString()) ||
		 (one.getType() != two.getType())))
	{
		ostringstream reporter;
		reporter << "Cannot compare [" << one << "] and [" << two << ']';
		CheckCondition(false, reporter.str());
	}
}

/* Helper function which checks that two Variants are asymmetric in the
 * compare function (i.e. a < b => !(b < a))
 */
void CheckAsymmetry(const Variant& one, const Variant& two)
{
	Variant::Comparator comp;
	if(comp(one, two) && comp(two, one))
	{
		ostringstream reporter;
		reporter << "[" << one << "] and [" << two << "] both compare less than each other";
		CheckCondition(false, reporter.str());
	}
}

/* Helper function which checks that the comparator is transitive
 * (i.e. if a < b and b < c, then a < c.
 */
void CheckTransitivity(const Variant& one, const Variant& two, const Variant& three)
{
	/* This code uses the next_permutation algorithm to check all permutations of the
	 * elements and see if transitivity fails in any of them.  Putting the next_permutation
	 * algorithm into a loop will exhaustively list all permutations of the input data.
	 * Pretty snazzy, isn't it?
	 */
	vector<const Variant*> elems;
	elems.push_back(&one);
	elems.push_back(&two);
	elems.push_back(&three);

	/* We need to sort these elements into ascending order by memory address so that
	 * next_permutation works correctly.
	 */
	sort(elems.begin(), elems.end());

	Variant::Comparator comp;
	do
	{
		if(comp(*elems[0], *elems[1]) && comp(*elems[1], *elems[2]) && !comp(*elems[0], *elems[2]))
		{
			ostringstream reporter;
			reporter << "[" << *elems[0] << "] < [" << *elems[1] << "] and "
				     << "[" << *elems[1] << "] < [" << *elems[2] << "], but !("
					 << "[" << *elems[0] << "] < "
					 << "[" << *elems[2] << "])";
			CheckCondition(false, reporter.str());
		}
	}
	while(next_permutation(elems.begin(), elems.end()));
}

/* Checking the comparator (Task 3) */
void ComparatorTest()
{
	/* Generate multiple random values and ensure that they're comparable. */
	const int NUM_TRIALS = 1000;
	for(int i = 0; i < NUM_TRIALS; ++i)
	{
		Variant one = GetRandomVariant(),
			    two = GetRandomVariant(), 
				three = GetRandomVariant();

		/* Check that all three are comparable somehow. */
		CheckComparability(one, two);
		CheckComparability(two, three);
		CheckComparability(one, three);

		/* Check for asymmetry. */
		CheckAsymmetry(one, two);
		CheckAsymmetry(two, three);
		CheckAsymmetry(one, three);

		/* Check for transitivity. */
		CheckTransitivity(one, two, three);
	}

	/* Testing the STL set.  This may generate some nasty compiler errors
	 * if your implementation is buggy.  Also note that if your implementation
	 * of the comparator is buggy that this may result in runtime errors.
	 */
	{
		typedef set<Variant, Variant::Comparator> VariantSet;
		VariantSet mySet;
		mySet.insert(42);
		mySet.insert(137);
		mySet.insert("Hello, world!");
		mySet.insert("Hello, world!");
		mySet.insert("Check me!");

		CheckCondition(mySet.size() == 4, "There should be four elements in the set.");
		CheckCondition(mySet.find(42) != mySet.end(), "The set should contain 42.");
		CheckCondition(mySet.find(137) != mySet.end(), "The set should contain 137");
		CheckCondition(mySet.find("Hello, world!") != mySet.end(), "The set should contain \"Hello, world!\"");
		CheckCondition(mySet.find("Check me!") != mySet.end(), "The set should contain \"Check me!\"");
		CheckCondition(mySet.find(0) == mySet.end(), "The set should not contain 0.");

		CheckCondition(mySet.erase(2718) == 0, "Removing 2718 should fail.");
		CheckCondition(mySet.erase(42) == 1, "Removing 42 should be successful.");
		CheckCondition(mySet.size() == 3, "There should be three elements in the set.");
	}
}

/* Seed the randomizer with the current time. */
void Randomize()
{
	srand(static_cast<unsigned>(time(NULL)));
}

int main()
{
	Randomize();
	BasicFunctionalityTest();
	CopyingBehaviorTest();
	StreamInsertionTest();
	ComparatorTest();
	return 0;
}
