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

	Purpose: Unit Test the ZRandomGenerator class.

	Changelog:
	2011/12/18 - Removed dependency on ZString (crertel)
	2011/11/27 - creation (jcrussell)
*/

#include "ZUnitTest.hpp"

#include <ZUtil/ZAlloc.hpp>
#include <ZUtil/ZRandomGenerator.hpp>

//#include <ZUtil/ZMath.hpp>

static const char* testMersenne();
static const char* testCMWC();
static const char* testSmallPRNG();

#define FPEQUALS(a,b) (a == b)

#define ZRG_TEST_SEED_1 (1000)
#define ZRG_TEST_SEED_2 (1024)

#define ZRG_TEST_FLOAT_RANGE_MIN (-64000.0f)
#define ZRG_TEST_FLOAT_RANGE_MAX (64000.0f)

#define ZRG_TEST_INT_RANGE_MIN (-10000)
#define ZRG_TEST_INT_RANGE_MAX (10000)

//List of unit tests
ZUnitTest ZRandomGeneratorUnitTests[] =
{
	{
		"Test SST_Mersenne Generator",
		testMersenne
	},
	{
		"Test SST_CMWC Generator",
		testCMWC
	},
	{
		"Test SST_SmallPRNG Generator",
		testSmallPRNG
	}
};

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

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

