// ---------------------------------------------------------------------------
// - cgen.cxx                                                                -
// - standard platform library - C generator functions implementation        -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "cthr.hpp"
#include "csys.hpp"
#include "cclk.hpp"
#include "cgen.hpp"
#include "cgen.hxx"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // static mutex creation function
  static void* mtx_create (void);
  // mutex or network services
  static void* mtx = mtx_create ();

  // this function destroy the mutex at exit
  static void mtx_destroy (void) {
    c_mtxdestroy (mtx);
  }

  // this function initialize a mutex statically and register its
  // destruction to be done at exit
  static void* mtx_create (void) {
    void* mtx = c_mtxcreate ();
    c_atexit (mtx_destroy);
    return mtx;
  }

  // the mersenne-twister (MT19937) pseudo-random number generator  
  struct s_mtprng {
    // the mt states
    t_quad d_state[624];
    // the mt index
    long   d_index;
    // create a default mt with seed 1
    s_mtprng (void) {
      ldseed (1);
    }
    // create a mt with a seed
    s_mtprng (const t_quad seed) {
      ldseed (seed);
    }
    // load the generator with a seed
    void ldseed (const t_quad seed) {
      // set the states
      d_state[0] = seed;
      for (long i = 1; i < 624; i++) {
	t_octa x  = d_state[i-1] ^ (d_state[i-1] >> 30);
	t_octa y  = 0x6c078965ULL * x + i;
	d_state[i] = y;
      }
      // reset the index
      d_index = 0;
    }
    // generate the random state
    void setrnd (void) {
      // do nothing if the index is not null
      if (d_index != 0) return;
      // make a round
      for (long i = 0; i < 623; i++) {
	t_quad x   = d_state[i] & 0x80000000UL;
	t_quad y   = x | ((d_state[i+1] % 624) & 0x7FFFFFFFUL);
	d_state[i] = d_state[(i+397)%624] ^ (y >> 1);
	if ((y % 2) != 0) d_state[i] ^= 0x9908b0dfUL;
      }
    }
    // get a random value
    t_quad getrnd (void) {
      // generate a state if the index is null
      if (d_index == 0) setrnd ();
      // extract the number
      t_quad y = d_state[d_index];
      y ^= (y >> 11);
      y ^= (y << 7)  & 0x9d2c5680UL;
      y ^= (y << 15) & 0xefc60000UL;
      y ^= (y >> 18);
      // adjust the index
      d_index = (d_index + 1) % 624;
      // hete it is
      return y;
    }
  };

  // the maximum values for MT19937
  static const t_quad CGEN_PRNG_IMAX = 0xFFFFFFFFUL;
  static const t_real CGEN_PRNG_RMAX = (t_real) CGEN_PRNG_IMAX;
  static const t_real CGEN_PRNG_NMAX = CGEN_PRNG_RMAX + 1.0;

  // the random engine seeding flag
  static bool     cgen_sflg = false;
  // the mersenne-twister prng
  static s_mtprng cgen_prng;

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // initialize the random generator

  void c_initrnd (void) {
    // compute a seed
    t_quad seed = (t_quad) (c_time () * c_getpid ());
    // seed the random generator
    c_mtxlock (mtx);
    cgen_prng.ldseed (seed);
    cgen_sflg = true;
    c_mtxunlock (mtx);
  }

  // return a real random number between 0.0 and 1.0

  t_real c_realrnd (const bool iflg) {
    c_mtxlock (mtx);
    t_real result = (t_real) cgen_prng.getrnd ();
    if (iflg == true) {
      result /= CGEN_PRNG_RMAX;
    } else {
      result /= CGEN_PRNG_NMAX;
    }
    c_mtxunlock (mtx);
    return result;
  }

  // return a random byte

  t_byte c_byternd (void) {
    return (t_byte) (255.0 * c_realrnd (true));
  }

  // return a random word

  t_word c_wordrnd (void) {
    return (t_byte) (65535.0 * c_realrnd (true));
  }

  // return a quad random number

  t_quad c_quadrnd (void) {
    c_mtxlock (mtx);
    t_quad result = cgen_prng.getrnd ();
    c_mtxunlock (mtx);
    return result;
  }

  // return an octa random number

  t_octa c_octarnd (void) {
    c_mtxlock (mtx);
    t_octa result = cgen_prng.getrnd ();
    result = (result << 32) | cgen_prng.getrnd ();
    c_mtxunlock (mtx);
    return result;
  }
}
