import java.util.*;

/**
 *  Random number generator by F. James adopted by Phil Linttell, James F. Hickling &
 *  Josef Pelikan
 *  <p>This random number generator originally appeared in "Toward a Universal
 *  Random Number Generator" by George Marsaglia and Arif Zaman.
 *  Florida State University Report: FSU-SCRI-87-50 (1987)
 *  <p>It was later modified by F. James and published in "A Review of Pseudo-random Number
 *  Generators"
 *  <p>Converted from FORTRAN to C by Phil Linttell, James F. Hickling
 *  Management Consultants Ltd, Aug. 14, 1989.
 *  <p>THIS IS THE BEST KNOWN RANDOM NUMBER GENERATOR AVAILABLE.
 *  (However, a newly discovered technique can yield a period of 10^600. But that is still
 *  in the development stage.)
 *  <p>It passes ALL of the tests for random number generators and has a period
 *  of 2^144, is completely portable (gives bit identical results on all
 *  machines with at least 24-bit mantissas in the floating point
 *  representation).
 *  <p>The algorithm is a combination of a Fibonacci sequence (with lags of 97
 *  and 33, and operation "subtraction plus one, modulo one") and an
 *  "arithmetic sequence" (using subtraction).
 *  <p>On a Vax 11/780, this random number generator can produce a number in 13 microseconds.
 *   @author  Josef.Pelikan@mff.cuni.cz
 *   @version 0.007 (2001/06/17)
 *   @since   0.006
 */
public class RandomJames {

    protected double[] u;

    protected double c, cd, cm;

    protected int i97, j97;

    protected double prepMean, prepVar, norMul, norAdd;

    protected int prepRep;

    /** Success flag. */
    public boolean ok;

    /**
     *  Initializing constructor.
     */
    public RandomJames ( int ij, int kl ) {
        u = new double[97];
        reset(ij,kl);
        prepMean = 0.0;
        prepVar  = -1.0;
        prepRep  = 0;
    }

    /**
     *  Default constructor ( ij = 1802, kl = 9373 ).
     */
    public RandomJames () {
        u = new double[97];
        reset(1802,9373);
        prepMean = 0.0;
        prepVar  = -1.0;
        prepRep  = 0;
    }

    /**
     *  Deterministic restart of a sequence.
     *   @param  ij Random seed #1 [0,31328]
     *   @param  kl Random seed #2 [0,30081]
     */
    public void reset ( int ij, int kl ) {
        double s, t;
        int i, j, k, l, m;
        int ii, jj;

        if ( ij < 0 || ij > 31328 || kl < 0 || kl > 30081 ) {
            ok = false;
            return;
            }
        ok = true;

        i = ((ij/177) % 177) + 2;
        j =  (ij      % 177) + 2;
        k = ((kl/169) % 178) + 1;
        l =  (kl      % 169);

        for ( ii = 0; ii < 97; ii++ ) {
            s = 0.0;
            t = 0.5;
            for ( jj = 0; jj++ < 24; ) {
                m = ( (i*j % 179) * k ) % 179;
                i = j;
                j = k;
                k = m;
                l = (53*l + 1) % 169;
                if ( l*m % 64 >= 32 ) s += t;
                t *= 0.5;
                }
            u[ii] = s;
            }

        c  =   362436.0 / 16777216.0;
        cd =  7654321.0 / 16777216.0;
        cm = 16777213.0 / 16777216.0;

        i97 = 96;
        j97 = 32;
    }