static const char* testGenerator(ZRandomGenerator *generator)
{
	uint32_t seed;
	uint64_t sequence, correctSequence;

	seed = generator->GetSeed();

	//First, we test grabbing floats
	float f1, f2, f3, f4;

	f1 = generator->GetFloat();
	f2 = generator->GetFloat();
	f3 = generator->GetFloat();
	f4 = generator->GetFloat();
	
	correctSequence = 4;

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
		"Generator has incorrect sequence number after GetFloat!");

	TASSERT(f1 >= 0.0f && f1 < 1.0f, "f1 is out of range [0, 1.0)!");
	TASSERT(f2 >= 0.0f && f2 < 1.0f, "f2 is out of range [0, 1.0)!");
	TASSERT(f3 >= 0.0f && f3 < 1.0f, "f3 is out of range [0, 1.0)!");
	TASSERT(f4 >= 0.0f && f4 < 1.0f, "f4 is out of range [0, 1.0)!");

	//Swap seed, make sure we don't get the same four floats
	float f5, f6, f7, f8;

	generator->SetSeed(ZRG_TEST_SEED_2);

	seed = generator->GetSeed();

	TASSERT(seed == ZRG_TEST_SEED_2, "After reseeding, generator returns incorrect seed!");

	sequence = generator->GetSequence();

	TASSERT(sequence == 0, "After reseeding, generator does not have sequence zero!");

	f5 = generator->GetFloat();
	f6 = generator->GetFloat();
	f7 = generator->GetFloat();
	f8 = generator->GetFloat();

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after SetSeed and four gets!");

	bool equal;

	equal = FPEQUALS(f1, f5) && FPEQUALS(f2, f6) && FPEQUALS(f3, f7) && FPEQUALS(f4, f8);

	TASSERT(!equal, "After reseeding, generator generates same four numbers!");

	//Now we test grabbing an array of floats
	float f1a[10], f2a[1000], f3a[100000];

	generator->GetFloatArray(f1a, sizeof(f1a) / sizeof(float));
	generator->GetFloatArray(f2a, sizeof(f2a) / sizeof(float));
	generator->GetFloatArray(f3a, sizeof(f3a) / sizeof(float));

	correctSequence = correctSequence + (sizeof(f1a) / sizeof(float)) + (sizeof(f2a) / sizeof(float)) + (sizeof(f3a) / sizeof(float));

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetFloatArray!");

	for (size_t i = 0; i < sizeof(f1a) / sizeof(float); i++)
	{
		TASSERT(f1a[i] >= 0.0f && f1a[i] < 1.0f, "f1a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(f2a) / sizeof(float); i++)
	{
		TASSERT(f2a[i] >= 0.0f && f2a[i] < 1.0f, "f2a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(f3a) / sizeof(float); i++)
	{
		TASSERT(f3a[i] >= 0.0f && f3a[i] < 1.0f, "f3a has members out of range!");
	}

	//Now we test grabbing floats in a range
	f1 = generator->GetFloatInRange(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f2 = generator->GetFloatInRange(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f3 = generator->GetFloatInRange(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f4 = generator->GetFloatInRange(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);

	correctSequence += 4;

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetFloatInRange!");

	TASSERT(f1 >= ZRG_TEST_FLOAT_RANGE_MIN && f1 < ZRG_TEST_FLOAT_RANGE_MAX, "f1 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX)!");
	TASSERT(f2 >= ZRG_TEST_FLOAT_RANGE_MIN && f2 < ZRG_TEST_FLOAT_RANGE_MAX, "f2 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX)!");
	TASSERT(f3 >= ZRG_TEST_FLOAT_RANGE_MIN && f3 < ZRG_TEST_FLOAT_RANGE_MAX, "f3 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX)!");
	TASSERT(f4 >= ZRG_TEST_FLOAT_RANGE_MIN && f4 < ZRG_TEST_FLOAT_RANGE_MAX, "f4 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX)!");

	//Now we test grabbing arrays of floats in a range
	generator->GetFloatArrayInRange(f1a, sizeof(f1a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	generator->GetFloatArrayInRange(f2a, sizeof(f2a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	generator->GetFloatArrayInRange(f3a, sizeof(f3a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);

	correctSequence = correctSequence + (sizeof(f1a) / sizeof(float)) + (sizeof(f2a) / sizeof(float)) + (sizeof(f3a) / sizeof(float));

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetFloatArrayInRange!");

	for (size_t i = 0; i < sizeof(f1a) / sizeof(float); i++)
	{
		TASSERT(f1a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f1a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f1a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(f2a) / sizeof(float); i++)
	{
		TASSERT(f2a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f2a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f2a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(f3a) / sizeof(float); i++)
	{
		TASSERT(f3a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f3a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f3a has members out of range!");
	}

	//Now we grab an int in a range
	int i1, i2, i3, i4;

	i1 = generator->GetIntInRange(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i2 = generator->GetIntInRange(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i3 = generator->GetIntInRange(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i4 = generator->GetIntInRange(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);

	correctSequence += 4;

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetIntInRange!");

	TASSERT(i1 >= ZRG_TEST_INT_RANGE_MIN && i1 < ZRG_TEST_INT_RANGE_MAX, "i1 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX)!");
	TASSERT(i2 >= ZRG_TEST_INT_RANGE_MIN && i2 < ZRG_TEST_INT_RANGE_MAX, "i2 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX)!");
	TASSERT(i3 >= ZRG_TEST_INT_RANGE_MIN && i3 < ZRG_TEST_INT_RANGE_MAX, "i3 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX)!");
	TASSERT(i4 >= ZRG_TEST_INT_RANGE_MIN && i4 < ZRG_TEST_INT_RANGE_MAX, "i4 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX)!");

	//Now we grab an int array in range
	int i1a[10], i2a[1000], i3a[100000];

	generator->GetIntArrayInRange(i1a, sizeof(i1a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	generator->GetIntArrayInRange(i2a, sizeof(i2a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	generator->GetIntArrayInRange(i3a, sizeof(i3a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);

	correctSequence = correctSequence + (sizeof(i1a) / sizeof(int)) + (sizeof(i2a) / sizeof(int)) + (sizeof(i3a) / sizeof(int));

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetIntArrayInRange!");

	for (size_t i = 0; i < sizeof(i1a) / sizeof(int); i++)
	{
		TASSERT(i1a[i] >= ZRG_TEST_INT_RANGE_MIN && i1a[i] < ZRG_TEST_INT_RANGE_MAX, "i1a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(i2a) / sizeof(int); i++)
	{
		TASSERT(i2a[i] >= ZRG_TEST_INT_RANGE_MIN && i2a[i] < ZRG_TEST_INT_RANGE_MAX, "i2a has members out of range!");
	}

	for (size_t i = 0; i < sizeof(i3a) / sizeof(int); i++)
	{
		TASSERT(i3a[i] >= ZRG_TEST_INT_RANGE_MIN && i3a[i] < ZRG_TEST_INT_RANGE_MAX, "i3a has member out of range!");
	}

	//Now we grab some gaussian distributed floats
	f1 = generator->GetGaussianFloat(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f2 = generator->GetGaussianFloat(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f3 = generator->GetGaussianFloat(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	f4 = generator->GetGaussianFloat(ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);

	correctSequence += 8; //Two per call

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetGaussianFloat!");

	TASSERT(f1 >= ZRG_TEST_FLOAT_RANGE_MIN && f1 < ZRG_TEST_FLOAT_RANGE_MAX, "f1 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX) for gaussian test!");
	TASSERT(f2 >= ZRG_TEST_FLOAT_RANGE_MIN && f2 < ZRG_TEST_FLOAT_RANGE_MAX, "f2 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX) for gaussian test!");
	TASSERT(f3 >= ZRG_TEST_FLOAT_RANGE_MIN && f3 < ZRG_TEST_FLOAT_RANGE_MAX, "f3 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX) for gaussian test!");
	TASSERT(f4 >= ZRG_TEST_FLOAT_RANGE_MIN && f4 < ZRG_TEST_FLOAT_RANGE_MAX, "f4 is out of range [ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX) for gaussian test!");

	//Now we grab some gaussian distributed floats in an array
	generator->GetGaussianFloatArray(f1a, sizeof(f1a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	generator->GetGaussianFloatArray(f2a, sizeof(f2a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);
	generator->GetGaussianFloatArray(f3a, sizeof(f3a) / sizeof(float), ZRG_TEST_FLOAT_RANGE_MIN, ZRG_TEST_FLOAT_RANGE_MAX);

	correctSequence = correctSequence + 2*(sizeof(f1a) / sizeof(float)) + 2*(sizeof(f2a) / sizeof(float)) + 2*(sizeof(f3a) / sizeof(float));	//Two per call

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetGaussianFloatArray!");

	for (size_t i = 0; i < sizeof(f1a) / sizeof(float); i++)
	{
		TASSERT(f1a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f1a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f1a has members out of range for gaussian test!");
	}

	for (size_t i = 0; i < sizeof(f2a) / sizeof(float); i++)
	{
		TASSERT(f2a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f2a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f2a has members out of range for gaussian test!");
	}

	for (size_t i = 0; i < sizeof(f3a) / sizeof(float); i++)
	{
		TASSERT(f3a[i] >= ZRG_TEST_FLOAT_RANGE_MIN && f3a[i] < ZRG_TEST_FLOAT_RANGE_MAX, "f3a has member out of range for gaussian test!\n");
	}

	//Now we grab some gaussian distributed ints
	i1 = generator->GetGaussianInt(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i2 = generator->GetGaussianInt(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i3 = generator->GetGaussianInt(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	i4 = generator->GetGaussianInt(ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);

	correctSequence += 8; //Two per call

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetGaussianInt!");

	TASSERT(i1 >= ZRG_TEST_INT_RANGE_MIN && i1 < ZRG_TEST_INT_RANGE_MAX, "i1 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX) for gaussian test!");
	TASSERT(i2 >= ZRG_TEST_INT_RANGE_MIN && i2 < ZRG_TEST_INT_RANGE_MAX, "i2 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX) for gaussian test!");
	TASSERT(i3 >= ZRG_TEST_INT_RANGE_MIN && i3 < ZRG_TEST_INT_RANGE_MAX, "i3 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX) for gaussian test!");
	TASSERT(i4 >= ZRG_TEST_INT_RANGE_MIN && i4 < ZRG_TEST_INT_RANGE_MAX, "i4 is out of range [ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX) for gaussian test!");

	//Now we grab some gaussian distributed ints in an array
	generator->GetGaussianIntArray(i1a, sizeof(i1a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	generator->GetGaussianIntArray(i2a, sizeof(i2a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);
	generator->GetGaussianIntArray(i3a, sizeof(i3a) / sizeof(int), ZRG_TEST_INT_RANGE_MIN, ZRG_TEST_INT_RANGE_MAX);

	correctSequence = correctSequence + 2*(sizeof(i1a) / sizeof(int)) + 2*(sizeof(i2a) / sizeof(int)) + 2*(sizeof(i3a) / sizeof(int));

	sequence = generator->GetSequence();

	TASSERT(sequence == correctSequence, 
			"Generator has incorrect sequence number after GetGaussianIntArray!");

	for (size_t i = 0; i < sizeof(i1a) / sizeof(int); i++)
	{
		TASSERT(i1a[i] >= ZRG_TEST_INT_RANGE_MIN && i1a[i] < ZRG_TEST_INT_RANGE_MAX, "i1a has members out of range for guassian test!");
	}

	for (size_t i = 0; i < sizeof(i2a) / sizeof(int); i++)
	{
		TASSERT(i2a[i] >= ZRG_TEST_INT_RANGE_MIN && i2a[i] < ZRG_TEST_INT_RANGE_MAX, "i2a has members out of range for guassian test!");
	}

	for (size_t i = 0; i < sizeof(i3a) / sizeof(int); i++)
	{
		TASSERT(i3a[i] >= ZRG_TEST_INT_RANGE_MIN && i3a[i] < ZRG_TEST_INT_RANGE_MAX, "i3a has member %i (%i) out of range for gaussian test!\n");
	}

	return ZTEST_SUCCESS;
}

static const char* testMersenne()
{
	ZRandomGenerator *generator;

	generator = znew ZRandomGenerator(SST_PRNG_MERSENNE, ZRG_TEST_SEED_1);

	TASSERT(generator->GetSeed() == ZRG_TEST_SEED_1, "Mersenne Generator has incorrect seed !");

	const char* result = testGenerator(generator);

	zdelete generator;

	return result;
}

static const char* testCMWC()
{
	ZRandomGenerator *generator;

	generator = znew ZRandomGenerator(SST_PRNG_CMWC, ZRG_TEST_SEED_1);

	TASSERT(generator->GetSeed() == ZRG_TEST_SEED_1, "CMWC Generator has incorrect seed!");

	const char* result = testGenerator(generator);

	zdelete generator;

	return result;
}

static const char* testSmallPRNG()
{
	ZRandomGenerator *generator;

	generator = znew ZRandomGenerator(SST_PRNG_SMALLPRNG, ZRG_TEST_SEED_1);

	TASSERT(generator->GetSeed() == ZRG_TEST_SEED_1, "Small PRNG Generator has incorrect seed!");

	const char* result = testGenerator(generator);

	zdelete generator;

	return result;
}
