/*
 * Legal Notice
 *
 * This document and associated source code (the "Work") is a part of a
 * benchmark specification maintained by the TPC.
 *
 * The TPC reserves all right, title, and interest to the Work as provided
 * under U.S. and international laws, including without limitation all patent
 * and trademark rights therein.
 *
 * No Warranty
 *
 * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION
 *     CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE
 *     AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER
 *     WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
 *     INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES,
 *     DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR
 *     PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
 *     WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE.
 *     ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT,
 *     QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT
 *     WITH REGARD TO THE WORK.
 * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO
 *     ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE
 *     COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS
 *     OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT,
 *     INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY,
 *     OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT
 *     RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD
 *     ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * Contributors:
 * Gradient Systems
 */
#include "config.h"
#include "init.h"
#include "porting.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <search.h>
#include <limits.h>
#endif
#include "config.h"
#include "porting.h"
#include "decimal.h"
#include "date.h"
#include "genrand.h"
#include "dist.h"
#include "r_params.h"
#include "params.h"

#include "columns.h"
#include "tables.h"
#include "streams.h"

static long Mult = 16807; /* the multiplier */
static long nQ = 127773;  /* the quotient MAXINT / Mult */
static long nR = 2836;    /* the remainder MAXINT % Mult */
void DSNthElement(HUGE_TYPE N, int nStream);

/*
 * Routine: next_random(int stream)
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
long next_random(int stream) {
	long s = Streams[stream].nSeed, div_res, mod_res;

	div_res = s / nQ;
	mod_res = s - nQ * div_res; /* i.e., mod_res = s % nQ */
	s = Mult * mod_res - div_res * nR;
	if (s < 0)
		s += MAXINT;
	Streams[stream].nSeed = s;
	Streams[stream].nUsed += 1;
#ifdef JMS
	Streams[stream].nTotal += 1;
#endif
	return (s);
}

/*
 * Routine: next_random_float(int stream)
 * Purpose:  return random in [0..1]
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
double next_random_float(int stream) {
	long res;

	res = next_random(stream);

	return ((double)res / (double)MAXINT);
}

/*
 * Routine: skip_random(int stream, int skip_count)
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
void skip_random(int nStream, ds_key_t N) {
	ds_key_t Z;
	ds_key_t M;

#ifdef UNDEF
	fprintf(stderr, "skipping stream %d to %d\n", nStream, N);
	Streams[nStream].nTotal = N;
#endif
	M = Mult;
	Z = (ds_key_t)Streams[nStream].nInitialSeed;
	while (N > 0) {
		if (N % 2 != 0) /* testing for oddness, this seems portable */
			Z = (M * Z) % MAXINT;
		N = N / 2; /* integer division, truncates */
		M = (M * M) % MAXINT;
	}
	Streams[nStream].nSeed = (long)Z;

	return;
}

/*
 * Routine: genrand_integer(int dist, int min, int max, int mean)
 * Purpose: generate a random integer given the distribution and limits
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns: int
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 */
int genrand_integer(int *dest, int dist, int min, int max, int mean, int stream) {
	int res = 0, i;
	double fres = 0;

	switch (dist) {
	case DIST_UNIFORM:
		res = next_random(stream);
		res %= max - min + 1;
		res += min;
		break;
	case DIST_EXPONENTIAL:
		for (i = 0; i < 12; i++)
			fres += (double)(next_random(stream) / MAXINT) - 0.5;
		res = min + (int)((max - min + 1) * fres);
		break;
	default:
		INTERNAL("Undefined distribution");
		break;
	}

	if (dest == NULL)
		return (res);

	*dest = res;

	return (0);
}

/*
 * Routine: genrand_key(ket_t *dest, int dist, ds_key_t min, ds_key_t max,
 * ds_key_t mean, int stream) Purpose: generate a random integer given the
 * distribution and limits Algorithm: Data Structures:
 *
 * Params:
 * Returns: ds_key_t
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: Need to rework to rely on RNG routines that will work for 64 bit return
 * values
 */
ds_key_t genrand_key(ds_key_t *dest, int dist, ds_key_t min, ds_key_t max, ds_key_t mean, int stream) {
	int res = 0, i;
	double fres = 0;

	switch (dist) {
	case DIST_UNIFORM:
		res = next_random(stream);
		res %= (int)(max - min + 1);
		res += (int)min;
		break;
	case DIST_EXPONENTIAL:
		for (i = 0; i < 12; i++)
			fres += (double)(next_random(stream) / MAXINT) - 0.5;
		res = (int)min + (int)((max - min + 1) * fres);
		break;
	default:
		INTERNAL("Undefined distribution");
		break;
	}

	if (dest == NULL)
		return ((ds_key_t)res);

	*dest = (ds_key_t)res;

	return ((ds_key_t)0);
}

