#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 endpos32_t values).
 * Call with idum a negative s32_teger 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 IM1 2147483563
#define IM2 2147483399
#define AM ((vec_t)(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/RAN_NTAB)
#define EPS ((vec_t)1.2e-9)
#define RNMX ((vec_t)(1.0-EPS))

/*
 * Low-level stuff
 */
void	V_GLOBAL
ranInitState(ran_state_p state,s32_t seed)
{
	s32_t j, k;
	if(!seed)
		seed = -1;
	else if(seed > 0)
		seed = -seed;
	state->idum = seed;
	state->idum2 = state->idum;
	for(j = RAN_NTAB + 7;j >= 0;j--)
	{
		k = (state->idum) / IQ1;
		state->idum = IA1 * (state->idum - k * IQ1) - k * IR1;
		if (state->idum < 0)
			state->idum += IM1;
		if (j < RAN_NTAB)
			state->iv[j] = state->idum;
	}
	state->iy = state->iv[0];
}

/*
 * Returns uniform-deviated integer random number in range [0..2147483563]
 */
s32_t	V_GLOBAL
ranGenerateNext(ran_state_p state)
{
	s32_t j;
	s32_t k;
	k = (state->idum) / IQ1;
	state->idum = IA1 * (state->idum - k*IQ1) - k*IR1;
	if(state->idum < 0)
		state->idum += IM1;
	k = state->idum2 / IQ2;
	state->idum2 = IA2 * (state->idum2 - k*IQ2) - k*IR2;
	if(state->idum2 < 0)
		state->idum2 += IM2;
	j = state->iy / NDIV;
	state->iy = state->iv[j] - state->idum2;
	state->iv[j] = state->idum;
	if(state->iy < 1)
		state->iy += IMM1;
	return state->iy;
}

/*
 * Wrapper object
 */

typedef struct ran_context_s
{
	libv_object_t	base;
	ran_state_t		state;
} ran_context_t, *ran_context_p;

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

#define	GET_RGEN(dest,handle) \
	dest = (ran_context_p)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;
	UNREF_EXIT(self);
	memFreeObject(self);
	return 0;
}

vhandle_t V_GLOBAL
ranCreateGenerator(s32_t seed)
{
	ran_context_p ctx;
	ALLOC_OBJECT(ctx,ran_context_t,0,RGEN_MAGIC);
	ctx->base.destroy = rgen_destroy;
	ranInitState(&ctx->state,seed);
	return (vhandle_t)ctx;
}

void V_GLOBAL
ranDestroyGenerator(vhandle_t gen)
{
	CALLER_NAME(ranDestroyGenerator)
	libvCloseHandle(gen,caller_name,rgen_name,RGEN_MAGIC);
}

/*
 * Returns uniform-deviated integer random number in range [0..2147483563]
 */
s32_t V_GLOBAL
ranNextLong(vhandle_t gen)
{
	CALLER_NAME(ranNextLong)
	ran_context_p ctx;
	GET_RGEN(ctx,gen);
	if(ctx == LIBV_INVALID_HANDLE)
		return 0;
	return ranGenerateNext(&ctx->state);
}

/*
 * Returns uniform-deviated float random number in range [0..1)
 */
V_LOCAL vec_t
ran_next_float(ran_context_p ctx)
{
	vec_t temp;
	temp = AM * ranGenerateNext(&ctx->state);
	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 == LIBV_INVALID_HANDLE)
		return 0;
	return ran_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 == LIBV_INVALID_HANDLE)
		return 0;
	r = ran_next_float(ctx);
	return min + r * (max - min);
}

V_LOCAL void
ran_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 = ran_next_float(ctx);
		ret[i] = min + r * delta;
	}
}

V_LOCAL void
ran_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 = ran_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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec3_ret)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	ran_next_vector_range(ctx,3,vec3_ret,min,max);
}

/*
 * Returns random unit vector
 */
void V_GLOBAL
ranDirection(vhandle_t gen,vec3_t dest)
{
	CALLER_NAME(ranNextVec3Range)
	vec3_t nv;
	vec_t len;
	ran_context_p ctx;
#define MINDLEN (vec_t)0.8
#define MAXDLEN (vec_t)1
	static vec_t minsq = MINDLEN * MINDLEN, maxsq = MAXDLEN * MAXDLEN;
	GET_RGEN(ctx, gen);
	if(ctx == LIBV_INVALID_HANDLE)
		return;
	do
	{ 
		ran_next_vector_range(ctx, 3, nv, -MAXDLEN, MAXDLEN);
		len = vec3Dot(nv, nv);
	} while(len <= minsq || len >= maxsq);
	/*
	 * Endpoint falls between spheres of radius MINLEN and MAXLEN
	 */
	/* vec3Unit(dest, nv); */
	len = 1 / (vec_t)sqrt(len);
	vec3Scale(dest, nv, len);
#undef MINDLEN
#undef MAXDLEN
}


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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec3_ret || !mins || !maxs)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	ran_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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec_ret)
	{
		ERROR_GENERIC(error_incorrect_parameters)
		return;
	}
	ran_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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec_ret || !mins || !maxs 
		|| vec_ret->size != mins->size || vec_ret->size != maxs->size)
	{
		ERROR_GENERIC(error_incorrect_parameters)
		return;
	}
	ran_next_vector_range_v(ctx,vec_ret->size,vec_ret->elements,
		mins->elements,maxs->elements);
}

V_LOCAL	void
ran_gaussian_pair(ran_context_p ctx,vec_p pair_ret)
{
	vec_t fac, rsq, v1, v2;
	for(;;)
	{
		v1 = ran_next_float(ctx) * 2 - 1;
		v2 = ran_next_float(ctx) * 2 - 1;
		rsq = v1 * v1 + v2 * v2;
		if(IsZero(rsq) || (rsq >= 1))
			continue;
		fac = (vec_t)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 == LIBV_INVALID_HANDLE)
		return 0;
	ran_gaussian_pair(ctx,pair);
	return mean + rms * pair[0];
}

void V_GLOBAL
ranNextGaussianVec2(vhandle_t gen,vec2_t vec2_ret,vec_t mean,vec_t rms)
{
	CALLER_NAME(ranNextGaussianVec2)
	vec2_t pair;
	ran_context_p ctx;
	GET_RGEN(ctx, gen);
	if(ctx == LIBV_INVALID_HANDLE)
		return;
	if(!vec2_ret)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	ran_gaussian_pair(ctx, pair);
	vec2_ret[0] = mean + rms * pair[0];
	vec2_ret[1] = mean + rms * pair[1];
}

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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec3_ret)
	{
		ERROR_GENERIC(error_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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec3_ret || !mean || !rms)
	{
		ERROR_GENERIC(error_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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec_ret)
	{
		ERROR_GENERIC(error_incorrect_parameters)
		return;
	}
	ptr = vec_ret->elements;
	niter = vec_ret->size >> 1;
	rest = vec_ret->size & 1;
	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 == LIBV_INVALID_HANDLE)
		return;
	if(!vec_ret || !mean || !rms
		|| vec_ret->size != mean->size || vec_ret->size != rms->size)
	{
		ERROR_GENERIC(error_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];
	}
}
