#include <../source/TAL/objbase.h>

/*
 * Cut-and-hack from NR
 */

/*
 * Long period (> 2*10^18) random number generator of L'Ecuyer with
 * Bays-Durham shuffle and added safeguards. Returns a uniform random
 * deviate between 0.0 and 1.0 (exclusive of the endpoint values).
 * Call with idum a negative integer to initialize; thereafter, do not
 * alter idum between successive deviates in a sequence. RNMX should
 * approximate the largest floating value that is less than 1.
 */
#define NTAB 32
#define IM1 2147483563
#define IM2 2147483399
#define AM (1.0/IM1)
#define IMM1 (IM1-1)
#define IA1 40014
#define IA2 40692
#define IQ1 53668
#define IQ2 52774
#define IR1 12211
#define IR2 3791
#define NDIV (1+IMM1/NTAB)
#define EPS 1.2e-9
#define RNMX (1.0-EPS)

#ifdef	CONFIG_FAST_RGEN

typedef struct ran_context_s
{
	int idum;
	long idum2;
	long iy;
	long iv[NTAB];
} ran_context_t, *ran_context_p;

#define	BAD_RGEN	NULL

DEBUG_MESSAGE(wrong_handle,"NULL handle passed")

#define	GET_RGEN(dest,handle) \
	dest = (ran_context_p)gen; \
	if(!dest) { \
		ERROR_GENERIC(wrong_handle); \
	}

#else	/* !CONFIG_FAST_RGEN */

typedef struct ran_context_s
{
	libv_object_t	base;
	int idum;
	long idum2;
	long iy;
	long iv[NTAB];
} ran_context_t, *ran_context_p;

static char *rgen_name = "NR ran2() wrapper";
#define	RGEN_MAGIC	0x5247454E /* RGEN */

#define	BAD_RGEN	LIBV_INVALID_HANDLE

#define	GET_RGEN(dest,handle) \
	dest = libvIsHandle(handle,caller_name,rgen_name,RGEN_MAGIC);

V_LOCAL u32_t V_CALL
rgen_destroy(vhandle_t handle)
{
	ran_context_p self=(ran_context_p)handle;
	if(--self->base.ref_count)
		return self->base.ref_count;
	memFreeObject(self);
	return 0;
}

#endif	/* !CONFIG_FAST_RGEN */

DEBUG_MESSAGE(ran_incorrect_parameters,"incorrect parameters")

vhandle_t V_GLOBAL
ranCreateGenerator(int seed)
{
	int j,k;
#ifdef	CONFIG_FAST_RGEN
	ran_context_p ctx = (ran_context_p)memAlloc(sizeof(ran_context_t));
#else
	ran_context_p ctx;
	ALLOC_OBJECT(ctx,ran_context_t,0,RGEN_MAGIC);
	ctx->base.destroy = rgen_destroy;
#endif
	if (seed < 1)
		ctx->idum = 1;
	else
		ctx->idum = seed;
	ctx->idum2 = ctx->idum;
	for (j=NTAB+7;j>=0;j--)
	{
		k = (ctx->idum)/IQ1;
		ctx->idum=IA1*(ctx->idum-k*IQ1)-k*IR1;
		if (ctx->idum < 0)
			ctx->idum += IM1;
		if (j < NTAB)
			ctx->iv[j] = ctx->idum;
	}
	ctx->iy = ctx->iv[0];
	return (vhandle_t)ctx;
}

/*
 * Returns uniform-deviated integer random number in range [0..upper_bound-1]
 */
long V_GLOBAL
ranNextLong(vhandle_t gen,long upper_bound)
{
	CALLER_NAME(ranNextLong)
	int j;
	long k;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return 0;
	k = (ctx->idum)/IQ1;
	ctx->idum = IA1*(ctx->idum-k*IQ1)-k*IR1;
	if (ctx->idum < 0)
		ctx->idum += IM1;
	k = ctx->idum2/IQ2;
	ctx->idum2 = IA2*(ctx->idum2-k*IQ2)-k*IR2;
	if (ctx->idum2 < 0)
		ctx->idum2 += IM2;
	j = ctx->iy/NDIV;
	ctx->iy = ctx->iv[j]-ctx->idum2;
	ctx->iv[j] = ctx->idum;
	if (ctx->iy < 1)
		ctx->iy += IMM1;
	return ctx->iy % upper_bound;
}

