// Copyright 1997-1999 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OFRandom.h"

#import <Foundation/Foundation.h>
#import <OmniBase/assertions.h>

/* This is an nonlinear congruential generator, see
   http://random.mat.sbg.ac.at/ftp/pub/publications/weinga/diplom/
   for a complete discussion of these.  Presumably, this one would
   be termed an inverse nonlinear congruential generator since it
   uses inverses modulo M.

   Discussion of the choices of the constants can be seen at 
   http://random.mat.sbg.ac.at/ftp/pub/publications/peter/imp/
*/

#define MERSENNE_PRIME_31 (2147483647)  /* 2^31 - 1 */

/* These are the 13th set of constants from pg 19 of the IMP paper above */
#define IMP_A             (16643900)
#define IMP_B             (480227068)

/* Uses Euclids algorithm to find x such that ax = 1 mod p where p is prime */
static inline unsigned long _invert(unsigned long a, unsigned long p)
{
    unsigned long               q, d;
    long                        u, v, inv, t;

    OBPRECONDITION(a && a != 1);

    d = p;
    inv = 0;
    v = 1;
    u = a;

    do {
	q = d / u;
	t = d % u;
	d = u;
	u = t;
	t = inv - q * v;
	inv = v;
	v = t;
    } while (u != 0);

    if (inv < 0)
	inv += p;

    if (d != 1) {
	/* This should never happen, really */
	fprintf(stderr, "Cannot invert %ld mod p", a);
    }

    OBPOSTCONDITION(inv && inv != 1);
    return (inv);
}

/* Modular Multiplication: Decomposition method (from L'Ecuyer & Cote) */
static inline unsigned long _mult_mod(unsigned long a, unsigned long s, unsigned long m)
{
    long                        H, a0, a1, q, qh, rh, k, p;


    H = 32768;			/* 2 ^ 15  for 32 bit basetypes. */

    if (a < H) {
	a0 = a;
	p = 0;
    } else {
	a1 = a / H;
	a0 = a - H * a1;
	qh = m / H;
	rh = m - H * qh;
	if (a1 >= H) {
	    a1 = a1 - H;
	    k = s / qh;
	    p = H * (s - k * qh) - k * rh;
	    while (p < 0)
		p += m;
	} else
	    p = 0;

	if (a1 != 0) {
	    q = m / a1;
	    k = s / q;
	    p = p - k * (m - a1 * q);
	    if (p > 0)
		p -= m;
	    p = p + a1 * (s - k * q);
	    while (p < 0)
		p += m;
	}
	k = p / qh;
	p = H * (p - k * qh) - k * rh;
	while (p < 0)
	    p += m;
    }
    if (a0 != 0) {
	q = m / a0;
	k = s / q;
	p = p - k * (m - a0 * q);
	if (p > 0)
	    p -= m;
	p = p + a0 * (s - k * q);
	while (p < 0)
	    p += m;
    }
    return (p);
}


void OFRandomSeed(OFRandomState *state, unsigned long y)
{
    state->y = y;
    OBPOSTCONDITION(state->y >= 2);
}


unsigned long OFRandomNextState(OFRandomState *state)
{
    unsigned long invY, tmp;
    unsigned long old;

    OBPRECONDITION(state->y >= 2);

    // We need to find IMP_A * invY + IMP_B mod M

    old = state->y;
    invY = _invert(old, MERSENNE_PRIME_31);

    tmp = _mult_mod(IMP_A, invY, MERSENNE_PRIME_31);

    state->y = tmp + IMP_B;

    if (state->y >= MERSENNE_PRIME_31)
	state->y -= MERSENNE_PRIME_31;

    OBPOSTCONDITION(state->y >= 2);
    return (old);
}


//
// Automatic seeding of the default random state
//

OFRandomState OFDefaultRandomState;

@interface _OFRandomSeeder : NSObject
@end

@implementation _OFRandomSeeder

+ (void)didLoad;
{
    unsigned int seed;
    NSTimeInterval interval;
    char *seedp, *intervalp;
    unsigned int seedSize, intervalSize;
    
    seedp = (char *)&seed;
    seedSize = sizeof(seed);
    while (seedSize--) {
        *seedp = 0;
        
        interval = [NSDate timeIntervalSinceReferenceDate];
        intervalp = (char *)&interval;
        intervalSize = sizeof(interval);
        while (intervalSize--) {
            *seedp ^= *intervalp;
            intervalp++;
        }
        seedp++;
    }

    OFRandomSeed(&OFDefaultRandomState, seed);
}

@end
