#include <ZUtil/ZRandomGenerator.hpp>

#include <ZUtil/ZAssert.hpp>

#include <math.h>		/* sine and other functions */
#include <time.h>		/* time function */

//Helper Function that returns a float distributed normally between a float min and max using random numbers r1, r2
float getGaussianHelper(float _min, float _max, float _r1, float _r2)
{
	float deltamid, delta;

	deltamid = (float)(((_max - _min) / 2) * sin(_r1 * 3.14159f));	/* absolute delta from middle value */
	delta = _max - _min - (2 * deltamid);							/* calculate the actual delta */

	return (_min + deltamid + (_r2 * delta));
}

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

ZRandomGenerator::ZRandomGenerator()
: Type(SST_PRNG_SMALLPRNG), Seed((uint32_t)time(0)), Sequence(0), RNG(NULL)
{
	RNG = SST_Random_CreatePRNGFromSeed(Type, Seed);

	ZASSERT_RUNTIME(RNG != NULL, "ZRandomGenerator: Failure to initialize SST_PRNG instance!");
}

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

ZRandomGenerator::ZRandomGenerator(SST_PRNG_TYPE _type, uint32_t _seed, uint64_t _sequence )
: Type(_type), Seed(_seed), Sequence(_sequence), RNG(SST_Random_CreatePRNGFromSeed(_type, _seed))
{
	ZASSERT_RUNTIME(RNG != NULL, "ZRandomGenerator: Failure to initialize SST_PRNG instance!");

	//Generate and discard a number of times equal to Sequence 
	for (uint64_t i = 0; i < Sequence; i++)
		SST_Random_GetPRNGInt(RNG, 0, 1);
}

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

ZRandomGenerator::~ZRandomGenerator()
{
	if (RNG != NULL)
		SST_Random_DestroyPRNG(RNG);
}

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

float ZRandomGenerator::GetFloat()
{
	++Sequence;

	return SST_Random_GetPRNGFloat(RNG, 0, 1);
}

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

void ZRandomGenerator::GetFloatArray( float *_array, size_t _count )
{
	Sequence += _count;

	return SST_Random_GetPRNGFloatArray(RNG, _array, _count, 0, 1);
}

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

float ZRandomGenerator::GetFloatInRange( float _min, float _max )
{
	++Sequence;

	return SST_Random_GetPRNGFloat(RNG, _min, _max);
}

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

void ZRandomGenerator::GetFloatArrayInRange( float *_array, size_t _count, float _min, float _max )
{
	Sequence += _count;

	return SST_Random_GetPRNGFloatArray(RNG, _array, _count, _min, _max);
}

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

float ZRandomGenerator::GetGaussianFloat( float _min, float _max )
{
	float r1, r2;

	r1 = GetFloat();
	r2 = GetFloat();

	return getGaussianHelper(_min, _max, r1, r2);
}

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

void ZRandomGenerator::GetGaussianFloatArray( float *_array, size_t _count, float _min, float _max )
{
	size_t i;
	float r1, r2;

	for (i = 0; i < _count; ++i)
	{
		r1 = GetFloat();
		r2 = GetFloat();

		_array[i] = getGaussianHelper(_min, _max, r1, r2);
	}
}

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

int ZRandomGenerator::GetGaussianInt( int _min, int _max )
{
	return (int)GetGaussianFloat((float)_min + 0.5f, (float)_max + 0.5f);
}

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

void ZRandomGenerator::GetGaussianIntArray( int *_array, size_t _count, int _min, int _max )
{
	size_t i;
	float r1, r2;

	for (i = 0; i < _count; ++i)
	{
		r1 = GetFloat();
		r2 = GetFloat();

		_array[i] = (int)getGaussianHelper((float)_min + 0.5f, (float)_max + 0.5f, r1, r2);
	}
}

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

int ZRandomGenerator::GetInt()
{
	++Sequence;

	return SST_Random_GetPRNGInt(RNG, STDINT_MIN, STDINT_MAX);
}

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

void ZRandomGenerator::GetIntArray( int *_array, size_t _count )
{
	Sequence += _count;

	return SST_Random_GetPRNGIntArray(RNG, _array, _count, STDINT_MIN, STDINT_MAX);
}

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

int ZRandomGenerator::GetIntInRange( int _min, int _max )
{
	++Sequence;

	return SST_Random_GetPRNGInt(RNG, _min, _max);
}

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

void ZRandomGenerator::GetIntArrayInRange( int *_array, size_t _count, int _min, int _max )
{
	Sequence += _count;

	return SST_Random_GetPRNGIntArray(RNG, _array, _count, _min, _max);
}

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

uint32_t ZRandomGenerator::GetSeed()
{
	return Seed;
}

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

uint64_t ZRandomGenerator::GetSequence()
{
	return Sequence;
}

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

void ZRandomGenerator::SetSequence( uint64_t _seq )
{
	//If less than, new instance
	if (_seq < Sequence)
		SetSeed(Seed);

	while (Sequence < _seq)
	{
		SST_Random_GetPRNGInt(RNG, 0, 1);
		++Sequence;
	}
}

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

void ZRandomGenerator::SetSeed( uint32_t _seed )
{
	if (RNG != NULL) SST_Random_DestroyPRNG(RNG);

	Seed = _seed;
	Sequence = 0;

	RNG = SST_Random_CreatePRNGFromSeed(Type, Seed);

	ZASSERT_RUNTIME(RNG != NULL, "ZRandomGenerator: Failure to initialize SST_PRNG_Instance!");
}
