
#include "ZUnitTest.hpp"

#include <ZUtil/ZConcurrency.hpp>
#include <ZUtil/ZWeakPointer.hpp>

static const char* test_Constructors_Nullify();
static const char* test_Operators();
static const char* test_Cast();
static const char* test_ArrayContainment();
static const char* test_ListContainment();
static const char* test_RingBufferContainment();
static const char* test_DeallocLock();
static const char* test_MultithreadAccess();

//List of unit tests
ZUnitTest ZWeakPointerUnitTests[] =
{
	{ "ZWeakPointer: Constructors, Nullify",					test_Constructors_Nullify },
	{ "ZWeakPointer: Operators",								test_Operators },
	{ "ZWeakPointer: Cast",										test_Cast },
	{ "ZWeakPointer: Containment in ZArray",					test_ArrayContainment },
	{ "ZWeakPointer: Containment in ZList",						test_ListContainment },
	{ "ZWeakPointer: Containment in ZRingBuffer",				test_RingBufferContainment },
	{ "ZWeakPointer: Deallocation Lock",						test_DeallocLock },
	{ "ZWeakPointer: Multithreaded Access, Deallocation Lock",	test_MultithreadAccess }
};

DECLARE_ZTESTBLOCK(ZWeakPointer);

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

namespace ZWeakPointerTestObjects
{
	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;

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

	//Method used to nullify a smart pointer in another thread
	int nullifySmartPointer(void* _sPtr)
	{
		ZSmartPointer<TestObject>& sPtr = *reinterpret_cast<ZSmartPointer<TestObject>*>(_sPtr);

		sPtr = NULL;

		return 0;
	}
}

using namespace ZWeakPointerTestObjects;

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

static const char* test_Constructors_Nullify()
{
	ZSmartPointer<TestObject> sNullPtr;
	ZSmartPointer<TestObject> sPtr(new TestObject());

	ZReferenceCounter* sPtrRefCounter = sPtr.Counter();

	ZWeakPointer<TestObject> wNullPtr;
	TASSERT(wNullPtr.Pointer() == NULL, "ZWeakPointer() constructed default pointer not set to null!\n");
	TASSERT(wNullPtr.Counter() == NULL, "ZWeakPointer() constructed default pointer with counter!\n");

	ZWeakPointer<TestObject> wNullPtrFromPtr(sNullPtr);
	TASSERT(wNullPtr.Pointer() == NULL, "ZWeakPointer() constructed from null smart pointer not set to null!\n");
	TASSERT(wNullPtr.Counter() == NULL, "ZWeakPointer() constructed from null smart pointer with counter!\n");

	ZWeakPointer<TestObject> wPtrFromSPtr = sPtr;
	TASSERT(TestObject::Instances == 1, "ZWeakPointer() constructed additional test object from smart pointer!");
	TASSERT(sPtr.Counter() == wPtrFromSPtr.Counter(), "ZWeakPointer() constructed from smart pointer does not share counter!");
	TASSERT(wPtrFromSPtr.Counter()->GetStrongRefCount() == 1, "ZWeakPointer() constructed from smart pointer added strong reference!");
	TASSERT(wPtrFromSPtr.Counter()->GetWeakRefCount() == 1, "ZWeakPointer() constructed from smart pointer did not add weak reference!");

	ZWeakPointer<TestObject> wPtrFromWPtr = wPtrFromSPtr;
	TASSERT(TestObject::Instances == 1, "ZWeakPointer() constructed additional test object from weak pointer!");
	TASSERT(wPtrFromSPtr.Counter() == wPtrFromWPtr.Counter(), "ZWeakPointer() constructed from weak pointer does not share counter!");
	TASSERT(wPtrFromWPtr.Counter()->GetStrongRefCount() == 1, "ZWeakPointer() constructed from weak pointer added strong reference!");
	TASSERT(wPtrFromWPtr.Counter()->GetWeakRefCount() == 2, "ZWeakPointer() constructed from weak pointer did not add weak reference!");

	//Equality Tests
	TASSERT(wPtrFromSPtr == sPtr, "Equality test failed on weak pointer from smart pointer and smart pointer!");
	TASSERT(wPtrFromWPtr == sPtr, "Equality test failed on weak pointer from weak pointer and smart pointer!");
	TASSERT(wPtrFromWPtr == wPtrFromSPtr, "Equality test failed on weak pointer from smart pointer and weak pointer from weak pointer!");

	//Lose the object
	sPtr = NULL;
	
	TASSERT(TestObject::Instances == 0, "Weak references prevented ZSmartPointer from deallocating object!");
	TASSERT(sPtr.Counter() == NULL, "ZSmartPointer failed to nullify pointer to reference counter!");

	TASSERT(sPtrRefCounter->GetStrongRefCount() == 0, "Nullification of smart pointer failed to lose strong reference!");

	TASSERT(sPtrRefCounter->GetWeakRefCount() == 2, "Nullification of smart pointer reduced weak reference count!");
	TASSERT(wPtrFromSPtr == NULL, "Nullification of weak pointer from smart pointer failed!");

	TASSERT(sPtrRefCounter->GetWeakRefCount() == 1, "Auto-nullification of weak pointer failed to lose weak reference!");
	TASSERT(wPtrFromWPtr == NULL, "Nullification of weak pointer from weak pointer failed!");

	TASSERT(wPtrFromSPtr.Counter() == NULL, "Nullification of weak pointer from smart pointer failed to lose reference counter!");
	TASSERT(wPtrFromWPtr.Counter() == NULL, "Nullification of weak pointer from weak pointer failed to lose reference counter!");

	return ZTEST_SUCCESS;
}

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

