#ifndef ARRAY_TESTER_H
#define ARRAY_TESTER_H

#ifndef nullptr
#define nullptr 0
#endif

#include "Tester.h"
#include "Array.h"
#include "ece250.h"

#include <iostream>
#include <cmath>

template <typename Type>
class Array_tester:public Tester< Array<Type> > {
	using Tester< Array<Type> >::object;
	using Tester< Array<Type> >::command;

	public:
		Array_tester( Array<Type> *obj = 0 ):Tester< Array<Type> >( obj ) {

			// empty
		}

		void process();
};

/****************************************************
 * void process()
 *
 * Process the current command.  For Array, these include:
 *
 *   new             new Array()     create a new array with default capacity
 *   new: n          new Array( n )  create a new array with capacity n
 *   size n          size            the size equals n
 *   capacity n      capacity        the capacity equals n
 *   empty b         empty           empty() returns the Boolean value b
 *   full b          full            full() returns the Boolean value b
 *
 *   sum n           sum             the sum of the entries is n
 *   min n           min             the minimum entry is n
 *   min!            min             an underflow exception is thrown
 *   max n           max             the maximum entry is n
 *   max!            max             an underflow exception is thrown
 *   average d       average         the average of the entries is d
 *   average!        average         an underflow exception is thrown
 *   variance d      variance        the variance of the entries is d
 *   variance!       variance        an underflow exception is thrown
 *   std_dev d       std_dev         the standard deviation of the entries is d
 *   std_dev!        std_dev         an underflow exception is thrown
 *   at i m          operator[]      object[i] returns m
 *   at! i           operator[]      object[i] throws an out_of_range
 *
 *   append n b      append          attempting to append n returns the Boolean value b
 *   clear           clear           empties the array--always succeeds as a test
 *
 *  Others
 *   cout            cout << Array   print the Array (for testing only)
 *   assign          operator =      assign this Array to a new Array object
 *   summary                         prints the amount of memory allocated
 *                                   minus the memory deallocated
 *   details                         prints a detailed description of which
 *                                   memory was allocated with details
 *   !!                              use the previous command, e.g.  5 append 3
 *                                                                   6 !! 7         // same as append 7
 *   !n                              use the command used in line n  7 append 7
 *                                                                   8 !7 9         // same as append 9
 *
 ****************************************************/