    /**
     *  Undeterministic restart of a sequence (uses system time).
     */
    public void randomize () {
        Calendar c = Calendar.getInstance();
        c.setTime( new Date() );
        int s = c.get(Calendar.SECOND);
        int m = c.get(Calendar.MINUTE);
        int h = c.get(Calendar.HOUR);
        int d = c.get(Calendar.DAY_OF_YEAR) - 1;

        double maxs_sig   = 60.0 + 60.0/60.0 + 24.0/60.0/60.0 + 366.0/24.0/60.0/60.0;
        double maxs_insig = 60.0 + 60.0*60.0 + 24.0*60.0*60.0 + 366.0*24.0*60.0*60.0;

        double s_sig      = s + m/60.0 + h/60.0/60.0 + d/24.0/60.0/60.0;
        double s_insig    = s + m*60.0 + h*60.0*60.0 + d*24.0*60.0*60.0;

        int s1 = (int)(s_sig   / maxs_sig   * 31328.0);
        int s2 = (int)(s_insig / maxs_insig * 30081.0);

        reset(s1,s2);
    }

    /**
     *  Validation test of a generator algorithm.
     */
    public boolean test () {
        reset(1802,9373);
        if ( !ok ) return false;
        double[] temp = new double[1000];
        int i;
        for ( i = 0; i++ < 20; ) {
            uniformNumbers(temp);
            if ( !ok ) return false;
            }
        uniformNumbers(temp,0,6);
        for ( i = 0; i < 6; i++ ) temp[i] = temp[i] * 4096.0 * 4096.0 + 0.5;
        return( (int)temp[0] ==  6533892 &&
                (int)temp[1] == 14220222 &&
                (int)temp[2] ==  7275067 &&
                (int)temp[3] ==  6172232 &&
                (int)temp[4] ==  8354498 &&
                (int)temp[5] == 10633180 );
    }

    /**
     *  Uniform random number generator from [0,1].
     */
    public double uniformNumber () {
        if ( !ok ) return 0.0;
        double uni = u[i97] - u[j97];
        if ( uni < 0.0 ) uni += 1.0;
        u[i97] = uni;
        if ( --i97 < 0 ) i97 = 96;
        if ( --j97 < 0 ) j97 = 96;
        if ( (c -= cd) < 0.0 ) c += cm;
        if ( (uni -= c) < 0.0 ) uni += 1.0;
        return uni;
    }

    /**
     *  Vector of uniform random numbers (from [0,1]).
     */
    public void uniformNumbers ( double[] vec ) {
        uniformNumbers(vec,0,vec.length);
    }

    /**
     *  Vector of uniform random numbers (from [0,1]).
     */
    public void uniformNumbers ( double[] vec, int from, int number ) {
        if ( vec == null || from >= vec.length ) return;
        number += from;
        if ( number > vec.length ) number = vec.length;
        while ( from < number )
            vec[from++] = uniformNumber();
    }

    /**
     *  Pseudo-normal (Gaussian) distribution generator.
     */
    public double normalNumber ( double mean, double variance, int rep ) {
        checkNormal(mean,variance,rep);
        int i;
        double sum = 0.0;
        for ( i = 0; i++ < rep; ) sum += uniformNumber();
        return( sum * norMul + norAdd );
    }

    /**
     *  Pseudo-normal (Gaussian) distribution generator.
     */
    public void normalNumbers ( double mean, double variance, int rep, double[] vec ) {
        normalNumbers(mean,variance,rep,vec,0,vec.length);
    }

    /**
     *  Pseudo-normal (Gaussian) distribution generator.
     */
    public void normalNumbers ( double mean, double variance, int rep,
                                double[] vec, int from, int number ) {
        if ( vec == null || from >= vec.length ) return;
        number += from;
        if ( number > vec.length ) number = vec.length;
        checkNormal(mean,variance,rep);
        int i;
        double sum;
        while ( from < number ) {
            sum = 0.0;
            for ( i = 0; i++ < rep; ) sum += uniformNumber();
            vec[from++] =  sum * norMul + norAdd;
            }
    }

    /**
     *  Pseudo-normal distribution parameter setup.
     */
    protected void checkNormal ( double mean, double variance, int rep ) {
        if ( rep != prepRep || variance != prepVar || mean != prepMean ) {
            prepRep = rep;
            prepMean = mean;
            prepVar = variance;
            norMul = variance * Math.sqrt( 12.0 / rep );
            norAdd = mean - rep * 0.5 * norMul;
            }
    }

}
