#include "ernd.h"

#include <sys/types.h>
#include <time.h>
#include <fcntl.h>


#include "logger.h"

#ifndef _MSC_VER
 #include <sys/time.h>
 #include <unistd.h>
#else
 #include <windows.h>
#endif

#include "esystem.h"

ernd rnd;

#ifdef EUTILS_HAVE_LIBGSL

double ernd::uniform()
{
  return((double)gsl_rng_uniform(grng));
}

unsigned int ernd::uniformint(unsigned int nmax)
{
  return((double)gsl_rng_uniform_int(grng,nmax)); // returns a uniformly distributed random int between 0 and nmax-1 inclusive
}

double ernd::gamma(double k,double t)
{
  return((double)gsl_ran_gamma(grng,k,t));
}

double ernd::gaussian(double sigma)
{
  return((double)gsl_ran_gaussian(grng,sigma));
}

double ernd::exponential(double rate)
{
  return((double)gsl_ran_exponential(grng,rate));
}

unsigned int ernd::geometric(double mean)
{
  return(gsl_ran_geometric(grng,mean));
}

void ernd::dirichlet(const evector& alpha, evector& ret)
{
  if (ret.size()!=alpha.size()) ret.init(alpha.size());
  gsl_ran_dirichlet(grng,alpha.size(),alpha.vector,ret.vector);
}

ernd::ernd(): grng(0x00)
{
  grng = gsl_rng_alloc(gsl_rng_ranlxd2);
#ifdef _WIN32
  _LARGE_INTEGER tptime;
  QueryPerformanceCounter(&tptime);
  seed=tptime.LowPart;
//  srand(seed);
  gsl_rng_set(grng,seed);
#else
  timeval tmptv;
  gettimeofday(&tmptv,0x00);
  seed=tmptv.tv_usec^getSystem().getPID();
  gsl_rng_set(grng,seed);

/*
  int fd=open("/dev/random",O_RDONLY);
  ldieif(fd==-1,"Error opening /dev/random");
//  unsigned long seed;
  ldieif(read(fd,&seed,sizeof(unsigned long))==-1,"Error reading from /dev/random");
  close(fd);
  gsl_rng_set(grng,seed);
*/
#endif
}

void ernd::setSeed(unsigned long _seed)
{
  seed=_seed;
  gsl_rng_set(grng,seed);
}

ernd::~ernd()
{
  if (grng)
    gsl_rng_free(grng);
  grng=0x00;
}

#else

  #ifdef WIN32
    #include <windows.h>
  #endif
  #include <stdlib.h>

  
double ernd::uniform()
{
//  int32_t res;
#ifdef __APPLE__
  return(double(rand_r(&rstate)) / RAND_MAX);
#elif defined(_MSC_VER)
	UINT32 res;
  ldieif(BCryptGenRandom(hCryptAlg, (PUCHAR)&res, sizeof(res), 0)!=0,"generating random number");
  return(double(res) / (0x1ul << 32u));
#else
  union {
    int32_t i;
	uint32_t ui;
  } res;

  random_r(&rdata,&res.i);
  return(double(res.ui)/(0x1ul<<32u));
#endif
}

unsigned int ernd::uniformint(unsigned int nmax)
{
  return(uniform()*nmax); // returns a uniformly distributed random int between 0 and nmax-1 inclusive
}



/*
double ernd::gaussian(double sigma)
{
  lerror("eutils compiled without libgsl support, so gaussian is not implemented");
//  return((double)gsl_ran_gaussian(grng,sigma));
  return(1.0);
}
*/
ernd::ernd()
{
#ifdef _WIN32
  _LARGE_INTEGER tptime;
  QueryPerformanceCounter(&tptime);
  seed=tptime.LowPart;
#else
  timeval tmptv;
  gettimeofday(&tmptv,0);
  seed=(tmptv.tv_usec^getSystem().getPID());
#endif

#ifdef __APPLE__
  rstate=seed;
#elif defined(_MSC_VER)
  ldieif(BCryptOpenAlgorithmProvider(&hCryptAlg, BCRYPT_RNG_ALGORITHM, NULL, 0) != CMC_STATUS_SUCCESS, "openning rng crypto provider");
#else
  initstate_r(seed,statebuf,16,&rdata);
#endif
}

void ernd::setSeed(unsigned long _seed)
{
  seed=_seed;
#ifdef __APPLE__
  rstate=seed;
#elif defined(_MSC_VER)

#else
  srandom_r(seed,&rdata);
#endif
}

ernd::~ernd()
{
#ifdef _MSC_VER
   BCryptCloseAlgorithmProvider(hCryptAlg, 0);
#endif
}

#endif


