#ifndef _beos_thread_manager_h_
#define _beos_thread_manager_h_

#include <cppunit/Exception.h>
#include <cppunit/TestResult.h>
#include <OS.h>
#include <signal.h>
#include <string>

// Pointer to a function that takes no parameters and
// returns no result. All threads must be implemented
// in a function of this type.

// Helper class to handle thread management
template <class TestClass, class ExpectedException>
class BThreadManager {
public:
	typedef void (TestClass::*ThreadMethod)();
	
	BThreadManager(std::string threadName, TestClass *object, ThreadMethod method, sem_id &threadSem);
	~BThreadManager();
	
    status_t LaunchThread(CppUnit::TestResult *result);
    
	// Thread management methods
	int32 Stop();
	int32 WaitForThread();
	bool IsRunning();

	std::string getName() const { return fName; }

protected:
	std::string fName;
	TestClass *fObject;
	ThreadMethod fMethod;
	thread_id fID;
	CppUnit::TestResult *fTestResult;
	sem_id &fThreadSem;
	
	static long EntryFunction(BThreadManager<TestClass, ExpectedException>* manager);
	void Run();	

};

template <class TestClass, class ExpectedException>
BThreadManager<TestClass, ExpectedException>::BThreadManager(
	std::string threadName,
	TestClass *object,
	ThreadMethod method,
	sem_id &threadSem
)
	: fName(threadName)
	, fObject(object)
	, fMethod(method)
	, fID(0)
	, fTestResult(NULL)
	, fThreadSem(threadSem)
{
}
	
	
template <class TestClass, class ExpectedException>
BThreadManager<TestClass, ExpectedException>::~BThreadManager() {
	Stop();
}
	
	
template <class TestClass, class ExpectedException>
int32
BThreadManager<TestClass, ExpectedException>::WaitForThread() {
	int32 result = 0;
	if (find_thread(NULL) != fID) 
		wait_for_thread(fID, &result);
	return result;
}
		
template <class TestClass, class ExpectedException>
int32
BThreadManager<TestClass, ExpectedException>::Stop() {
	int32 result = 0;
	if (find_thread(NULL) != fID) {
		while (IsRunning()) {
			kill(fID, SIGINT);
			snooze(1000000);
		}
		result = WaitForThread();
	}	
	return result;
}
	
	
template <class TestClass, class ExpectedException>
bool
BThreadManager<TestClass, ExpectedException>::IsRunning(void) {
	if (fID != 0) {
		thread_info info;
		if (get_thread_info(fID, &info) == B_OK)
			return true;
		else
			fID = 0;
	}
	return false;
}
	
	
template <class TestClass, class ExpectedException>
status_t
BThreadManager<TestClass, ExpectedException>::LaunchThread(CppUnit::TestResult *result) {
	if (IsRunning())
		return B_ALREADY_RUNNING;

	fTestResult = result;
	fID = spawn_thread((thread_entry)(BThreadManager::EntryFunction),
		fName.c_str(), B_NORMAL_PRIORITY, this);
					
	status_t err;
	if (fID == B_NO_MORE_THREADS || fID == B_NO_MEMORY) {
		err = fID;
		fID = 0;
	} else {
		// Aquire the semaphore, then start the thread.
		if (acquire_sem(fThreadSem) != B_OK)
			throw CppUnit::Exception("BThreadManager::LaunchThread() -- Error acquiring thread semaphore");
		err = resume_thread(fID);
	}
	return err;
}
	
	
template <class TestClass, class ExpectedException>
long
BThreadManager<TestClass, ExpectedException>::EntryFunction(BThreadManager<TestClass, ExpectedException> *manager) {
	manager->Run();
	return 0;
}
	
template <class TestClass, class ExpectedException>
void
BThreadManager<TestClass, ExpectedException>::Run(void) {
	// These outer try/catch blocks handle unexpected exceptions.
	// Said exceptions are caught and noted in the TestResult
	// class, but not allowed to escape and disrupt the other
	// threads that are assumingly running concurrently.
	try {
	
		// Our parent ThreadedTestCaller should check fObject to be non-NULL,
		// but we'll do it here too just to be sure.
		if (!fObject)
			throw CppUnit::Exception("BThreadManager::Run() -- NULL fObject pointer");
		
		// Before running, we need to add this thread's name to
		// the object's id->(name,subtestnum) map.
		fObject->InitThreadInfo(fID, fName);

		// This inner try/catch block is for expected exceptions.
		// If we get through it without an exception, we have to
		// raise a different exception that makes note of the fact
		// that the exception we were expecting didn't arrive. If
		// no exception is expected, then nothing is done (see
		// "cppunit/TestCaller.h" for detail on the classes used
		// to handle this behaviour).
		try {
			(fObject->*fMethod)();		
	    } catch ( ExpectedException & ) {
			return;
		}		
	  	CppUnit::ExpectedExceptionTraits<ExpectedException>::expectedException();
	  	
	} catch ( CppUnit::Exception &e ) {
		// Add on the thread name, then note the exception
        CppUnit::Exception *threadException = new CppUnit::Exception(
        	std::string(e.what()) + " (thread: " + fName + ")",
        	e.sourceLine()
        );        	        	
		fTestResult->addFailure( fObject, threadException );
	}
	catch ( std::exception &e ) {
		// Add on the thread name, then note the exception
        CppUnit::Exception *threadException = new CppUnit::Exception(
        	std::string(e.what()) + " (thread: " + fName + ")"
        );        	        	
		fTestResult->addError( fObject, threadException );
	}
	catch (...) {
		// Add on the thread name, then note the exception
		CppUnit::Exception *threadException = new CppUnit::Exception(
			"caught unknown exception (thread: " + fName + ")"
		);
		fTestResult->addError( fObject, threadException );
	}
	
	// Release the semaphore we acquired earlier
	release_sem(fThreadSem);  	
}
	

#endif
