/*
 * Copyright (c) 2004-2005 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagrandom.c 1262 2005-03-15 04:00:04Z abel $
 */
 
#include <math.h>
#include <stdlib.h>
#include "dagrandom.h"

unsigned int getDistrNumber (distribution_t * distr) {
	unsigned int res = 0;

	switch (distr->type) {
		case _CONSTANT:
			res = distr->x;
			break;
		case _NORMAL:
			res = distr_normal (distr);
			break;
		case _UNIFORM:
			res = distr_uniform (distr);
			break;
		case _POISSON:
			res = distr_poisson (distr);
			break;
		case _PATTERN_ROUND:
			res = distr_pattern_round (distr);
			break;
		case _PATTERN_BOUNCE:
			res = distr_pattern_bounce (distr);
			break;
	}

	return res;
}

distribution_t * distrNew (unsigned char type, unsigned int x, unsigned int y,
	unsigned int z, distribution_t * nested_distr) {
		
	distribution_t * distr;
	
	distr = (distribution_t *) malloc (sizeof(distribution_t));
	distr->type = type;
	distr->x = x;
	distr->y = y;
	distr->z = z;
	distr->next = x;
	if (x < y) distr->direction = 1;
	else distr->direction = -1;
	distr->distr = nested_distr;
	
	//printf ("[distrNew] type: %d, x: %d, y: %d, z: %d, distr: %x, distr->distr: %x\n",
	//	distr->type, distr->x, distr->y, distr->z, distr, distr->distr);
	
	return distr;
}

void distrFree (distribution_t * distr) {
	if (distr != NULL) {
		if (distr->distr != NULL) {
			distrFree(distr->distr);
		}
		free(distr);
	}
}

// Polar method for generating normal random variables
int distr_normal (distribution_t * distr) {
	double rnd1, rnd2;
	double rnd3, rnd4;
	
	do {
		// random numbers between 0 and RAND_MAX
#ifndef _WIN32
		rnd1 = (double) random();
		rnd2 = (double) random();
#else /* _WIN32 */
		rnd1 = (double) rand();
		rnd2 = (double) rand();
#endif /* _WIN32 */

		// random numbers between 0 and 1
		rnd1 /= RAND_MAX;
		rnd2 /= RAND_MAX;
		
		//printf ("rnd1: %f, rnd2: %f\n", rnd1, rnd2);
		
		// re-escale to -1 .. 1
		rnd1 = (rnd1 * 2.0) - 1;	// can we use '<<' ?
		rnd2 = (rnd2 * 2.0) - 1;
		rnd3 = rnd1*rnd1 + rnd2*rnd2;
	
	} while (rnd3 > 1.0);
	
	// we can now re-use rnd1 and rnd2
	rnd4 = sqrt((-2.0 * log(rnd3)) / rnd3) * rnd1;
	// don't need the second number
	//rnd2 = sqrt(-2 * log(rnd3) / rnd3) * rnd2;
	//printf ("rnd4: %f\n", rnd4);
	// rnd1 is a normal random number (and rnd2 too)
	
	// Multiply per deviation, sum mean
	rnd4 *= (double) distr->y;
	rnd4 += (double) distr->x;
		
	//printf ("normal(%d,%d): %d, (%d)\n", distr->x, distr->y, (int) rnd4, ((int)rnd4>distr->x)?(int)rnd4-distr->x:distr->x-(int)rnd4 );
	return (int) rnd4;
}

int distr_uniform (distribution_t * distr) {
	double rnd;

#ifndef _WIN32
	rnd = random();		// random number between 0 and RAND_MAX
#else /* _WIN32 */
	rnd = rand();		// random number between 0 and RAND_MAX
#endif /* _WIN32 */
	
	/* sanity check */
	if (rnd == RAND_MAX)
		rnd--;
		
	rnd /= RAND_MAX;	// random number between 0 and 1
		
	if (distr->x < distr->y)
		rnd = (distr->y - distr->x + 1) * rnd + distr->x;
	else
		rnd = (distr->x - distr->y + 1) * rnd + distr->y;
	
	//printf ("uniform: %d\n", (int) rnd);
	
	return (int)rnd;
}

// not really interesting
int distr_poisson (distribution_t * distr) {
	return distr->x;
}

int distr_pattern_round (distribution_t * distr) {
	int step;
	int last;
	
	last = distr->next; // return last, calculate next
	
	if (distr->z) step = 1;
	else step = getDistrNumber (distr->distr);
	
	distr->next += step * distr->direction;
	
	if (distr->x < distr->y) {
		if (distr->next > distr->y) distr->next = distr->x;
	} else {
		if (distr->next < distr->y) distr->next = distr->x;
	}
	
	//printf ("pattern_round: %d\n", last);

	return last;
}

int distr_pattern_bounce (distribution_t * distr) {
	int step;
	int last;
	
	last = distr->next;
	
	if (distr->z) step = 1;
	else step = getDistrNumber (distr->distr);
	
	distr->next += step * distr->direction;
	
	if (distr->x < distr->y) {
		if (distr->next < distr->x) {
			distr->next = distr->x;
			distr->direction *= -1;
		}
		
		if (distr->next > distr->y) {
			distr->next = distr->y;
			distr->direction *= -1;
		}
	} else {
		if (distr->next > distr->x) {
			distr->next = distr->x;
			distr->direction *= -1;
		}
		
		if (distr->next < distr->y) {
			distr->next = distr->y;
			distr->direction *= -1;
		}
	}

	//printf ("pattern_bounce: %d\n", last);
	
	return last;
}

unsigned int randomNibble () {
	unsigned int nibble;
	double aux;

#ifndef _WIN32
	aux = random();		// random number between 0 and RAND_MAX
#else /* _WIN32 */
	aux = rand();		// random number between 0 and RAND_MAX
#endif /* _WIN32 */
	nibble = (int) aux & 0x0f;
	//if (nibble = 16) nibble = 0;
	
	return nibble;
}

void randomInit (unsigned long seed) {
#ifndef _WIN32
	srandom(seed);
#else /* _WIN32 */
	srand(seed);
#endif /* _WIN32 */
}