template <typename Type>
void Array_tester<Type>::process() {
	if ( command == "new" ) {
		object = new Array<Type>();
		std::cout << "object = new Array<Type>();  // ";
		std::cout << "Okay" << std::endl;
	} else if ( command == "new:" ) {
		int n;
		std::cin >> n;
		std::cout << "object = new Array<Type>( " << n << " );  // ";
		object = new Array<Type>( n );
		std::cout << "Okay" << std::endl;
	} else if ( command == "size" ) {
		int expected_size;
		std::cin >> expected_size;

		std::cout << "object->size() == " << expected_size << ";  // ";

		int actual_size = object->size() ;

		if ( actual_size == expected_size ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in size(): expecting the value '" << expected_size
			          << "' but got '" << actual_size << "'" << std::endl;
		}
	} else if ( command == "capacity" ) {
		int expected_capacity;
		std::cin >> expected_capacity;

		std::cout << "object->capacity() == " << expected_capacity << ";  // ";

		int actual_capacity = object->capacity() ;

		if ( actual_capacity == expected_capacity ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in capacity(): expecting the value '" << expected_capacity
			          << "' but got '" << actual_capacity << "'" << std::endl;
		}
	} else if ( command == "empty" ) {
		bool expected_empty;
		std::cin >> expected_empty;

		if ( expected_empty ) {
			std::cout << "object->empty() == true;  // ";
		} else {
			std::cout << "object->empty() == false;  // ";
		}

		bool actual_empty = object->empty() ;

		if ( actual_empty == expected_empty ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in empty(): expecting the value '" << expected_empty
			          << "' but got '" << actual_empty << "'" << std::endl;
		}
	} else if ( command == "full" ) {
		bool expected_full;
		std::cin >> expected_full;

		if ( expected_full ) {
			std::cout << "object->full() == true;  // ";
		} else {
			std::cout << "object->full() == false;  // ";
		}

		bool actual_full = object->full() ;

		if ( actual_full == expected_full ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in full(): expecting the value '" << expected_full
			          << "' but got '" << actual_full << "'" << std::endl;
		}
	} else if ( command == "sum" ) {
		Type expected_sum;
		std::cin >> expected_sum;

		std::cout << "object->sum() == " << expected_sum << ";  // ";

		Type actual_sum = object->sum() ;

		if ( actual_sum == expected_sum ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in sum(): expecting the value '" << expected_sum
			          << "' but got '" << actual_sum << "'" << std::endl;
		}
	} else if ( command == "average" ) {
		double expected_average;
		std::cin >> expected_average;

		std::cout << "object->average() == " << expected_average << ";  // ";

		double actual_average = object->average() ;

		if ( std::fabs( (actual_average - expected_average)/expected_average ) < 1e-5 ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in average(): expecting the value '" << expected_average
			          << "' but got '" << actual_average << "'" << std::endl;
		}
	} else if ( command == "average!" ) {
		std::cout << "try {" << std::endl
		          << "    object->average();" << std::endl
		          << "} catch ( underflow ) {" << std::endl
		          << "    // expecting this error" << std::endl
		          << "} // ";

		try {
			object->average();
			std::cout << "Failure in average(): expecting to catch an exception but nothing was raised."
			          << std::endl;
		} catch( underflow ) {
			std::cout << "Okay" << std::endl;
		} catch (...) {
			std::cout << "Failure in average(): expecting an underflow exception but caught a different exception" << std::endl;
		}
	} else if ( command == "variance" ) {
		double expected_variance;
		std::cin >> expected_variance;

		std::cout << "object->variance() == " << expected_variance << ";  // ";

		double actual_variance = object->variance() ;

		if ( std::fabs( (actual_variance - expected_variance)/expected_variance ) < 1e-5 ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in variance(): expecting the value '" << expected_variance
			          << "' but got '" << actual_variance << "'" << std::endl;
		}
	} else if ( command == "variance!" ) {
		std::cout << "try {" << std::endl
		          << "    object->variance();" << std::endl
		          << "} catch ( underflow ) {" << std::endl
		          << "    // expecting this error" << std::endl
		          << "} // ";

		try {
			object->variance();
			std::cout << "Failure in variance(): expecting to catch an exception but nothing was raised."
			          << std::endl;
		} catch( underflow ) {
			std::cout << "Okay" << std::endl;
		} catch (...) {
			std::cout << "Failure in variance(): expecting an underflow exception but caught a different exception" << std::endl;
		}
	} else if ( command == "std_dev" ) {
		double expected_std_dev;
		std::cin >> expected_std_dev;

		std::cout << "object->std_dev() == " << expected_std_dev << ";  // ";

		double actual_std_dev = object->std_dev() ;

		if ( std::fabs( (actual_std_dev - expected_std_dev)/expected_std_dev ) < 1e-5 ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in std_dev(): expecting the value '" << expected_std_dev
			          << "' but got '" << actual_std_dev << "'" << std::endl;
		}
	} else if ( command == "std_dev!" ) {
		std::cout << "try {" << std::endl
		          << "    object->std_dev();" << std::endl
		          << "} catch ( underflow ) {" << std::endl
		          << "    // expecting this error" << std::endl
		          << "} // ";

		try {
			object->std_dev();
			std::cout << "Failure in std_dev(): expecting to catch an exception but nothing was raised."
			          << std::endl;
		} catch( underflow ) {
			std::cout << "Okay" << std::endl;
		} catch (...) {
			std::cout << "Failure in std_dev(): expecting an underflow exception but caught a different exception" << std::endl;
		}
	} else if ( command == "at" ) {
		int index;
		std::cin >> index;

		Type expected_value;
		std::cin >> expected_value;

		std::cout << "(*object)[" << index << "] == " << expected_value << ";  // ";

		Type actual_value = object->operator[]( index ) ;

		if ( actual_value == expected_value ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in instance[" << index << "]: expecting the value '" << expected_value
			          << "' but got '" << actual_value << "'" << std::endl;
		}
	} else if ( command == "at!" ) {
		int index;
		std::cin >> index;

		std::cout << "try {" << std::endl
		          << "    (*object)[" << index << "];" << std::endl
		          << "} catch ( out_of_range ) {" << std::endl
		          << "    // expecting this error" << std::endl
		          << "} // ";


		try {
			object->operator[]( index );
			std::cout << "Failure in instance[" << index << "]: expecting to catch an exception but nothing was raised."
			          << std::endl;
		} catch( out_of_range ) {
			std::cout << "Okay" << std::endl;
		} catch (...) {
			std::cout << "Failure in instance[" << index << "]: expecting an out_of_range exception but caught a different exception" << std::endl;
		}
	} else if ( command == "append" ) {
		Type n;
		bool expected_append;

		std::cin >> n;
		std::cin >> expected_append;

		if ( expected_append ) {
			std::cout << "object->append( " << n << " ) == true;  // ";
		} else {
			std::cout << "object->append( " << n << " ) == false;  // ";
		}

		bool actual_append = object->append( n );

		if ( actual_append == expected_append ) {
			std::cout << "Okay" << std::endl;
		} else {
			std::cout << "Failure in append(" << n << "): expecting the value '" << expected_append
			          << "' but got '" << actual_append << "'" << std::endl;
		}
	} else if ( command == "clear" ) {
		std::cout << "object->clear();  // ";
		object->clear();

		std::cout << "Okay" << std::endl;
	} else if ( command == "cout" ) {
		std::cout << "cout << *object << std::endl;  // ";
		std::cout << *object << std::endl;
	}  else if ( command == "assign" ) {
		Array<Type> *new_object = new Array<Type>();

		std::cout << "stack.push( object );" << std::endl
		          << "object = new Array<Type>();" << std::endl
		          << "*object = *( stack.top() );  // ";

		*new_object = *object;
		
		std::cout << "Okay" << std::endl;

		Array_tester tester( new_object );

		tester.run();

		std::cout << "object = stack.pop();" << std::endl;
	}  else {
		std::cout << command << ": Command not found." << std::endl;
	}
}

#endif