#ifndef ARBUTILS_RANDOM_HPP
#define ARBUTILS_RANDOM_HPP

#include <chrono>
#include <cstdint>
#include <random>

namespace Arbutils {

    template <class RandomT> class BaseRandom {
    private:
        uint_fast32_t _seed;
        RandomT _rng;
        std::uniform_real_distribution<double> _distribution;

    public:
        inline constexpr BaseRandom() noexcept
            : _seed(std::chrono::duration_cast<std::chrono::milliseconds>(
                        std::chrono::system_clock::now().time_since_epoch())
                        .count()),
              _rng(_seed), _distribution(0.0, 1.0) {}

        explicit inline constexpr BaseRandom(uint_fast32_t seed) noexcept
            : _seed(seed), _rng(seed), _distribution(0.0, 1.0){};

        /// Gets a random float between 0.0 and 1.0.
        [[nodiscard]] inline constexpr float GetFloat() noexcept { return static_cast<float>(GetDouble()); }

        /// Gets a random double between 0.0 and 1.0.
        [[nodiscard]] inline constexpr double GetDouble() noexcept { return _distribution(_rng); }

        /// Gets a random 32 bit integer
        [[nodiscard]] inline constexpr int32_t Get() noexcept { return static_cast<int32_t>(_rng()); }

        /// Gets a random 32 bit integer between 0, and given max parameter.
        /// \param max The exclusive max value the random value should be.
        [[nodiscard]] inline int32_t Get(int32_t max) noexcept {
            std::uniform_int_distribution<int32_t> distribution(0, max - 1);
            return distribution(_rng);
        }

        /// Gets a random 32 bit integer between given min and max parameters.
        /// \param min The inclusive min value the random value should be.
        /// \param max The exclusive max value the random value should be.
        [[nodiscard]] inline int32_t Get(int32_t min, int32_t max) noexcept {
            std::uniform_int_distribution<int32_t> distribution(min, max - 1);
            return distribution(_rng);
        }

        /// Gets a random 32 bit unsigned integer between 0 and max unsigned int.
        [[nodiscard]] inline constexpr uint32_t GetUnsigned() noexcept { return _rng(); }

        /// Gets a random 32 bit unsigned integer between 0, and given max parameter.
        /// \param max The exclusive max value the random value should be.
        [[nodiscard]] inline uint32_t GetUnsigned(uint32_t max) noexcept {
            std::uniform_int_distribution<uint32_t> distribution(0, max - 1);
            return distribution(_rng);
        }

        /// Gets a random 32 bit unsigned integer between given min and max parameters.
        /// \param min The inclusive min value the random value should be.
        /// \param max The exclusive max value the random value should be.
        [[nodiscard]] inline uint32_t GetUnsigned(uint32_t min, uint32_t max) noexcept {
            std::uniform_int_distribution<uint32_t> distribution(min, max - 1);
            return distribution(_rng);
        }

        [[nodiscard]] inline constexpr uint_fast32_t GetSeed() const noexcept { return _seed; }
    };

    class Random : public BaseRandom<std::mt19937> {
    public:
        constexpr Random() : BaseRandom() {}
        constexpr Random(uint_fast32_t seed) : BaseRandom(seed) {}
    };
}
#endif // ARBUTILS_RANDOM_HPP