/*
 * Returns uniform-deviated float random number in range [0..1)
 */
V_LOCAL vec_t
urn_next_float(ran_context_p ctx)
{
	long k, j;
	vec_t temp;
	k = (ctx->idum)/IQ1;
	ctx->idum = IA1*(ctx->idum-k*IQ1)-k*IR1;
	if (ctx->idum < 0)
		ctx->idum += IM1;
	k = ctx->idum2/IQ2;
	ctx->idum2 = IA2*(ctx->idum2-k*IQ2)-k*IR2;
	if (ctx->idum2 < 0)
		ctx->idum2 += IM2;
	j = ctx->iy/NDIV;
	ctx->iy = ctx->iv[j]-ctx->idum2;
	ctx->iv[j] = ctx->idum;
	if (ctx->iy < 1)
		ctx->iy += IMM1;
	temp = AM * ctx->iy;
	if (temp > RNMX)
		return RNMX;
	else
		return temp;
}

/*
 * Returns uniform-deviated float random number in range [0..1)
 */
vec_t V_GLOBAL
ranNextFloat(vhandle_t gen)
{
	CALLER_NAME(ranNextFloat)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return 0;
	return urn_next_float(ctx);
}

vec_t V_GLOBAL
ranNextFloatRange(vhandle_t gen,vec_t min,vec_t max)
{
	CALLER_NAME(ranNextFloatRange)
	vec_t r;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return 0;
	r = urn_next_float(ctx);
	return min + r * (max - min);
}

V_LOCAL void
urn_next_vector_range(ran_context_p ctx,u32_t ndims,
	vec_t ret[],vec_t min,vec_t max)
{
	u32_t i;
	vec_t r, delta = max - min;
	for(i=0;i<ndims;i++)
	{
		r = urn_next_float(ctx);
		ret[i] = min + r * delta;
	}
}

V_LOCAL void
urn_next_vector_range_v(ran_context_p ctx,u32_t ndims,
	vec_t ret[],vec_t mins[],vec_t maxs[])
{
	u32_t i;
	vec_t r;
	for(i=0;i<ndims;i++)
	{
		r = urn_next_float(ctx);
		ret[i] = mins[i] + r * (maxs[i] - mins[i]);
	}
}

void V_GLOBAL
ranNextVec3Range(vhandle_t gen,vec3_t vec3_ret,vec_t min,vec_t max)
{
	CALLER_NAME(ranNextVec3Range)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec3_ret)
	{
		ERROR_GENERIC(ran_incorrect_parameters);
		return;
	}
	urn_next_vector_range(ctx,3,vec3_ret,min,max);
}

void V_GLOBAL
ranNextVec3RangeV(vhandle_t gen,vec3_t vec3_ret,vec3_t mins,vec3_t maxs)
{
	CALLER_NAME(ranNextVec3RangeV)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec3_ret || !mins || !maxs)
	{
		ERROR_GENERIC(ran_incorrect_parameters);
		return;
	}
	urn_next_vector_range_v(ctx,3,vec3_ret,mins,maxs);
}

void	V_GLOBAL
ranNextVectorRange(vhandle_t gen,vector_p vec_ret,vec_t min,vec_t max)
{
	CALLER_NAME(ranNextVectorRange)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec_ret)
	{
		ERROR_GENERIC(ran_incorrect_parameters)
		return;
	}
	urn_next_vector_range(ctx,vec_ret->size,vec_ret->elements,min,max);
}

void	V_GLOBAL
ranNextVectorRangeV(vhandle_t gen,
	vector_p vec_ret,vector_p mins,vector_p maxs)
{
	CALLER_NAME(ranNextVectorRangeV)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec_ret || !mins || !maxs 
		|| vec_ret->size != mins->size || vec_ret->size != maxs->size)
	{
		ERROR_GENERIC(ran_incorrect_parameters)
		return;
	}
	urn_next_vector_range_v(ctx,vec_ret->size,vec_ret->elements,
		mins->elements,maxs->elements);
}

void V_GLOBAL
ranDestroyGenerator(vhandle_t gen)
{
#ifdef	CONFIG_FAST_RGEN
	if(gen)
		memFree(gen);
#else
	CALLER_NAME(ranDestroyGenerator)
	libvCloseHandle(gen,caller_name,rgen_name,RGEN_MAGIC);
#endif
}

