#ifndef UTESTER_HPP
#define UTESTER_HPP
// [--License--]

#include <iostream>
#include <list>
#include <string>
#include <cstdio>


#define TestClass(X)\
int main(void){\
	X Controller;\
        return Controller.Run();\
}\


/**
* @author Berenger Bramas (berenger.bramas@inria.fr)
* Please read the license
*
* This class is used to make simple unit test cases
*
* Please refer to testUTest.cpp to see an example
* @warning Create a derived class that implement SetTests() and use TestClass() macro
*/
template <class TestClass>
class FUTester{
	// Test function pointer
	typedef void (TestClass::*TestFunc)(void);
	
	/** Test descriptor */
	struct TestFuncDescriptor{
		TestFunc func;		//< Test adress
		std::string name;	//< Test name
	};
	

	std::list<TestFuncDescriptor> tests;	//< all tests
	
	int totalTests;				//< number of tests
	
	int currentTest;			//< current processing test in the run
	int currentStep;			//< current processing step in the run

	int failledSteps;			//< number of failled step in the current test
	int failledTests;			//< number of failled tests
	
protected:
	/** Constructor */
	FUTester(){
		totalTests = 0;
	}

	/** Callback before processing test */
	virtual void Before(){}
	
	/** Callback after processing test */
	virtual void After(){}
	
	/** Callback before each unit test */
	virtual void PreTest(){}
	
	/** Callback after each unit test */
	virtual void PostTest(){}
	
	/**
	* This function has to add tests
        * <code> AddTest(&MyTest::TestOne); </code>
	*/
        virtual void SetTests() = 0;

	/**
	* Add a test without giving a name
	* @param inFunc test function address
	*/
        void AddTest(TestFunc inFunc){
		char buff[256];
		sprintf(buff,"Unamed Test number %d",totalTests+1);
		AddTest(inFunc,buff);
	}
	
	/**
	* Add a test with a name
	* @param inFunc test function address
	* @param inFuncName function name
	*/
        void AddTest(TestFunc inFunc, const std::string& inFuncName){
		++totalTests;
		TestFuncDescriptor desc;
		desc.func = inFunc;
                desc.name = inFuncName;
		tests.push_back(desc);
	}
	
	/**
	* To print a message manually in the test
	* @param value a object that ostream can work on
	*/
        template <class Output>
	void Print(const Output& value){
		std::cout<< "--- Output from program : " << value << "\n";
	}
	
	/**
	* To test
	* @param result the test result
	* if result is false test failled
	*/
        void assert(const bool result){
		++currentStep;
		if(!result){
			std::cout << ">> Step " << currentStep << " Failled\n";
			++failledSteps;
		}
	}
	
	/**
	* To test equality
	* @param v1 value one
	* @param v2 value 2
	* if v1 is not equal v2 test failled
	*/
	template <class T>
        void equal(const T& v1, const T& v2){
                assert(v1 == v2);
	}
	
	/**
	* To test equality
	* @param v1 value one
	* @param v2 value 2
	* if v1 is equal v2 test failled
	*/
	template <class T>
        void different(const T& v1, const T& v2){
                assert(v1 != v2);
	}
	
public :
	/**
	* Processing the test
        * return application exit code (= nb of errors)
	*/
        int Run(){
		tests.clear();
		// register tests
		SetTests();
	
		TestClass* const toTest = static_cast<TestClass*>(this);
		currentTest = 0;
		failledTests = 0;

		Before();
		
		// for each tests
                const typename std::list<TestFuncDescriptor>::const_iterator end = tests.end();
                for(typename std::list<TestFuncDescriptor>::iterator iter = tests.begin() ; iter != end ; ++iter){
			currentStep = 0;
			failledSteps = 0;
			
			std::cout << "[Start] " << (*iter).name << "\n";
			
			PreTest();
			TestFunc ff = (*iter).func;
			(toTest->*ff)();
			PostTest();
			
			if(failledSteps){
				std::cout << "[Finished] FAILLED (" << failledSteps << "/" << currentStep<< " steps failled)\n";
				++failledTests;
			}
                        else{
				std::cout << "[Finished] PASSED (" << currentStep << " steps)\n";
			}
			
			++currentTest;
		}
		
		
		After();

		std::cout <<"Test is over, " << (totalTests-failledTests) << " Passed, " << failledTests << " Failled\n";

                return failledTests;
	}

};





#endif