/*************************************************************************
 *   c_gsl_rng - C front ends for bits of GNU Scientific Library         *
 *                                                                       *
 *   Copyright (C) 2016-2017 by Boud Roukema                             *
 *   boud cosmo.torun.pl                                                 *
 *                                                                       *
 *   This program is free software; you can redistribute it and/or modify*
 *   it under the terms of the GNU General Public License as published by*
 *   the Free Software Foundation; either version 2 of the License, or   *
 *   (at your option) any later version.                                 *
 *                                                                       *
 *   This program is distributed in the hope that it will be useful,     *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with this program; if not, write to the                       *
 *   Free Software Foundation, Inc.,                                     *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA            *
 *   or see https://www.gnu.org/licenses/licenses.html#GPL .             *
 *************************************************************************/

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#ifdef __GNUC__
#include <stdio.h>
#include <strings.h>
#endif

#define N_SIGMA_GAUSS 1.0

/* Only turn on this check for small numbers; heavy I/O! */

/*
#define CHECK_STREAM_INDEPENDENCE 1
*/


void get_gsl_rng_seeds(int64_t iseed,
                       int32_t MaxRandNumStreams,
                       int64_t *seeds /* 1D array [MaxRandNumStreams] */
                       );/* get_gsl_rng_ran_seeds */


/* global random number generator - generate seeds, close generator */
void get_gsl_rng_seeds(int64_t iseed,
                       int32_t MaxRandNumStreams,
                       int64_t *seeds /* 1D array [MaxRandNumStreams] */
                       )/* get_gsl_rng_ran_seeds */
{
  const gsl_rng_type * T_gsl;
  gsl_rng * r_gsl;

  int32_t i_seeds;

  /* MT19937 - Makoto Matsumoto and Takuji Nishimura;
     Mersenne prime period of 2^19937 - 1;
     second revision 2002 - info gsl for details */
  T_gsl = gsl_rng_mt19937;

  /* RANLUX algorithm of L\"uscher;
     period \sim 10^171;
     second generation algorithm;
     double-precision, highest luxury level;
     slow: about 7-10 times slower than MT19937;
     claims of well-tested randomness - info gsl for more
  */
  /* T_gsl = gsl_rng_ranlxd2; */

  /* Fortran does not include unsigned integers,
     so we store iseed and seeds[...] as signed 64-bit integers,
     but convert them back and forth when giving them to or
     getting them from gsl_rng.
  */

  /* No conversion from int64_t should be needed as of 2017. */
  gsl_rng_default_seed = (unsigned long) iseed;
  r_gsl = gsl_rng_alloc(T_gsl);

  for(i_seeds=0; i_seeds < MaxRandNumStreams; i_seeds++){
    seeds[i_seeds] = (int64_t) gsl_rng_get(r_gsl);
  };

  gsl_rng_free(r_gsl);
}

void gauss_gsl(int64_t Seed,
               double *RandNum,
               int free_gauss_gsl);


/* Independent instances of this are created
   by independent mpi threads. */
void gauss_gsl(int64_t Seed,
               double *RandNum,
               int free_gauss_gsl){

  /* The static attribute should work for mpi (independent memory per
     thread). If porting to openmp (shared memory) is needed, test this
     before trusting it.
  */
  static int first_time = 1;
  static const gsl_rng_type * T_gsl;
  static gsl_rng * r_gsl = NULL;

  static const double n_sigma_gauss = N_SIGMA_GAUSS;

  if(free_gauss_gsl){
    gsl_rng_free(r_gsl);
    first_time = 0; /* in case this function gets called again after
                       freeing*/
  }else{

    if(first_time){
       first_time = 0; /* no longer first time */
      /* initialise the random number generator */

      T_gsl = gsl_rng_ranlxd2;
      /* A faster option could be:
         T_gsl = gsl_rng_mt19937;
         See the GSL documentation for more generators.
      */
      gsl_rng_default_seed = (unsigned long) Seed;
      r_gsl = gsl_rng_alloc(T_gsl); /* uses gsl_rng_default_seed */
    };

    /* generate double-precision gaussian random value
       https://en.wikipedia.org/wiki/Ziggurat_algorithm
     */
    *RandNum = gsl_ran_gaussian_ziggurat(r_gsl,
                                         n_sigma_gauss);

#ifdef CHECK_STREAM_INDEPENDENCE
    fflush(stdout);
    printf("Check: gauss_gsl: Seed= %ld, r_gsl= %p, *RandNum= %e\n",
           Seed, r_gsl, *RandNum);
    fflush(stdout);
#endif

  };
}