V_LOCAL	void
ran_gaussian_pair(ran_context_p ctx,vec_p pair_ret)
{
	vec_t fac, rsq, v1, v2;
	for(;;)
	{
		v1 = urn_next_float(ctx) * 2 - 1;
		v2 = urn_next_float(ctx) * 2 - 1;
		rsq = v1 * v1 + v2 * v2;
		if(IsZero(rsq) || (rsq >= 1))
			continue;
		fac = sqrt(-2 * log(rsq) / rsq);
		pair_ret[0] = v1 * fac;
		pair_ret[1] = v2 * fac;
		break;
	}
}

vec_t	V_GLOBAL
ranNextGaussian(vhandle_t gen,vec_t mean,vec_t rms)
{
	CALLER_NAME(ranNextGaussian)
	vec2_t pair;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return 0;
	ran_gaussian_pair(ctx,pair);
	return mean + rms * pair[0];
}

void	V_GLOBAL
ranNextGaussianVec3(vhandle_t gen,vec3_t vec3_ret,vec_t mean,vec_t rms)
{
	CALLER_NAME(ranNextGaussianVec3)
	vec2_t pair;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec3_ret)
	{
		ERROR_GENERIC(ran_incorrect_parameters);
		return;
	}
	ran_gaussian_pair(ctx,pair);
	vec3_ret[0] = mean + rms * pair[0];
	vec3_ret[1] = mean + rms * pair[1];
	ran_gaussian_pair(ctx,pair);
	vec3_ret[2] = mean + rms * pair[0];
}

void	V_GLOBAL
ranNextGaussianVec3V(vhandle_t gen,vec3_t vec3_ret,vec3_t mean,vec3_t rms)
{
	CALLER_NAME(ranNextGaussianVec3V)
	vec2_t pair;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec3_ret || !mean || !rms)
	{
		ERROR_GENERIC(ran_incorrect_parameters);
		return;
	}
	ran_gaussian_pair(ctx,pair);
	vec3_ret[0] = mean[0] + rms[0] * pair[0];
	vec3_ret[1] = mean[1] + rms[1] * pair[1];
	ran_gaussian_pair(ctx,pair);
	vec3_ret[2] = mean[2] + rms[2] * pair[0];
}

void	V_GLOBAL
ranNextGaussianVector(vhandle_t gen,vector_p vec_ret,vec_t mean,vec_t rms)
{
	CALLER_NAME(ranNextGaussianVector)
	u32_t niter, rest;
	vec2_t pair;
	vec_p ptr;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec_ret)
	{
		ERROR_GENERIC(ran_incorrect_parameters)
		return;
	}
	ptr = vec_ret->elements;
	niter = vec_ret->size / 2;
	rest = vec_ret->size % 2;
	while(niter--)
	{
		ran_gaussian_pair(ctx,pair);
		*ptr++ = mean + rms * pair[0];
		*ptr++ = mean + rms * pair[1];
	}
	if(rest)
	{
		ran_gaussian_pair(ctx,pair);
		*ptr = mean + rms * pair[0];
	}
}

void	V_GLOBAL
ranNextGaussianVectorV(vhandle_t gen,vector_p vec_ret,vector_p mean,vector_p rms)
{
	CALLER_NAME(ranNextGaussianVectorV)
	u32_t niter, rest;
	vec2_t pair;
	vec_p ptr, mptr, rptr;
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == BAD_RGEN)
		return;
	if(!vec_ret || !mean || !rms
		|| vec_ret->size != mean->size || vec_ret->size != rms->size)
	{
		ERROR_GENERIC(ran_incorrect_parameters)
		return;
	}
	ptr = vec_ret->elements;
	mptr = mean->elements;
	rptr = rms->elements;
	niter = vec_ret->size / 2;
	rest = vec_ret->size % 2;
	while(niter--)
	{
		ran_gaussian_pair(ctx,pair);
		*ptr++ = *mptr++ + *rptr++ * pair[0];
		*ptr++ = *mptr++ + *rptr++ * pair[1];
	}
	if(rest)
	{
		ran_gaussian_pair(ctx,pair);
		*ptr++ = *mptr++ + *rptr++ * pair[0];
	}
}
