#ifndef INCLUDED_RANDOM_
#define INCLUDED_RANDOM_

#include <cmath>
#include <random>               // using the stl's mercenne twister
#include <vector>
#include <memory>

#include "../enums/enums.h"

class Random
{
    typedef std::lognormal_distribution<double>::param_type LogNormalParams;

    typedef std::vector<std::mt19937>   EngineVect;

    bool d_vary;
    size_t d_seed;
    size_t d_nCases;

    EngineVect d_engine;

        // all distribution types are default: double
    std::uniform_real_distribution<>  d_uniform;            // default: 0, max
    std::uniform_real_distribution<>  d_uniformCase;  
    std::lognormal_distribution<>     d_logNormal;          // default: 0, 1

        // these are used for varying:
                                                            // default: 0, 1
    std::unique_ptr<std::normal_distribution<>> d_normalVary;     
    std::unique_ptr<std::uniform_real_distribution<>> d_uniformVary;   
    std::unique_ptr<std::lognormal_distribution<>> d_logNormalVary;   

    static Random s_random;

    public:
        Random(Random const &other) = delete;

        void setSeed(size_t seed);

                                            // called at each iteration
        void reinit(size_t nCases,  bool vary, GeneratorType type);
                                            
        static Random &instance();
                                            // when parsing the config file
                                            // specify which vary-specific
                                            // distributions are used
        void use(DistType distribution);

        double uniform();
                                        // used for generating reproducible 
        double uniformCase();           // case characteristics

        double logNormal(double mean, double stdDev);

                // only used when 'spread: true':
        double normalVary();
        double uniformVary();
        double logNormalVary(double mean, double stdDev);

    private:
        Random();

        void reset();
};

inline void Random::setSeed(size_t seed)
{
    d_seed = seed;
}

// static
inline Random &Random::instance()
{
    return s_random;
}

#endif
