// This is core/vnl/vnl_random.h
#ifndef vnl_random_h
#define vnl_random_h
#ifdef _MSC_VER
#  include <vcl_msvc_warnings.h>
#endif
#include "vnl/vnl_export.h"

//:
// \file
// \author Aaron Kotcheff (Manchester)
// \brief A superior random number generator

constexpr unsigned int vnl_random_array_size = 37;

//: A superior random number generator.
// Implements a new random number generator that
// recently appeared in the literature. It generates 32 bit
// numbers with a higher degree of randomness than previous
// generators and has a cycle of 10^354 i.e. so huge that in
// practice it never cycles.
// For the mathematics behind it see:
// "A New Class of Random Number Generators" G. Marsaglia and A. Zaman,
// Annals of Applied Probability 1991, Vol. 1, No. 3, 462.
class VNL_EXPORT vnl_random
{
    enum {linear_congruential_multiplier = 1664525, mz_previous1 = 24};
    unsigned long linear_congruential_previous;
    unsigned long mz_seed_array[vnl_random_array_size];
    unsigned long mz_array[vnl_random_array_size];
    unsigned int mz_array_position{0UL};
    int mz_borrow{0};
    unsigned long linear_congruential_lrand32();

    double mz_previous_normal;
    int mz_previous_normal_flag{0};

  public:
    //: Default constructor.
    // Initializes the random number generator non-deterministically.
    // i.e. it will generate a different series of random numbers each
    // time the program is run.
    vnl_random();

    //: Destructor
    ~vnl_random();

    //: Construct with seed.
    //  Initializes the random number generator deterministically
    //  using a single ulong as the 'seed'. A linear congruential
    //  generator is used to generate the 37 ulongs needed
    //  as the real seed. The same seed will produce the
    //  same series of random numbers.
    //
    //  9667566  is a good seed.
    vnl_random(unsigned long seed);

    //: Construct with seed.
    //  Initializes the random number generator deterministically
    //  using 37 ulongs as the 'seed'. The same seed will
    //  produce the same series of random numbers.
    vnl_random(unsigned long seed[vnl_random_array_size]);

    //: Copy constructor.
    //  Initializes/sets the random number generator to exactly
    //  the same state as the argument, i.e. both will generate exactly
    //  the same series of random numbers from then on.
    vnl_random(const vnl_random&);

    //: Copy operator.
    //  Initializes/sets the random number generator to exactly
    //  the same state as the argument, i.e. both will generate exactly
    //  the same series of random numbers from then on.
    vnl_random& operator=(const vnl_random&);

    //: Starts a new non-deterministic sequence from an already declared generator.
    void reseed();

    //: Starts a new deterministic sequence from an already declared generator using the provided seed.
    void reseed(unsigned long);

    //: Starts a new deterministic sequence from an already declared generator using the provided seed.
    void reseed(const unsigned long[vnl_random_array_size]);

    //: This restarts the sequence of random numbers.
    //  Restarts so that it repeats
    //  from the point at which you declared the generator, last
    //  initialized it, or last called a 'reseed'.
    void restart();

    //: Generates a random unsigned 32-bit number.
    unsigned long lrand32();

    //: Generates a random unsigned long in [a,b]
    int lrand32(int a, int b);

    //: Generates a random unsigned long in [0,b]
    int lrand32(int b) {return lrand32(0, b);}

    //: Generates a random unsigned long in [a,b]
    int lrand32(int a, int b, int&);

    //:  Generates a random double in the range a <= x <= b with 32 bit randomness.
    //   drand32(1,0) is random down to about the 10th decimal place.
    double drand32(double a, double b);

    //: Generates a random unsigned integer in [0,n)
    // This function allows the random number generator to be used as
    // a functor, e.g. with std::random_shuffle()
    unsigned long operator()(unsigned n) { return lrand32(0, n-1); }

    //:  Generates a random double in the range 0 <= x <= b with 32 bit randomness.
    //   drand32(1.0) is random down to about the 10th decimal place.
    double drand32(double b) {return drand32(0.0, b);}

    //:  Generates a random double in the range 0 <= x <= 1 with 32 bit randomness.
    //   drand32() is random down to about the 10th decimal place.
    double drand32() {return drand32(0.0, 1.0);}

    //: Generates a random double in the range a <= x <= b with 64 bit randomness.
    //  Completely random down to the accuracy of an IEEE double.
    double drand64(double a, double b);

    //: Generates a random double in the range 0 <= x <= b with 64 bit randomness.
    //  Completely random down to the accuracy of an IEEE double.
    double drand64(double b) {return drand64(0.0, b);}

    //: Generates a random double in the range 0 <= x <= 1 with 64 bit randomness.
    //  Completely random down to the accuracy of an IEEE double.
    double drand64() {return drand64(0.0, 1.0);}

    //: Random value from a unit normal distribution about zero.
    // Uses a drand32() as its underlying generator.
    // Because the function uses a probability transform, the randomness (and
    // quantisation) is non-linearly dependent on the value. The further the
    // sample is from zero, the lower the number of bits on which it is random.
    double normal();

    //: Random value from a unit normal distribution about zero.
    // Uses a drand64() as its underlying generator.
    // Because the function uses a probability transform, the randomness (and
    // quantisation) is non-linearly dependent on the value. The further the
    // sample is from zero, the lower the number of bits on which it is random.
    double normal64();
};

#endif // vnl_random_h
