/**************************************************************************
 *{@C
 *      Copyright:      2008-2010 Paul Obermeier (obermeier@tcl3d.org)
 *
 *      Module:         Tcl3D -> tcl3dOgl
 *      Filename:       tcl3dUtilRandom.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Machine independent pseudo-random number generators.
 *                      The functions defined below are similar to the
 *                      "drand48" and "lrand48" functions available with
 *                      System V Unix. They are re-implemented here so that
 *                      we have identical random number generators available
 *                      on all types of computers.
 *
 *      Exported functions:
 *                      tcl3dNewRandomGen
 *                      tcl3dDeleteRandomGen
 *                      tcl3dGetRandomInt
 *                      tcl3dGetRandomFloat
 *
 **************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#include "tcl3dUtilRandom.h"

static const unsigned short
    a[3] = {0xe66d, 0xdeec, 0x0005},
    c = 0xb;

/* A random unsigned integer number is calculated.
   The numbers are uniformly distributed over the interval [ 0, (1<<32) - 1 ).
*/

static unsigned int calcRandom (randomGen rGen)
{
    unsigned int y[3];

    /* Compute (r * a + c) % (1 << 48), using 48-bit integer arithmetic. */
    y[2] =                   (rGen->r0 * a[2]);
    y[2] = (y[2] & 0xffff) + (rGen->r1 * a[1]);
    y[2] = (y[2] & 0xffff) + (rGen->r2 * a[0]);
    y[1] =                   (rGen->r0 * a[1]);
    y[2] = (y[2] & 0xffff) + (y[1] >> 16);
    y[1] = (y[1] & 0xffff) + (rGen->r1 * a[0]);
    y[2] = (y[2] & 0xffff) + (y[1] >> 16);
    y[0] =                   (rGen->r0 * a[0]);
    y[1] = (y[1] & 0xffff) + (y[0] >> 16);
    y[2] = (y[2] & 0xffff) + (y[1] >> 16);
    y[0] = (y[0] & 0xffff) + c;
    y[1] = (y[1] & 0xffff) + (y[0] >> 16);
    y[2] = (y[2] & 0xffff) + (y[1] >> 16);
    rGen->r0 = y[0];
    rGen->r1 = y[1];
    rGen->r2 = y[2];
    return (rGen->r2 << 16) | rGen->r1;
}

/***************************************************************************
 *[@e
 *      Name:           tcl3dNewRandomGen
 *
 *      Usage:          Initialize a new random number generator.
 *
 *      Synopsis:       randomGen tcl3dNewRandomGen (unsigned int seed)
 *
 *      Description:    A new pseudo-random number generator is created and
 *                      initialized with seed value "seed".
 *
 *      Return value:   The identifier for the new random number generator.
 *                      If the generator could not be created, return NULL.
 *
 *      See also:
 *
 ***************************************************************************/

randomGen tcl3dNewRandomGen (unsigned int seed)
{
    RandomGenStruct *pGen;

    pGen = (RandomGenStruct *) malloc (sizeof (RandomGenStruct));
    if (!pGen) {
        return NULL;
    }
    pGen->r0 = 0x330e;
    pGen->r1 = seed & 0xffff;
    pGen->r2 = seed >> 16;
    return pGen;
}

/***************************************************************************
 *[@e
 *      Name:           tcl3dDeleteRandomGen
 *
 *      Usage:          Delete a random number generator.
 *
 *      Synopsis:       void tcl3dDeleteRandomGen (randomGen rGen)
 *
 *      Description:    Delete the random number generator identified by "rGen".
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void tcl3dDeleteRandomGen (randomGen rGen)
{
    if (rGen) {
        free (rGen);
    }
}

/***************************************************************************
 *[@e
 *      Name:           tcl3dGetRandomInt
 *
 *      Usage:          Generate a pseudo-random integer number.
 *
 *      Synopsis:       int tcl3dGetRandomInt (randomGen rGen, int rmin, int rmax)
 *
 *      Description:    A random integer number is generated.
 *                      The generated numbers are uniformly distributed over the
 *                      interval [rmin, rmax).
 *                      Precondition: rmin < rmax
 *
 *      Return value:   The integer random number.
 *
 *      See also:       tcl3dGetRandomFloat
 *
 ***************************************************************************/

int tcl3dGetRandomInt (randomGen rGen, int rmin, int rmax)
{
    double num;

    num = (double)(0x3fffffff & calcRandom(rGen)) / (double)0x40000000;
    return (int) ((double)rmin + ((double)(rmax - rmin) * num));
}

/***************************************************************************
 *[@e
 *      Name:           tcl3dGetRandomFloat
 *
 *      Usage:          Generate a pseudeo-random floating point number.
 *
 *      Synopsis:       float tcl3dGetRandomFloat (randomGen rGen,
 *                                                 float rmin, float rmax)
 *
 *      Description:    A random floating point number is generated.
 *                      The generated numbers are uniformly distributed over the 
 *                      interval [rmin, rmax).
 *                      Precondition: rmin < rmax
 *
 *      Return value:   The floating point random number.
 *
 *      See also:       tcl3dGetRandomInt
 *
 ***************************************************************************/

float tcl3dGetRandomFloat (randomGen rGen, float rmin, float rmax)
{
    double num;

    num = (double)(0x3fffffff & calcRandom(rGen)) / (double)0x40000000;
    return (float) (rmin + ((rmax - rmin) * num));
}
