/*
Unit tests for ZSmartPointer to ensure correct behavior.
*/

#define ZSTL_DISABLE_RUNTIME_CHECKS 1
#include "ZUnitTest.hpp"

#include <ZUtil/ZSmartPointer.hpp>
#include <ZUtil/ZConcurrency.hpp>
#include <SST/SST_MemBarrier.h>

static const char* test_Constructors();
static const char* test_Operators();
static const char* test_Cast();
static const char* test_Detach();
static const char* test_ArrayContainment();
static const char* test_ListContainment();
static const char* test_RingBufferContainment();
static const char* test_MultithreadAccess();

//List of unit tests
ZUnitTest ZSmartPointerUnitTests[] =
{
	{ "ZSmartPointer: Constructors",				test_Constructors },
	{ "ZSmartPointer: Operators",					test_Operators },
	{ "ZSmartPointer: Cast",						test_Cast },
	{ "ZSmartPointer: Detach",						test_Detach },
	{ "ZSmartPointer: Containment in ZArray",		test_ArrayContainment },
	{ "ZSmartPointer: Containment in ZList",		test_ListContainment },
	{ "ZSmartPointer: Containment in ZRingBuffer",	test_RingBufferContainment },
	{ "ZSmartPointer: Multithreaded Access",		test_MultithreadAccess }
};

DECLARE_ZTESTBLOCK(ZSmartPointer);

/*************************************************************************/

namespace ZSmartPointerTestObjects
{
	struct TestObject
	{
		//Constructor
		TestObject() { TestObject::Instances++; }

		//Destructor
		~TestObject() { TestObject::Instances--; }

		//Value field
		size_t Value;

		//Static Instance Count
		static size_t Instances;

		//Virtual Function
		virtual bool Foo() const { return true; }
	};

	struct TestObjectSubclass : public TestObject
	{
		//Constructor
		TestObjectSubclass() : TestObject() {}

		//Subclass Override
		bool Foo() const { return false; }

		//Subclass Method
		bool Bar() const { return true; }
	};

	//Defaults to zero
	size_t TestObject::Instances = 0;

	//Factory Method
	ZSmartPointer<TestObject> getAllocatedTestObject()
	{
		return ZSmartPointer<TestObject>(new TestObject());
	}

	//This function is used to test the auto up-casting of contained types
	bool autoCast(ZSmartPointer<TestObject> testObj)
	{
		return true;
	}
}

using namespace ZSmartPointerTestObjects;

/*************************************************************************/

