/**
 * \file dnn/test/common/rng.h
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
 * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */
#pragma once
#include "megdnn/dtype.h"

#include "test/common/utils.h"
#include "test/common/random_state.h"
#include <random>
#include <set>

namespace megdnn {
namespace test {

#if __cplusplus >= 201703L
#define COMPAT_RANDOM(begin, end)              \
    {                                          \
        std::default_random_engine rng_engine; \
        std::shuffle(begin, end, rng_engine);  \
    }
#else
#define COMPAT_RANDOM(begin, end) std::random_shuffle(begin, end);
#endif

class RNG {
protected:
    class RNGxorshf;

public:
    virtual void gen(const TensorND& tensor) = 0;
    virtual ~RNG() = default;
};

class Float16PeriodicalRNG : public RNG {
public:
    Float16PeriodicalRNG();
    Float16PeriodicalRNG(size_t range);

    void gen(const TensorND& tensor) override;
    dt_float16 get_single_val();

private:
    void gen_all_valid_float16();
    size_t m_offset;
    std::vector<dt_float16> m_sequence;
};

class BFloat16PeriodicalRNG : public RNG {
public:
    BFloat16PeriodicalRNG() {
        size_t bits = sizeof(dt_bfloat16) * 8;
        size_t mantissa_bits = std::numeric_limits<dt_bfloat16>::digits - 1;
        size_t exponent_bits = bits - mantissa_bits - 1;
        for (size_t exp = 1u << (exponent_bits - 2);
             exp < (1u << exponent_bits) - (1u << (exponent_bits - 2)); ++exp) {
            for (size_t x = 0; x < 1u << mantissa_bits; ++x) {
                size_t pos_num = (exp << mantissa_bits) + x;
                size_t neg_num =
                        (1u << (bits - 1)) + (exp << mantissa_bits) + x;
                union U {
                    U() {}
                    uint16_t i;
                    dt_bfloat16 f;
                } i2f;
                i2f.i = static_cast<uint16_t>(pos_num);
                m_sequence.push_back(i2f.f);
                i2f.i = static_cast<uint16_t>(neg_num);
                m_sequence.push_back(i2f.f);
            }
        }
        std::shuffle(m_sequence.begin(), m_sequence.end(),
                     RandomState::generator());
    }

    void gen(const TensorND& tensor) override {
        megdnn_assert(tensor.layout.dtype.enumv() == DTypeTrait<dt_bfloat16>::enumv);
        size_t nr_elems = tensor.layout.span().dist_elem();
        auto offset = tensor.layout.span().low_elem;
        for (size_t i = 0; i < nr_elems; ++i) {
            tensor.ptr<dt_bfloat16>()[offset + i] = get_single_val();
        }
    }

    dt_bfloat16 get_single_val() {
        if (m_offset >= m_sequence.size()) {
            m_offset = 0;
        }
        return m_sequence[m_offset++];
    }

private:
    size_t m_offset = 0;
    std::vector<dt_bfloat16> m_sequence;
};

class IIDRNG : public RNG {
public:
    void gen(const TensorND& tensor) override;
    virtual dt_float32 gen_single_val() = 0;
    virtual bool output_is_float() { return true; }

protected:
    virtual bool has_fast_float32();
    virtual void fill_fast_float32(dt_float32* dest, size_t size);
};

class NormalRNG final : public IIDRNG {
public:
    NormalRNG(dt_float32 mean = 0.0f, dt_float32 stddev = 1.0f)
            : m_dist(mean, stddev) {}

