/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2002-10-13
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <time.h>

#include "Random.h"

namespace QGpCoreMath {

#define IM1 2147483563
#define IM2 2147483399
#define AM (1.0/IM1)
#define IMM1 (IM1-1)
#define IA1 40014
#define IA2 40692
#define IQ1 53668
#define IQ2 52774
#define IR1 12211
#define IR2 3791
#define NDIV (1+IMM1/RANDOM_NTAB)
//#define EPS 1.2e-7 // for float
#define EPS 2.2e-16 // for double
#define RNMX (1.0-EPS)

/*!
  \class Random Random.h
  \brief Random generator for intensive random computations

*/

/*!
  \fn int Random::uniform(int min, int max)

  Returns a uniform integer deviate between \a min and \a max inclusive.
*/

/*!
  \fn double Random::uniform(double min, double max)

  Returns a uniform deviate between \a min and \a max exclusive.
*/

/*!
  Initialize random generator with \a seed.
  If \a seed is null, a random number is calculated from current time.
*/
Random::Random(int seed)
{
  TRACE;
  if(seed==0) _idum=time(nullptr)%9999999;
  else if(seed<0) _idum=-seed;
  else _idum=seed;
  App::log(5, tr("Random seed %1\n").arg(_idum));
  // Initialize ran2 generation
  _idum2=_idum;
  long k;
  int j;
  for(j=RANDOM_NTAB+7;j>=0;j--) {      // Load the shuffle table (after 8 warm-ups).
    k=_idum/IQ1;
    _idum=IA1*(_idum-k*IQ1)-k*IR1;
    if(_idum < 0) _idum += IM1;
    if(j < RANDOM_NTAB) _iv[j]=_idum;
  }
  _iy=_iv[0];
  //_nCalls=0;
}

/*!
  Create a random generator from an old random generator state saved in \a s.

  \a s size must be 35 integers.
*/
Random::Random(const QByteArray& s)
{
  TRACE;
  ASSERT(s.size()==RANDOM_STATE_SIZE);
  const qint64 * sPtr=reinterpret_cast<const qint64 *>(s.data());
  _idum=sPtr[0];
  _idum2=sPtr[1];
  _iy=sPtr[2];
  for(int i=0; i<RANDOM_NTAB; i++) {
    _iv[i]=sPtr[i+3];
  }
}

/*!
  Returns a normal (gauss distribution) deviates with mean and stddev
*/
double Random::normal(double mean, double stddev)
{
  TRACE;
  // Construct a normal variable from uniform deviates
  // Based on Central Limit theorem.
  // n=100 values are stacks and variable defined by
  // mean+stddev*sqrt(12/n)*(sum(1...n)[yi]-n/2)
  // a-has a normal distribution.
  double sum=0;
  for(int i=0;i<100;i++) sum+=ran2();
  return mean+stddev*0.346410161514*(sum-50.0);
}

/*!
  Inspired from Numerical recipes in C

  Long period (> 2e18) random number generator of L'Ecuyer with Bays-Durham
  shuffle and added safeguards. Returns a uniform random deviate between 0.0 and
  1.0 (exclusive of the endpoint values). Call with idum a negative integer to
  initialize; thereafter, do not alter idum between successive deviates in a 
  sequence. RNMX should approximate the largest floating value that is less than
  1.

  Implemented and adapted by Marc Wathelet (oct 2002) to generates double
  floating point values between 0.0 and 1.0 exclusive.
*/
double Random::ran2()
{
  TRACE;
  //_nCalls++;
  int j;
  long k;
  double temp;
  k=(_idum)/IQ1;                   // Start here when not initializing.
  _idum=IA1*(_idum-k*IQ1)-k*IR1;   // Compute idum=(IA1*idum) % IM1 
  if(_idum<0) _idum+=IM1;     // without over ows by Schrage s method.
  k=_idum2/IQ2;
  _idum2=IA2*(_idum2-k*IQ2)-k*IR2; // Compute idum2=(IA2*idum) % IM2 likewise.
  if(_idum2<0) _idum2+=IM2;
  j=_iy/NDIV;                      // Will be in the range 0..RANDOM_NTAB-1.
  _iy=_iv[j]-_idum2;               // Here idum is shuffled, idum and idum2
  _iv[j]=_idum;                  // are combined to generate output.
  if(_iy<1) _iy+=IMM1;        // Because users don't expect endpoint values.
  if((temp=AM*_iy)>RNMX) return RNMX; else return temp;
}

/*!
  Checks periodicity of random generator.
  Stores the first 50 values and wait for a matching pattern.
*/
void Random::testPeriod()
{
  TRACE;
  QVector<double> series;
  for(int i=0;i<50;i++) {
    series.append(ran2());
  }
  int p=0;
  qint64 i=0;
  while(true) {
    i++;
    if(ran2()==series[p]) {
      p++;
      App::log(tr("Repeating sequence on %1 samples after %2 generations\n").arg(p).arg(i) );
      if(p==50) break;
    } else {
      p=0;
    }
  }
}

/*!
  Checks that distribution is uniform. Values are randomly generated between 0 and 1.
  An histogram is computed for values between 0 and \a max (<=1). It is reset to 0
  after generating \a nReset0 random values. This latter argument lets you check
  the random generator at various "ages".
*/
void Random::testDistribution(double max, qint64 nReset0)
{
  TRACE;
#define N_CLASSES 16
  QVector<qint64> histogram(N_CLASSES);
  for(int i=0;i<N_CLASSES;i++) {
    histogram[i]=0;
  }
  qint64 nPrint=16, nReset=nReset0, nPrint0=0;
  double factor=N_CLASSES/max;
  qint64 i=0;
  while(true) {
    double v=ran2();
    int ci=(int)floor(v*factor);
    if(ci<N_CLASSES) histogram[ci]++;
    i++;
    if(i==nPrint0+nPrint) {
      qint64 sum=0;
      for(int j=0;j<N_CLASSES;j++) {
        sum+=histogram[j];
      }
      double f=1.0/((double)sum*max);
      printf("%15lli ",i);
      for(int j=0;j<N_CLASSES;j++) {
        printf("%9lf ", histogram[j]*f);
      }
      printf("\n");
      nPrint*=2;
    }
    if(i==nReset) {
      nPrint=16;
      nPrint0=i;
      for(int j=0;j<N_CLASSES; j++) {
        histogram[j]=0;
      }
      nReset+=nReset0;
    }
  }
}

/*!
  Returns internal state into a newly allocated buffer.

  \sa Random(const char * state)
*/
QByteArray Random::state() const
{
  TRACE;
  QByteArray s;
  s.resize(RANDOM_STATE_SIZE);
  qint64 * sPtr=reinterpret_cast<qint64 *>(s.data());
  sPtr[0]=_idum;
  sPtr[1]=_idum2;
  sPtr[2]=_iy;
  for(int i=0; i<RANDOM_NTAB; i++) {
    sPtr[i+3]=_iv[i];
  }
  return s;
}

} // namespace QGpCoreMath