static const char* test_Constructors()
{
	ZSmartPointer<TestObject> nullPtr;
	TASSERT(nullPtr.Pointer() == NULL, "ZSmartPointer() constructed default pointer not set to null!\n");
	TASSERT(nullPtr.Counter() == NULL, "ZSmartPointer() constructed default pointer with counter!\n");

	TestObject* testObj = new TestObject();
	ZSmartPointer<TestObject> fromPtr(testObj);
	TASSERT(fromPtr.Pointer() != NULL, "ZSmartPointer() constructed pointer from valid pointer as null!\n");
	TASSERT(fromPtr.Pointer() == testObj, "ZSmartPointer() constructed pointer from valid pointer and changed pointer!\n");
	TASSERT(fromPtr.Counter() != NULL, "ZSmartPointer() constructed pointer without counter!\n");
	TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(fromPtr.Counter()->CombinedCount) == 1, "ZSmartPointer() constructed default pointer with bad strong count!\n");
	TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(fromPtr.Counter()->CombinedCount) == 0, "ZSmartPointer() constructed default pointer with bad weak count!\n");

	ZSmartPointer<TestObject> fromOtherPtr(fromPtr);
	TASSERT(fromOtherPtr.Pointer() != NULL, "ZSmartPointer() constructed pointer from valid pointer as null!\n");
	TASSERT(fromOtherPtr.Pointer() == testObj, "ZSmartPointer() constructed pointer from valid pointer and changed pointer!\n");
	TASSERT(fromOtherPtr.Counter() != NULL, "ZSmartPointer() constructed pointer without counter!\n");
	TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(fromOtherPtr.Counter()->CombinedCount) == 2, "ZSmartPointer() constructed default pointer with bad strong count!\n");
	TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(fromOtherPtr.Counter()->CombinedCount) == 0, "ZSmartPointer() constructed default pointer with bad weak count!\n");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Operators()
{
	//Scope in to ensure all destructors get called
	{	
		TestObject *temp;

		//Equivalent to 
		//	TestObject* testObj1 = NULL;
		ZSmartPointer<TestObject> testObj1;

		TASSERT(TestObject::Instances == 0, "Default constructor allocated object!");

		//Equivalent to 
		//	TestObject* testObj2 = new TestObject();
		ZSmartPointer<TestObject> testObj2(new TestObject()); 

		TASSERT(TestObject::Instances == 1, "TestObject failed to construct!");
		TASSERT(testObj1 != testObj2, "ZSmartPointer != operator failed!");

		//Equivalent to
		//  testObj1 = new TestObject();
		temp = new TestObject();

		TASSERT(TestObject::Instances == 2, "TestObject failed to construct!");

		testObj1 = temp;

		//Equivalent syntax
		testObj1->Value = 5;
		testObj2->Value = 10;

		TASSERT(testObj1->Value == 5 && testObj2->Value == 10, "ZSmartPointer = operator failed!");
		TASSERT(testObj1 != testObj2, "ZSmartPointer != operator failed!");

		//Equivalent to
		//	delete testObj2;
		//	testObj2 = testObj1;
		testObj2 = testObj1;

		TASSERT(TestObject::Instances == 1, "ZSmartPointer failed to deallocate!");
		TASSERT(testObj2->Value == 5, "ZSmartPointer = operator failed!");
		TASSERT(testObj2 == testObj1, "ZSmartPointer == operator failed!");

		//Hard to equate to new/delete, as it allocates a new object and returns a smart pointer to it
		testObj1 = getAllocatedTestObject();

		TASSERT(TestObject::Instances == 2, "ZSmartPointer copy constructor and assignment operator failed!");

		//Equivalent syntax
		testObj1->Value = 10;

		TASSERT(testObj2->Value == 5, "ZSmartPointer referencing incorrect object!");
		TASSERT(testObj1->Value == 10, "ZSmartPointer referencing incorrect object!");

		//Equivalent to
		//  delete testObj1;
		//  testObj1 = testObj2;
		testObj1 = testObj2;

		TASSERT(TestObject::Instances == 1, "ZSmartPointer failed to deallocate!");

		testObj2 = NULL;

		TASSERT(TestObject::Instances == 1, "ZSmartPointer deallocated incorrectly!");

		testObj1 = NULL;

		TASSERT(TestObject::Instances == 0, "ZSmartPointer failed to deallocate!");
	}

	TASSERT(TestObject::Instances == 0, "ZSmartPointer failed to call destructors!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Cast()
{
	ZSmartPointer<TestObject> testObj1(new TestObject());
	TASSERT(TestObject::Instances == 1, "Superclass failed to allocate!");

	ZSmartPointer<TestObject> testObj2(new TestObjectSubclass());
	TASSERT(TestObject::Instances == 2, "Subclass failed to allocate!");

	TASSERT(testObj1->Foo(), "TestObject virtual method call failed!");
	TASSERT(!testObj2->Foo(), "TestObjectSubclass virtual method call failed!");
	TASSERT(testObj2.Cast<TestObjectSubclass>()->Bar(), "ZSmartPointer failed to cast!");

	ZSmartPointer<TestObjectSubclass> testObjSub = testObj2.Cast<TestObjectSubclass>();

	TASSERT(TestObject::Instances == 2, "ZSmartPointer cast function created new object!");

	TASSERT(autoCast(testObjSub), "ZSmartPointer failed to auto up-cast!");

	testObj2 = NULL;

	TASSERT(TestObject::Instances == 2, "ZSmartPointer casted to subclass failed to maintain reference!");

	testObjSub = NULL;

	TASSERT(TestObject::Instances == 1, "ZSmartPointer subclass nullify failed to call destructor!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Detach()
{
	TestObject* object = new TestObject();

	ZSmartPointer<TestObject> sPtr(object);
	ZSmartPointer<TestObject> sPtr2(sPtr);

	TASSERT(sPtr.Detach() == NULL, "Detach() succeeded when two references are held!");

	sPtr2 = NULL;

	TASSERT(sPtr.Detach() == object, "Detach() failed when one reference was held!");

	delete object;

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_ArrayContainment()
{
	ZArray< ZPtr< TestObject > > testArray(10);

	TASSERT(TestObject::Instances == 0, "ZArray of smart pointers allocated instances!");

	for (size_t i = 0; i < 10; i++)
	{
		testArray.PushBack(ZSmartPointer<TestObject>(new TestObject()));
		testArray[i]->Value = i;
	}

	TASSERT(TestObject::Instances == 10, "ZArray of smart pointers failing to maintain instances!");

	//After PopBack, we should still have 10 objects (we held onto the popped element)
	ZPtr<TestObject> testObj1 = testArray.PopBack();

	TASSERT(TestObject::Instances == 10, "ZArray failed to deallocate contained smart pointer!");
	TASSERT(testObj1->Value == 9, "ZArray failed to return correct smart pointer from PopBack!");

	//Now we should have lost the popped element from earlier
	testObj1 = testArray.PopFront();

	TASSERT(TestObject::Instances == 9, "ZArray failed to deallocate smart pointer after PopFront!");
	TASSERT(testObj1->Value == 0, "ZArray failed to return correct smart pointer from PopFront!");
	
	//This assignment will cause us to lose a test object, but also gain one
	testObj1 = ZPtr<TestObject>(new TestObject()); 
	testObj1->Value = 11;

	//This should keep the same amount of smart pointers (two references to the last one)
	testArray.PushBack(testObj1);

	TASSERT(TestObject::Instances == 9, "ZArray failed to accept smart pointer with PushBack!");
	TASSERT(testArray[testArray.Size() - 1]->Value == 11, "ZArray failed to push correct smart pointer with PushBack!");
	
	//This will cause us to lose one
	testArray.Erase(5);

	TASSERT(TestObject::Instances == 8, "ZArray failed to deallocate smart pointer after Erase(5)!");

	//This will cause us to lose two more
	testArray.Erase(2, 4);

	TASSERT(TestObject::Instances == 6, "ZArray failed to deallocate smart pointers after Erase(2, 4)!");

	//This should cause us to lose them all, but testObj1 still has a single reference
	testArray.Clear();

	TASSERT(TestObject::Instances == 1, "ZArray failed to deallocate all smart pointers after Clear()!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_ListContainment()
{
	unsigned int i;

	ZList< ZPtr<TestObject> > testList;

	TASSERT(TestObject::Instances == 0, "ZList of smart pointers allocated instances!");

	for (i = 0; i < 10; i++)
	{
		testList.PushBack(ZPtr<TestObject>(new TestObject()));
		testList.Back()->Value = i;
	}

	TASSERT(TestObject::Instances == 10, "ZList of smart pointers failing to maintain instances!");

	ZPtr<TestObject> testObj1 = testList.PopBack();

	TASSERT(TestObject::Instances == 10, "ZList failed to deallocate smart pointer!");
	TASSERT(testObj1->Value == 9, "ZList failed to return correct smart pointer!");

	testObj1 = testList.PopFront();

	TASSERT(TestObject::Instances == 9, "ZSmartPointer failed to deallocate!");
	TASSERT(testObj1->Value == 0, "ZList failed to return correct smart pointer!");

	testList.PushBack(testObj1);

	TASSERT(TestObject::Instances == 9, "ZList failed to accept SmartPointer!");
	TASSERT(testList.Back()->Value == 0, "ZList failed to push correct smart pointer!");
	
	ZList< ZSmartPointer< TestObject > >::Iterator itr3 = testList[5];

	testList.Erase(itr3);

	TASSERT(TestObject::Instances == 8, "ZList failed to deallocate smart pointer!");

	ZList< ZSmartPointer< TestObject > >::Iterator itr1 = testList[2];
	ZList< ZSmartPointer< TestObject > >::Iterator itr2 = testList[4];

	testList.Erase(itr1, itr2);

	TASSERT(TestObject::Instances == 6, "ZList failed to deallocate smart pointers!");

	testList.Clear();

	TASSERT(TestObject::Instances == 1, "ZList failed to deallocate all smart pointers!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_RingBufferContainment()
{
	return "TODO";
}

/*************************************************************************/

#define TEST_NUM_POINTERS (1024)
#define TEST_NUM_THREADS	4

static ZArray< ZSmartPointer<TestObject> > g_threadPointers[TEST_NUM_THREADS];
static SST_TLS tls;
static SST_Event readyGo;

static void testFunction(ZSmartPointer<TestObject> _sPtr)
{
	ZSmartPointer<TestObject>* sPtr = (ZSmartPointer<TestObject>*)SST_Concurrency_GetTLSValue(tls);	//This is just here to hold the reference for a while

	*sPtr = _sPtr;
}

static int threadTest(void* tid)
{
	size_t index = (size_t)tid;
	ZArray< ZSmartPointer<TestObject> >* thrPtrs = &g_threadPointers[index];
	ZSmartPointer<TestObject>* ptr;
	size_t numnullified = 0;
	
	//Allocate a smart pointer object, store in TLS slot
	ptr = new ZSmartPointer<TestObject>();
	SST_Concurrency_SetTLSValue(tls,  (void*)ptr);

	//For for ready
	SST_Concurrency_WaitEvent(readyGo, SST_WAIT_INFINITE);
	
	//Iterate over and nullify if value is above the loop count
	while (WHILE_TRUE)
	{
		for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
		{
			if (i == 0)	{
				i = 0;
			}

			if ((*thrPtrs)[i] != NULL && (*thrPtrs)[i]->Value > i)
			{
				(*thrPtrs)[i] = NULL;
				numnullified++;
			}
			else
			{
				testFunction((*thrPtrs)[i]);
			}

		}

		if (numnullified == TEST_NUM_POINTERS)
			break;

		//ZConcurrency::SleepThread(10);
	}

	testFunction(ZSmartPointer<TestObject>()); //Nullify

	delete ptr;
	
	return 0;
}

/*
This is a bit of a strange test.  The only way to really ensure that it is safe to lose and gain references
across threads is to have two threads operating on smart pointers that reference the same object.  If the method
terminates, than it was safe.  Otherwise... we loop because of bad reference count state.
*/
static const char* test_MultithreadAccess()
{
	ZArray< ZSmartPointer<TestObject> > pointers;
	ZThreadContext ctx[TEST_NUM_THREADS];
	
	pointers.Resize(TEST_NUM_POINTERS);
	
	//Resize all thread smart pointer arrays
	for(size_t j=0; j<TEST_NUM_THREADS; j++)
		g_threadPointers[j].Resize(TEST_NUM_POINTERS);

	for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
	{
		pointers[i] = znew TestObject();
		
		//Copy smart pointers to other threads
		for(size_t j=0; j< TEST_NUM_THREADS; j++)
			g_threadPointers[j][i] = pointers[i];
	}

	//Create TLS and event
	tls = SST_Concurrency_CreateTLS();
	readyGo = SST_Concurrency_CreateEvent();
	
	for(size_t i=0; i<TEST_NUM_THREADS; i++)
		ctx[i] = ZConcurrency::CreateThread(threadTest, (void*)i);

	size_t numdeleted = 0;
	size_t itrval = 0;

	SST_Concurrency_SignalEvent(readyGo);
	while (numdeleted < TEST_NUM_POINTERS)
	{
		TestObject *object;

		for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
		{
			object = pointers[i].Detach();	//This will only work if this thread holds the only reference
			
			if (object != NULL)
			{
				zdelete object;
				numdeleted++;
			}
			else if (pointers[i] != NULL)
				pointers[i]->Value = itrval;
		}

		if(itrval < TEST_NUM_POINTERS)
			itrval++;
	}
	
	//Await thread exit
	for(size_t i=0; i<TEST_NUM_THREADS; i++)
	{
		ZConcurrency::WaitThread(ctx[i]);
		ZConcurrency::DestroyThread(ctx[i]);
	}
	
	SST_Concurrency_DestroyEvent(readyGo);
	SST_Concurrency_DestroyTLS(tls);
	
	return ZTEST_SUCCESS;
}