/*
 * Routine:
 *	genrand_decimal(int dist, decimal_t *min, decimal_t *max, decimal_t *mean)
 * Purpose: create a random decimal_t
 * Algorithm:
 * Data Structures:
 *
 * Params: min/max are char * to allow easy passing of precision
 * Returns: decimal_t *; NULL on failure
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int genrand_decimal(decimal_t *dest, int dist, decimal_t *min, decimal_t *max, decimal_t *mean, int stream) {
	int i;
	decimal_t res;
	double fres = 0.0;

	if (min->precision < max->precision)
		dest->precision = min->precision;
	else
		dest->precision = max->precision;

	switch (dist) {
	case DIST_UNIFORM:
		res.number = next_random(stream);
		res.number %= max->number - min->number + 1;
		res.number += min->number;
		break;
	case DIST_EXPONENTIAL:
		for (i = 0; i < 12; i++) {
			fres /= 2.0;
			fres += (double)((double)next_random(stream) / (double)MAXINT) - 0.5;
		}
		res.number = mean->number + (int)((max->number - min->number + 1) * fres);
		break;
	default:
		INTERNAL("Undefined distribution");
		break;
	}

	dest->number = res.number;
	i = 0;
	while (res.number > 10) {
		res.number /= 10;
		i += 1;
	}
	dest->scale = i;

	return (0);
}

/* Routine: RNGReset(int tbl)
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int RNGReset(int tbl) {
	int i;

	for (i = 0; Streams[i].nColumn != -1; i++)
		if (Streams[i].nTable == tbl)
			Streams[i].nSeed = Streams[i].nInitialSeed;

	return (0);
}

/* WARNING!  This routine assumes the existence of 64-bit                 */

/* integers.  The notation used here- "HUGE" is *not* ANSI standard. */

/* Hopefully, you have this extension as well.  If not, use whatever      */

/* nonstandard trick you need to in order to get 64 bit integers.         */

/* The book says that this will work if MAXINT for the type you choose    */

/* is at least 2**46  - 1, so 64 bits is more than you *really* need      */

static HUGE_TYPE Multiplier = 16807;   /* or whatever nonstandard */
static HUGE_TYPE Modulus = 2147483647; /* trick you use to get 64 bit int */

/* Advances value of Seed after N applications of the random number generator
   with multiplier Mult and given Modulus.
   NthElement(Seed[],count);

   Theory:  We are using a generator of the form
        X_n = [Mult * X_(n-1)]  mod Modulus.    It turns out that
        X_n = [(Mult ** n) X_0] mod Modulus.
   This can be computed using a divide-and-conquer technique, see
   the code below.

   In words, this means that if you want the value of the Seed after n
   applications of the generator,  you multiply the initial value of the
   Seed by the "super multiplier" which is the basic multiplier raised
   to the nth power, and then take mod Modulus.
*/

/* Nth Element of sequence starting with StartSeed */
void DSNthElementNthElement(HUGE_TYPE N, int nStream) {
	HUGE_TYPE Z;
	HUGE_TYPE Mult;

	Mult = Multiplier;
	Z = (HUGE_TYPE)Streams[nStream].nInitialSeed;
	while (N > 0) {
		if (N % 2 != 0) /* testing for oddness, this seems portable */
		{
#ifdef JMS
			Streams[nStream].nTotal += 1;
#endif
			Z = (Mult * Z) % Modulus;
		}
		N = N / 2; /* integer division, truncates */
		Mult = (Mult * Mult) % Modulus;
#ifdef JMS
		Streams[nStream].nTotal += 2;
#endif
	}
	Streams[nStream].nSeed = (long)Z;

	return;
}