static const char* test_Operators()
{
	TestObject* object1 = new TestObject();
	TestObject* object2 = new TestObject();

	ZSmartPointer<TestObject> sPtr1(object1);
	ZSmartPointer<TestObject> sPtr2(object2);

	ZWeakPointer<TestObject> wPtr1 = sPtr1;
	ZWeakPointer<TestObject> wPtr2 = sPtr2;
	
	TASSERT(wPtr1 == object1, "Equality operator failed between weak pointer and object pointer!");
	TASSERT(wPtr1 == sPtr1, "Equality operator failed between weak pointer and smart pointer!");
	TASSERT(wPtr1 != sPtr2, "Equality operator succeeded between weak pointer and different smart pointer!");
	TASSERT(wPtr1 != wPtr2, "Equality operator succeeded between weak pointer and different weak pointer!");
	TASSERT(wPtr1->Foo(), "Access operator, call to Foo() failed to return true!");
	TASSERT((*wPtr1).Foo(), "Dereference operator, call to Foo() failed to return true!");

	return ZTEST_SUCCESS;
}

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

static const char* test_Cast()
{
	ZSmartPointer<TestObject> sPtr(new TestObjectSubclass());
	ZWeakPointer<TestObject> wPtr1(sPtr);
	ZWeakPointer<TestObjectSubclass> wPtr2(sPtr);

	TASSERT(TestObject::Instances == 1, "Subclass weak pointer created additional test object!");
	TASSERT(sPtr.Counter()->GetWeakRefCount() == 2, "Subclassed weak pointer failed to share reference counter!"); 

	TASSERT(!wPtr1->Foo(), "TestObject virtual method call failed!");
	TASSERT(!wPtr2->Foo(), "TestObjectSubclass virtual method call failed!");
	TASSERT(wPtr1.Cast<TestObjectSubclass>()->Bar(), "ZWeakPointer failed to cast!");

	TASSERT(autoCast(wPtr2), "ZWeakPointer failed to auto up-cast!");

	return ZTEST_SUCCESS;
}

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

static const char* test_ArrayContainment()
{
	ZArray< ZSmartPointer< TestObject > > sPtrArray(10);
	ZArray< ZWeakPointer< TestObject > > wPtrArray(10);

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

	for (size_t i = 0; i < 10; i++)
	{
		//Add a smart pointer to the set
		sPtrArray.PushBack(ZSmartPointer<TestObject>(znew TestObject()));
		sPtrArray[i]->Value = i;

		//Add a weak pointer created from that smart pointer
		wPtrArray.PushBack(sPtrArray[i]);
	}

	for (size_t i = 0; i < 10; i++)
	{
		TASSERT(sPtrArray[i].Counter()->GetStrongRefCount() == 1, "Starting Strong Count is incorrect!");
		TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 1, "Starting Weak Count is incorrect!");
	}

	wPtrArray.PopBack();

	TASSERT(sPtrArray[sPtrArray.Size() - 1].Counter()->GetWeakRefCount() == 0, "PopBack() with weak pointer array failed to remove weak reference!");

	wPtrArray.PopFront();

	TASSERT(sPtrArray[0].Counter()->GetWeakRefCount() == 0, "PopFront() with weak pointer array failed to remove weak reference!");

	wPtrArray.Erase(0, 2);

	for (size_t i = 1; i < 3; i++)
	{
		TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Erase() with weak pointer array failed to remove weak reference!");
	}

	//At this point, we have six elements in the weak pointer array
	wPtrArray.Resize(4);

	//That call should have ditched weak references from the last two
	for (size_t i = 7; i < 9; i++)
	{
		TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Resize() with lower count failed to remove weak references!");
	}

	wPtrArray.Clear();

	for (size_t i = 0; i < 10; i++)
	{
		TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Clear() failed to remove all weak references!");
	}

	return ZTEST_SUCCESS;
}

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

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

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

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

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