    void fill_fast_float32(dt_float32* dest, size_t size) override;

protected:
    dt_float32 gen_single_val() override;

private:
    std::normal_distribution<dt_float32> m_dist;
    bool has_fast_float32() override;
};

class ConstValue final : public IIDRNG {
public:
    ConstValue(dt_float32 value = 0.0f) : value_(value) {}
    void fill_fast_float32(dt_float32* dest, size_t size) override;

protected:
    dt_float32 gen_single_val() override { return value_; }

private:
    dt_float32 value_;
    bool has_fast_float32() override { return true; }
};

class UniformIntRNG : public IIDRNG {
public:
    UniformIntRNG(dt_int32 a, dt_int32 b) : m_dist(a, b) {}
    dt_float32 gen_single_val() override;
    bool output_is_float() override { return false; }

protected:
    std::uniform_int_distribution<dt_int32> m_dist;
};

//! range must be positive; each value would be negated with prob 0.5
class UniformIntNonZeroRNG : public UniformIntRNG {
    std::uniform_int_distribution<dt_int32> m_dist_flip{0, 1};

public:
    UniformIntNonZeroRNG(int a, int b) : UniformIntRNG(a, b) {
        megdnn_assert(a > 0 && b > a);
    }

    dt_float32 gen_single_val() override;
};

class UniformFloatRNG : public IIDRNG {
public:
    UniformFloatRNG(dt_float32 a, dt_float32 b) : m_dist(a, b) {}
    dt_float32 gen_single_val() override;

protected:
    std::uniform_real_distribution<dt_float32> m_dist;
    bool has_fast_float32() override;
    void fill_fast_float32(dt_float32* dest, size_t size) override;
};

//! range must be positive; each value would be negated with prob 0.5
class UniformFloatNonZeroRNG : public UniformFloatRNG {
    std::uniform_int_distribution<dt_int32> m_dist_flip{0, 1};

public:
    UniformFloatNonZeroRNG(float a, float b) : UniformFloatRNG(a, b) {
        megdnn_assert(a > 0 && b > a);
    }

    dt_float32 gen_single_val() override;
    void fill_fast_float32(dt_float32* dest, size_t size) override;
};

class UniformFloatWithZeroRNG final : public UniformFloatRNG {
public:
    UniformFloatWithZeroRNG(dt_float32 a, dt_float32 b,
                            float zero_val_proportion)
            : UniformFloatRNG(a, b) {
        if (zero_val_proportion < 0.f)
            zero_val_proportion_ = 0.f;
        else if (zero_val_proportion > 1.f)
            zero_val_proportion_ = 1.f;
        else
            zero_val_proportion_ = zero_val_proportion;
    }

private:
    float zero_val_proportion_;
    void fill_fast_float32(dt_float32* dest, size_t size) override;
};

class BernoulliRNG final : public IIDRNG {
public:
    BernoulliRNG(dt_float32 probability_);
    dt_float32 gen_single_val() override;

private:
    dt_float32 m_probability;
    std::uniform_real_distribution<dt_float32> m_dist;
};

/**
 * \brief RNG without replacement, so that no two values in the tensor are
 * equal.
 *
 * Each value is generated repeatedly by IIDRNG, until the newly-generated value
 * differs from any previous value.
 */
class NoReplacementRNG final : public RNG {
private:
    IIDRNG* m_iid_rng;

public:
    NoReplacementRNG(IIDRNG* iid_rng) : m_iid_rng(iid_rng) {}
    void gen(const TensorND& tensor) override;
};

//! generate a batch of matrices that are likely to have a small condition num
class InvertibleMatrixRNG final : public RNG {
    std::unique_ptr<RNGxorshf> m_rng;

public:
    InvertibleMatrixRNG();
    ~InvertibleMatrixRNG() noexcept;

    void gen(const TensorND& tensor) override;

private:
    template <typename ctype>
    void do_gen(ctype* ptr, size_t batch, size_t n);
};

//! generate a continuous number of delta, start from value
class ConsecutiveRNG final : public IIDRNG {
public:
    ConsecutiveRNG(dt_float32 value = 0.0f, dt_float32 delta = 1.0f)
            : value_(value), delta_(delta) {}
    void fill_fast_float32(dt_float32* dest, size_t size) override;

protected:
    dt_float32 gen_single_val() override {
        auto res = value_;
        value_ += delta_;
        return res;
    }

private:
    dt_float32 value_, delta_;
    bool has_fast_float32() override { return true; }
};

}  // namespace test
}  // namespace megdnn
// vim: syntax=cpp.doxygen
