/*
	Test-ZReferenceCounter.cpp
	Author: James Russell <jcrussell@762studios.com>

	Purpose: Unit Test the ZReferenceCounter class.
*/

#include "ZUnitTest.hpp"

#include <ZUtil/ZConcurrency.hpp>
#include <ZUtil/ZReferenceCounter.hpp>
#include <SST/SST_Thread.h> // SST_Concurrency_YieldThread()

static const char* test_GainLoseReferences();
static const char* test_Signal();

//List of unit tests
ZUnitTest ZReferenceCounterUnitTests[] =
{
	{ "ZReferenceCounter: Gain / Lose References",	test_GainLoseReferences },
	{ "ZReferenceCounter: Signal",					test_Signal }
};

//Now declare the ZUnitTestBlock associated with this.
DECLARE_ZTESTBLOCK(ZReferenceCounter);


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

static const char* test_GainLoseReferences()
{
	ZReferenceCounter counter;

	//Ensure both start at zero
	TASSERT(counter.GetStrongRefCount() == 0, "Strong count set to non-zero value on default construct!");
	TASSERT(counter.GetWeakRefCount() == 0, "Weak count set to non-zero value on default construct!");

	//Test GainStrongRef()
	counter.GainStrongRef();
	TASSERT(counter.GetStrongRefCount() == 1, "Strong count not equal to 1 after strong reference gained!");

	//Test GainWeakRef()
	counter.GainWeakRef();
	counter.GainWeakRef();
	TASSERT(counter.GetWeakRefCount() == 2, "Weak count not equal to 2 after double increment!");

	//Test validity of both counters after weak ref decrement
	uint32_t combinedCount = counter.LoseWeakRef();
	TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(combinedCount) == 1, "After losing weak reference, strong count does not equal one!");
	TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(combinedCount) == 1, "After losing weak reference, weak count does not equal one!");

	//Test validity of both counters after strong ref decrement
	combinedCount = counter.LoseStrongRef();
	TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(combinedCount) == 0, "After losing strong reference, strong count does not equal zero!");
	TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(combinedCount) == 1, "After losing strong reference, weak count does not equal one!");

	//Test validity of both counters when all references are lost
	combinedCount = counter.LoseWeakRef();
	TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(combinedCount) == 0, "After losing second weak reference, strong count does not equal zero!");
	TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(combinedCount) == 0, "After losing second weak reference, weak count does not equal one!");

	return ZTEST_SUCCESS;
}

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

static int signalDeallocate(void* _refCounter)
{
	//This should block
	reinterpret_cast<ZReferenceCounter*>(_refCounter)->SignalDeallocateObject();

	return 0;
}

static const char* test_Signal()
{
	ZReferenceCounter counter;

	counter.GainStrongRef();
	counter.GainWeakRef();

	TASSERT(counter.GetStrongRefCount() == 1, "Strong count incorrect after GainStrongRef!");
	TASSERT(counter.GetWeakRefCount() == 1, "Weak count incorrect after GainWeakRef!");

	TASSERT(counter.SignalInUse(), "SignalInUse returned false when it should be valid to use!");

	TASSERT(counter.State > 0, "State not increased after SignalInUse!");
	
	ZThreadContext ctxt = ZConcurrency::CreateThread(signalDeallocate, &counter);

	//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 (counter.State > 0)
		SST_Concurrency_YieldThread();

	TASSERT(counter.SignalInUse() == false, "SignalInUse returned true when it should be invalid to use!");

	counter.SignalUnused(); //This should allow the thread to return from the SignalDeallocate method

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

	TASSERT(counter.State == (-1 & ZREFCOUNTER_DEALLOC_BIT), "State variable invalid after SignalDeallocate and SignalUnused!");

	return ZTEST_SUCCESS;
}
