
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <cassert>
using namespace std;

#include "cs2multiset.h"


int
main( int argc, char* argv[] )
{
	if ( argc != 2 )
	{
		std::cerr << "Usage: " << argv[0] << " test-results.\n";
		return 0;
	}
	std::ofstream ostr( argv[1] );

	if ( !ostr )
	{
		std::cerr << "Failed to open " << argv[1] << "\n";
		return 0;
	}


	int test_count = 0;
	cs2multiset<std::string> set1;
	cs2multiset<std::string>::iterator p;

	//******************************//
	ostr << "Testing insert\n";

	++test_count;
	std::string to_insert = std::string("hello");
	p = set1.insert( to_insert );
	bool success =  *p == to_insert;
	ostr << test_count << ":  insert \"" << to_insert << "\":  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_insert = std::string("good-bye");
	p = set1.insert( to_insert );
	success =  *p == to_insert;
	ostr << test_count << ":  insert \"" << to_insert << "\":  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_insert = std::string("good-bye");
	p = set1.insert( to_insert );
	success =  *p == to_insert;
	ostr << test_count << ":  insert \"" << to_insert << "\":  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_insert = std::string("dog");
	p = set1.insert( to_insert );
	success =  *p == to_insert;
	ostr << test_count << ":  insert \"" << to_insert << "\":  "
		<< (success ? "succeeded" : "failed") << endl;

	to_insert = std::string("friend");  p = set1.insert( to_insert );
	to_insert = std::string("puppy");   p = set1.insert( to_insert );
	to_insert = std::string("dog");     p = set1.insert( to_insert );

	++test_count;
	to_insert = std::string("good-bye");
	p = set1.insert( to_insert );
	success =  *p == to_insert;
	ostr << test_count << ":  insert \"" << to_insert << "\":  "
		<< (success ? "succeeded" : "failed") << endl;

	to_insert = std::string("zebra");   p = set1.insert( to_insert );

	// ******************************//
	ostr << "\n" << "Testing iterators\n";
	vector<string> inserted;
	inserted.push_back( "dog" );
	inserted.push_back( "dog" ); 
	inserted.push_back( "friend" );
	inserted.push_back( "good-bye" );  
	inserted.push_back( "good-bye" );
	inserted.push_back( "good-bye" );
	inserted.push_back( "hello" );
	inserted.push_back( "puppy" );
	inserted.push_back( "zebra" );

	vector<string>::iterator vi;
	cs2multiset<string>::iterator mi;
	int different = 0;
	for ( vi = inserted.begin(), mi = set1.begin(); vi!=inserted.end(), mi!=set1.end(); ++mi, ++vi )
	{
		if ( *vi != *mi )
		{
			++different;
			ostr << "disagreement at " << *mi << ", should be " << *vi << '\n';
		}
	}
	success = ( different == 0 && mi == set1.end() && vi == inserted.end() );
	test_count ++;
	ostr << test_count << ": iterating through set1 " << (success ? "succeeded" : "failed") << endl;

	++test_count;
	string to_find("foo");
	p = set1.find( to_find );
	success = ( p == set1.end() );
	ostr << test_count << ":  find \"" << to_find << "\" (not in set):  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_find = "zylophone";
	p = set1.find( to_find );
	success = ( p == set1.end() );
	ostr << test_count << ":  find \"" << to_find << "\" (not in set):  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_find = "dog";
	p = set1.find(to_find);
	success = ( p != set1.end() && (*p == to_find) );
	ostr << test_count << ":  find \"" << to_find << "\" (in set):  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_find = "good-bye";
	p = set1.find(to_find);
	success = ( p != set1.end() && (*p == to_find) );
	ostr << test_count << ":  find \"" << to_find << "\" (in set):  "
		<< (success ? "succeeded" : "failed") << endl;

	++test_count;
	to_find = "zebra";
	p = set1.find(to_find);
	success = ( p != set1.end() && (*p == to_find) );
	ostr << test_count << ":  find \"" << to_find << "\" (in set):  "
		<< (success ? "succeeded" : "failed") << endl;


	ostr << "\nHere is the tree, printed sideways.\n";
	set1.print_as_sideways_tree( ostr );


	//******************************//
	ostr << "\nTesting erase\n";
	int erase_count = set1.erase( "good-bye" );
	success = (erase_count == 3);
	++test_count;
	ostr << test_count << ":  erase \"good-bye\".  number erased = " << erase_count
		<< ".  " << (success ? "succeeded" : "failed") << endl;

	success = ( set1.size() == 6 );
	++test_count;
	ostr << test_count << ":  after erase the tree size should be 6.  It is " << set1.size()
		<< ".  " << (success ? "succeeded" : "failed") << endl;

	ostr << "\nHere is the tree after the erase, printed sideways.\n";
	set1.print_as_sideways_tree( ostr );


	//*****************************//
	set1.insert( "friend");
	set1.insert( "friend" );
	ostr << "\nHere is the tree after two more inserts of \"friend\":\n";
	set1.print_as_sideways_tree( ostr );

	p = set1.lower_bound( "friend" );
	//If it worked, then the current location and then next two should be "friend"
	success = (p != set1.end() && *p == "friend");
	++p;  success == success && (p != set1.end() && *p == "friend");
	++p;  success == success && (p != set1.end() && *p == "friend");
	++ test_count;
	ostr << test_count << ":  lower_bound found the first \"friend\":  " 
		<< (success ? "succeeded" : "failed") << endl;

	p = set1.lower_bound( "peach" );
	success = (p != set1.end() && *p == "puppy" );
	++test_count;
	ostr << test_count << ":  lower_bound on \"peach\" is \"puppy\":  found " << *p << ".  "
		<< (success ? "succeeded" : "failed") << endl;

	p = set1.upper_bound( "happy" );
	success = (p != set1.end() && *p == "hello" );
	++test_count;
	ostr << test_count << ":  upper_bound on \"happy\" is \"hello\":  found " << *p << ".  "
		<< (success ? "succeeded" : "failed") << endl;

	p = set1.upper_bound( "dog" );
	success = (p != set1.end() && *p == "friend" );
	++test_count;
	ostr << test_count << ":  upper_bound on \"dog\" is \"friend\":  found " << *p << ".  "
		<< (success ? "succeeded" : "failed") << endl;

	p = set1.upper_bound( "zebra" );
	success = (p == set1.end());
	++test_count;
	ostr << test_count << ":  upper_bound on \"zebra\" is the end iterator:  "
		<< (success ? "succeeded" : "failed") << endl;


	/* extended test cases */
	ostr << "Extended test case " << endl;

	cs2multiset<int> IntSet1;
	cs2multiset<int> IntSet2;
	cs2multiset<int>::iterator IntSetIter;
	cs2multiset<int>::iterator IntSetIter0;

	for (int i=0; i<10; i++) {
		IntSet1.insert(0);
	}
	for (int i=0; i<5; i++) {
		IntSet1.insert(-1);
	}
	IntSet1.print_as_sideways_tree( ostr );

	for (IntSetIter = IntSet1.begin(); IntSetIter != IntSet1.end(); IntSetIter++) {
		IntSet2.insert(*IntSetIter);
	}
	IntSet2.print_as_sideways_tree( ostr );

	IntSetIter = IntSet1.begin();
	IntSetIter0 = IntSet2.begin();
	for ( ; IntSetIter != IntSet1.end(), IntSetIter0 != IntSet2.end(); IntSetIter++, IntSetIter0++) {
		if (*IntSetIter != *IntSetIter0) {
			++different;
		}
	}
	++test_count;
	success = (different == 0);
	ostr << test_count << ":  despite layout, iterations should match "<< (success ? "succeeded" : "failed") << endl;


	IntSet2.erase(0);
	while ((IntSetIter = IntSet1.find(0)) != IntSet1.end()) {
		IntSet1.erase(IntSetIter);
	}
	IntSetIter = IntSet1.begin();
	IntSetIter0 = IntSet2.begin();
	for ( ; IntSetIter != IntSet1.end(), IntSetIter0 != IntSet2.end(); IntSetIter++, IntSetIter0++) {
		if (*IntSetIter != *IntSetIter0) {
			++different;
		}
	}
	++test_count;
	success = ((IntSet1.size() == IntSet2.size())&&(different == 0));
	ostr << test_count << ": All zeros should be deleted, from the duplicate sets. Will be the same "<< (success ? "succeeded" : "failed") << endl;

	IntSet1.erase(-1);

	++test_count;
	success = (IntSet1.size() == 0);
	ostr << test_count << ": The set should be empty, all 0s and -1s are removed "<< (success ? "succeeded" : "failed") << endl;

	/** Please be aware that the VS debug window may cause locking issues. **/
	++test_count;
	vector< int > VecCheck;
	cs2multiset< int > IntSetA;
	/** - constructors - **/
	for (int i=0; i<40; i += 2) {
		IntSetA.insert(i*i);
	}

	for (int i=5; i<20; i += 2) {
		IntSetA.insert(i*i);
	}

	for (int i=50; i>35; i -= 3) {
		IntSetA.insert(i*i);
	}

	cs2multiset<int>::iterator iter0 = IntSetA.begin();
        
        for (;iter0 != IntSetA.end(); iter0++) {
          VecCheck.push_back(*iter0);
        }

	cs2multiset<int> IntSetB  = IntSetA;
	cs2multiset<int> IntSetC(IntSetA);
	cs2multiset<int> IntSetD(VecCheck);

	ostr << test_count << ":  Contents of original multi-set" << endl;
	IntSetA.print_as_sideways_tree( ostr );
	ostr << test_count << ":  Contents of multi-set created with copy constructor" << endl;
	IntSetB.print_as_sideways_tree( ostr );
	ostr << test_count << ":  Contents of multi-set created with copy constructor" << endl;
	IntSetC.print_as_sideways_tree( ostr );
	ostr << test_count << ":  Contents of multi-set created with vector constructor" << endl;
	IntSetD.print_as_sideways_tree( ostr );

	/* please be aware that the multi-sets should be independent */

        iter0 = IntSetA.begin();
	cs2multiset<int>::iterator iter1 = IntSetB.begin();
	cs2multiset<int>::iterator iter2 = IntSetC.begin();
	cs2multiset<int>::iterator iter3 = IntSetD.begin();

	different = 0;
	for (;iter0 != IntSetA.end(),iter1 != IntSetB.end(),iter2 != IntSetC.end(),iter3 != IntSetD.end(); ++iter0, ++iter1, ++iter2,++iter3) {
		if ((*iter0 == *iter1) && (*iter2 == *iter1) && (*iter3 == *iter1) )
		{
		} else {
			++different;
		}
	}

	
	success = ((IntSetA.size() == IntSetB.size()) && (IntSetC.size() == IntSetB.size()) && (different == 0));
	ostr << test_count << ":  The size and contents of the sets are the same. "<< (success ? "succeeded" : "failed") << endl;

	ostr.close();
	return 0;
}