static int useWeakPointer(void* _wPtr)
{
	ZWeakPointer<TestObject>& wPtr = *reinterpret_cast<ZWeakPointer<TestObject>*>(_wPtr);

	DeallocLock(wPtr) {
		return 0;
	} else {
		return 1;
	}
}

static const char* test_DeallocLock()
{
	TestObject* object = znew TestObject();

	ZSmartPointer<TestObject> sPtr(object);

	ZWeakPointer<TestObject> wPtr = sPtr;

	DeallocLock(wPtr) {
		TASSERT(wPtr.Counter()->State > 0, "ZWeakPointer DeallocLock did not increase State variable!");
	}

	TASSERT(wPtr.Counter()->State == 0, "ZWeakPointer DeallocLock did not decrement State variable!");

	ZThreadContext ctxt;
	ZThreadContext ctxt2;

	DeallocLock(wPtr) {
		ctxt = ZConcurrency::CreateThread(nullifySmartPointer, &sPtr);

		//At this point, the other thread should be waiting on this one, but 
		//should signal us by setting state to NULL with an atomic XOR
		while (wPtr.Counter()->State > 0)
			continue;

		ZWeakPointer<TestObject> wPtr2 = wPtr;

		ctxt2 = ZConcurrency::CreateThread(useWeakPointer, &wPtr2);

		ZConcurrency::WaitThread(ctxt2);

		//At the time we have gotten here, State has been signaled and we should fail to be able to signal 'InUse'
		TASSERT(ctxt2.ThreadExitStatus == 1, "ZWeakPointer was locked and used after being signaled for deallocation!");

		ZConcurrency::DestroyThread(ctxt2);
	}

	//Now that we have signaled that we are no longer in use, we can wait
	ZConcurrency::WaitThread(ctxt);
	ZConcurrency::DestroyThread(ctxt);

	TASSERT(TestObject::Instances == 0, "Failed to completely deallocate all TestObject instances!");

	return ZTEST_SUCCESS;
}

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

#define TEST_NUM_POINTERS (1024)

static ZArray< ZWeakPointer<TestObject> > g_threadPointers;

static int threadTest(void *arg)
{
	URFP(arg);

	while (WHILE_TRUE)
	{
		size_t numupdated = 0;

		for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
		{
			if (g_threadPointers[i] == NULL)
				continue;

			//Effectively DeallocLock, but with the ability to inspect testLock
			if (ZWeakPointerDeallocLock testLock = g_threadPointers[i]()) {
				g_threadPointers[i]->Value++;
				numupdated++;
			}
		}

		if (numupdated == 0)
			break;
	}

	return 0;
}

static const char* test_MultithreadAccess()
{
	ZArray< ZSmartPointer<TestObject> > pointers;

	pointers.Resize(TEST_NUM_POINTERS);
	g_threadPointers.Resize(TEST_NUM_POINTERS);

	for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
	{
		pointers[i] = znew TestObject();
		g_threadPointers[i] = pointers[i];

		pointers[i]->Value = 0;
	}

	ZThreadContext ctxt = ZConcurrency::CreateThread(threadTest, NULL);

	//Each object now has a smart pointer in this thread and a weak pointer that will be used 
	//by the other thread, so it should be safe to use the weak pointer as long as the DeallocLock
	//macro is used to prevent deallocation

	for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
	{
		pointers[i] = NULL;

		ZConcurrency::SleepThread(10);
	}

	ZConcurrency::WaitThread(ctxt);
	ZConcurrency::DestroyThread(ctxt);

	TASSERT(TestObject::Instances == 0, "Failed to completely deallocate all TestObject instances!");

	return ZTEST_SUCCESS;
}