/**********************************************************************

  random.c -

  $Author: melville $
  $Date: 2003/05/14 13:58:44 $
  created at: Fri Dec 24 16:39:21 JST 1993

  Copyright (C) 1993-2000 Yukihiro Matsumoto

**********************************************************************/

#include "ruby.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <time.h>
#ifndef NT
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#else
struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};
#endif
#endif /* NT */

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

/*
 * Prefer to use drand48, otherwise use random, or rand as a last resort.
 */
#ifdef HAVE_DRAND48

#ifndef HAVE_DRAND48_DECL
double drand48 _((void));
void srand48 _((long));
#endif

#define SRANDOM(s)	srand48((long)(s))
#define RANDOM_NUMBER	drand48()

#else /* not HAVE_DRAND48 */

/*
 * The largest number returned by the random number generator is
 * RANDOM_MAX.  If we're using `rand' it's RAND_MAX, but if we're
 * using `random' it's 2^31-1.
 */
#ifndef RANDOM_MAX
# ifndef HAVE_RANDOM
#  define RANDOM_MAX	RAND_MAX
# else
#  define RANDOM_MAX	2147483647.0
# endif
#endif

#ifdef HAVE_RANDOM

#define RANDOM	random
#define SRANDOM	srandom

#else /* HAVE_RANDOM */

#define RANDOM	rand
#define SRANDOM	srand

#endif /* HAVE_RANDOM */

/* 0 <= RANDOM_NUMBER < 1 */
#define RANDOM_NUMBER (((double)RANDOM())/((double)RANDOM_MAX+1))

#endif /* not HAVE_DRAND48 */

static int first = 1;
#ifdef HAVE_RANDOM
static char state[256];
#endif

static int
rand_init(seed)
    long seed;
{
    int old;
    static unsigned int saved_seed;

#if defined HAVE_INITSTATE
    if (first == 1) {
	initstate(1, state, sizeof state);
    }
    else {
	setstate(state);
    }
#endif
    first = 0;

    SRANDOM(seed);
    old = saved_seed;
    saved_seed = seed;

    return old;
}

static VALUE
rb_f_srand(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    VALUE a;
    unsigned int seed, old;

    rb_secure(4);
    if (rb_scan_args(argc, argv, "01", &a) == 0) {
	static int n = 0;
	struct timeval tv;

	gettimeofday(&tv, 0);
	seed = tv.tv_sec ^ tv.tv_usec ^ getpid() ^ n++;
    }
    else {
	seed = NUM2UINT(a);
    }
    old = rand_init(seed);

    return rb_uint2inum(old);
}

static VALUE
rb_f_rand(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    VALUE vmax;
    long val, max;

    rb_scan_args(argc, argv, "01", &vmax);
    if (first) {
	struct timeval tv;

	gettimeofday(&tv, 0);
	rand_init(tv.tv_sec ^ tv.tv_usec ^ getpid());
    }
    switch (TYPE(vmax)) {
      case T_FLOAT:
	if (RFLOAT(vmax)->value <= LONG_MAX && RFLOAT(vmax)->value >= LONG_MIN) {
	    max = (long)RFLOAT(vmax)->value;
	    break;
	}
	vmax = rb_dbl2big(RFLOAT(vmax)->value);
	/* fall through */
      case T_BIGNUM:
	return rb_big_rand(vmax, RANDOM_NUMBER);
      case T_NIL:
	max = 0;
	break;
      default:
	max = NUM2LONG(vmax);
	break;
    }

    if (max == 0) {
	return rb_float_new(RANDOM_NUMBER);
    }
    val = max*RANDOM_NUMBER;

    if (val < 0) val = -val;
    return rb_int2inum(val);
}

void
Init_Random()
{
    rb_define_global_function("srand", rb_f_srand, -1);
    rb_define_global_function("rand", rb_f_rand, -1);
}