/*
 * Routine:
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int dump_seeds_ds(int tbl) {
	int i;

	for (i = 0; Streams[i].nColumn != -1; i++)
		if (Streams[i].nTable == tbl)
			printf("%04d\t%09d\t%09ld\n", i, Streams[i].nUsed, Streams[i].nSeed);
	return (0);
}

/*
 * Routine: gen_charset(char *set, int min, int max)
 * Purpose: generate random characters from set for a random length [min..max]
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int gen_charset(char *dest, char *set, int min, int max, int stream) {
	int len, i, temp;

	if (set == NULL) {
		dest = NULL;
		return (-1);
	}

	genrand_integer(&len, DIST_UNIFORM, min, max, 0, stream);

	for (i = 0; i < max; i++) {
		genrand_integer(&temp, DIST_UNIFORM, 0, strlen(set) - 1, 0, stream);
		if (i < len)
			dest[i] = *(set + temp);
	}
	dest[len] = '\0';

	return (0);
}

/*
 * Routine: genrand_date(int dist, date_t *min, date_t *max)
 * Purpose: generate random date within [min..max]
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int genrand_date(date_t *dest, int dist, date_t *min, date_t *max, date_t *mean, int stream) {
	int range, imean = 0, temp, idt, nYear, nTotalWeight = 0, nDayCount;

	idt = dttoj(min);
	range = dttoj(max);
	range -= idt;
	nDayCount = min->day;
	nYear = min->year;

	switch (dist) {
	case DIST_SALES:
	case DIST_RETURNS:
		/* walk from min to max to "integrate" the distribution */
		while (range -= 1) {
			nTotalWeight += dist_weight(NULL, "calendar", nDayCount, dist + is_leap(nYear));
			if (nDayCount == 365 + is_leap(nYear)) {
				nYear += 1;
				nDayCount = 1;
			} else
				nDayCount += 1;
		}
		/* pick a value in the resulting range */
		temp = genrand_integer(NULL, DIST_UNIFORM, 1, nTotalWeight, 0, stream);
		/* and walk it again to translate that back to a date */
		nDayCount = min->day;
		idt = min->julian;
		nYear = min->year;
		while (temp >= 0) {
			temp -= dist_weight(NULL, "calendar", nDayCount, dist + is_leap(nYear));
			nDayCount += 1;
			idt += 1;
			if (nDayCount > 365 + is_leap(nYear)) {
				nYear += 1;
				nDayCount = 1;
			}
		}
		break;
	case DIST_EXPONENTIAL:
		imean = dttoj(mean);
		imean -= idt;
	case DIST_UNIFORM:
		genrand_integer(&temp, dist, 0, range, imean, stream);
		idt += temp;
		break;
	default:
		break;
	}

	jtodt(dest, idt);

	return (0);
}

/**************
 **************
 **
 ** static routines
 **
 **************
 **************/

/*
 * Routine: init_rand()
 * Purpose: Initialize the RNG used throughout the code
 * Algorithm: To allow two columns to use the same stream of numbers (for
 *joins), pre-sort the streams list by Duplicate and then assign values. Order
 *by column after initialization Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO:
 */
// FIXME: allow re-init
void init_rand(void) {
	long long i, skip, nSeed; // changed to long long from int

	if (!InitConstants::init_rand_init) {
		if (is_set("RNGSEED"))
			nSeed = get_int("RNGSEED");
		else
			nSeed = RNG_SEED;
		skip = MAXINT / MAX_COLUMN;
		for (i = 0; i < MAX_COLUMN; i++) {
            // simulate the overflow as if it were an int
            if (i != 0 && (INT_MAX - nSeed) / i < skip) {
                long long val = nSeed + skip * i;
                val %= MAXINT;
                val -= MAXINT;
                val -= 2;
                Streams[i].nInitialSeed = val;
                Streams[i].nSeed = val;
            } else {
                Streams[i].nInitialSeed = nSeed + skip * i;
                Streams[i].nSeed = nSeed + skip * i;
            }
			    Streams[i].nUsed = 0;
		}
		InitConstants::init_rand_init = 1;
	}
	return;
}

void resetSeeds(int nTable) {
	int i;

	for (i = 0; i < MAX_COLUMN; i++)
		if (Streams[i].nTable == nTable)
			Streams[i].nSeed = Streams[i].nInitialSeed;
	return;
}

/*
 * Routine:
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
void genrand_email(char *pEmail, char *pFirst, char *pLast, int nColumn) {
	char *pDomain;
	char szCompany[50];
	int nCompanyLength;

	pick_distribution(&pDomain, "top_domains", 1, 1, nColumn);
	genrand_integer(&nCompanyLength, DIST_UNIFORM, 10, 20, 0, nColumn);
	gen_charset(&szCompany[0], ALPHANUM, 1, 20, nColumn);
	szCompany[nCompanyLength] = '\0';

	sprintf(pEmail, "%s.%s@%s.%s", pFirst, pLast, szCompany, pDomain);

	return;
}

/*
 * Routine:
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
void genrand_ipaddr(char *pDest, int nColumn) {
	int arQuads[4], i;

	for (i = 0; i < 4; i++)
		genrand_integer(&arQuads[i], DIST_UNIFORM, 1, 255, 0, nColumn);
	sprintf(pDest, "%03d.%03d.%03d.%03d", arQuads[0], arQuads[1], arQuads[2], arQuads[3]);

	return;
}

/*
 * Routine:
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int genrand_url(char *pDest, int nColumn) {
	strcpy(pDest, "http://www.foo.com");

	return (0);
}

/*
 * Routine:
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int setSeed(int nStream, int nValue) {
	int nRetValue;

	nRetValue = Streams[nStream].nSeed;
	Streams[nStream].nSeed = nValue;

	return (nRetValue);
}

#ifdef TEST
main() {
	printf("r_genrand:No test routine has been defined for this module\n");

	exit(0);
}
#endif /* TEST */
