#include <stdint.h>
#include <math.h>
#include <unistd.h>
#include <time.h>
#ifdef LINUX
#include <sys/time.h>
#include <fcntl.h>
#endif
#include <SDL.h>

#include "rng.h"
#include "log.h"

/* Mersenne twister state. */
static uint32_t MT[624];
static uint32_t mt_y;
static int mt_pos = 0;  /* Current number. */

static uint32_t rng_timeEntropy(void);
/* Mersenne twister. */
static void mt_initArray(uint32_t seed);
static void mt_genArray(void);
static uint32_t mt_getInt(void);

void rng_init(void) {
  uint32_t i;
  int need_init;

  need_init = 1; /* Initialize by default. */
#ifdef LINUX
  int fd;
  fd = open("/dev/urandom", O_RDONLY); /* /dev/urandom is better then time seed. */
  if(fd != -1) {
    i = sizeof(uint32_t)*624;
    if(read(fd, &MT, i) == (ssize_t)i)
      need_init = 0;
    else
      i = rng_timeEntropy();
    close(fd);
  } else
    i = rng_timeEntropy();
#else
  i = rng_timeEntropy();
#endif
  if(need_init)
    mt_initArray(i);
  for(i = 0; i < 10; i++)
    /* Generate numbers to get away from poor initial values. */
    mt_genArray();
}

/* Use the time as source of entropy. */
static uint32_t rng_timeEntropy(void) {
  int i;
  
#ifdef WIN32
  struct _timeb tb;
  _ftime(&tb);
  i = tb.time * 1000 + tb.millitm;
#else
  struct timeval tv;
  gettimeofday(&tv, NULL);
  i = tv.tv_sec * 1000000 + tv.tv_usec;
#endif
  return i;
}

/* Generates the initial mersenne twister based on seed. */
static void mt_initArray(uint32_t seed) {
  int i;
  MT[0] = seed;
  for(i = 1; i < 624; i++)
    MT[i] = 1812433253 * (MT[i-1] ^ (((MT[i-1])) + i) >> 30);
  mt_pos = 0;
}

/* Generate an array of numbers. */
static void mt_genArray(void) {
  int i;
  for(i = 0; i < 624; i++) {
    mt_y = (MT[i] & 0x80000000) + ((MT[i] % 624) & 0x7FFFFFFF);
    if(mt_y % 2)
      /* Odd. */
      MT[i] = (MT[(i+397) % 624] ^ (mt_y >> 1)) ^ 2567483615;
    else
      /* Even. */
      MT[i] = MT[(i+397) % 624] ^ (mt_y >> 1);
  }
  mt_pos = 0;
}

/* Get the next int. */
static uint32_t mt_getInt(void) {
  if(mt_pos >= 624) mt_genArray();

  mt_y = MT[mt_pos++];
  mt_y ^= mt_y >> 11;
  mt_y ^= (mt_y << 7)   & 2636928640;
  mt_y ^= (mt_y << 15)  & 4022730752;
  mt_y ^= (mt_y >> 18);

  return mt_y;
}

/* Return a random int. */
unsigned int randint(void) {
  return mt_getInt();
}

/* Return a random double. */
static double m_div = (double)(0xFFFFFFFF);
double randfp(void) {
  double m = (double)mt_getInt();
  return m / m_div;
}

