#include <thrust/generate.h>
#include <thrust/random.h>

#include <sstream>

#include <unittest/unittest.h>

template <typename Engine>
struct ValidateEngine
{
  _CCCL_HOST_DEVICE ValidateEngine(const typename Engine::result_type value_10000)
      : m_value_10000(value_10000)
  {}

  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    Engine e;
    e.discard(9999);

    // get the 10Kth result
    return e() == m_value_10000;
  }

  const typename Engine::result_type m_value_10000;
}; // end ValidateEngine

template <typename Engine, bool trivial_min = (Engine::min == 0)>
struct ValidateEngineMin
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    Engine e;

    bool result = true;

    for (int i = 0; i < 10000; ++i)
    {
      result &= (e() >= Engine::min);
    }

    return result;
  }
}; // end ValidateEngineMin

template <typename Engine>
struct ValidateEngineMin<Engine, true>
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    return true;
  }
};

template <typename Engine>
struct ValidateEngineMax
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    Engine e;

    bool result = true;

    for (int i = 0; i < 10000; ++i)
    {
      result &= (e() <= Engine::max);
    }

    return result;
  }
}; // end ValidateEngineMax

template <typename Engine>
struct ValidateEngineEqual
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    bool result = true;

    // test from default constructor
    Engine e0, e1;
    result &= (e0 == e1);

    // advance engines
    e0.discard(10000);
    e1.discard(10000);
    result &= (e0 == e1);

    // test from identical seeds
    Engine e2(13), e3(13);
    result &= (e2 == e3);

    // test different seeds aren't equal
    Engine e4(7), e5(13);
    result &= !(e4 == e5);

    // test reseeding engine to the same seed causes equality
    e4.seed(13);
    result &= (e4 == e5);

    return result;
  }
};

template <typename Engine>
struct ValidateEngineUnequal
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    bool result = true;

    // test from default constructor
    Engine e0, e1;
    result &= !(e0 != e1);

    // advance engines
    e0.discard(1000);
    e1.discard(1000);
    result &= !(e0 != e1);

    // test from identical seeds
    Engine e2(13), e3(13);
    result &= !(e2 != e3);

    // test different seeds aren't equal
    Engine e4(7), e5(13);
    result &= (e4 != e5);

    // test reseeding engine to the same seed causes equality
    e4.seed(13);
    result &= !(e4 != e5);

    // test different discards causes inequality
    Engine e6(13), e7(13);
    e6.discard(500);
    e7.discard(1000);
    result &= (e6 != e7);

    return result;
  }
};

template <typename Distribution, typename Engine>
struct ValidateDistributionMin
{
  typedef Engine random_engine;

  _CCCL_HOST_DEVICE ValidateDistributionMin(const Distribution& dd)
      : d(dd)
  {}

  _CCCL_HOST_DEVICE bool operator()(void)
  {
    Engine e;

    bool result = true;

    for (int i = 0; i < 10000; ++i)
    {
      result &= (d(e) >= d.min());
    }

    return result;
  }

  Distribution d;
};

template <typename Distribution, typename Engine>
struct ValidateDistributionMax
{
  typedef Engine random_engine;

  _CCCL_HOST_DEVICE ValidateDistributionMax(const Distribution& dd)
      : d(dd)
  {}

  _CCCL_HOST_DEVICE bool operator()(void)
  {
    Engine e;

    bool result = true;

    for (int i = 0; i < 10000; ++i)
    {
      result &= (d(e) <= d.max());
    }

    return result;
  }

  Distribution d;
};

template <typename Distribution>
struct ValidateDistributionEqual
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    return d0 == d1;
  }

  Distribution d0, d1;
};

template <typename Distribution>
struct ValidateDistributionUnqual
{
  _CCCL_HOST_DEVICE bool operator()(void) const
  {
    return d0 != d1;
  }

  Distribution d0, d1;
};

template <typename Engine, thrust::detail::uint64_t value_10000>
void TestEngineValidation()
{
  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), ValidateEngine<Engine>(value_10000));

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), ValidateEngine<Engine>(value_10000));

  ASSERT_EQUAL(true, d[0]);
}

template <typename Engine>
void TestEngineMax()
{
  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), ValidateEngineMax<Engine>());

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), ValidateEngineMax<Engine>());

  ASSERT_EQUAL(true, d[0]);
}

template <typename Engine>
void TestEngineMin()
{
  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), ValidateEngineMin<Engine>());

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), ValidateEngineMin<Engine>());

  ASSERT_EQUAL(true, d[0]);
}

template <typename Engine>
void TestEngineSaveRestore()
{
  // create a default engine
  Engine e0;

  // run it for a while
  e0.discard(10000);

  // save it
  std::stringstream ss;
  ss << e0;

  // run it a while longer
  e0.discard(10000);

  // restore old state
  Engine e1;
  ss >> e1;

  // run e1 a while longer
  e1.discard(10000);

  // both should return the same result

  ASSERT_EQUAL(e0(), e1());
}

template <typename Engine>
void TestEngineEqual()
{
  ValidateEngineEqual<Engine> f;

  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), f);

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), f);

  ASSERT_EQUAL(true, d[0]);
}

template <typename Engine>
void TestEngineUnequal()
{
  ValidateEngineUnequal<Engine> f;

  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), f);

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), f);

  ASSERT_EQUAL(true, d[0]);
}

void TestRanlux24BaseValidation()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineValidation<Engine, 7937952u>();
}
DECLARE_UNITTEST(TestRanlux24BaseValidation);

void TestRanlux24BaseMin()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestRanlux24BaseMin);

void TestRanlux24BaseMax()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestRanlux24BaseMax);

void TestRanlux24BaseSaveRestore()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestRanlux24BaseSaveRestore);

void TestRanlux24BaseEqual()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestRanlux24BaseEqual);

void TestRanlux24BaseUnequal()
{
  typedef thrust::random::ranlux24_base Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestRanlux24BaseUnequal);

void TestRanlux48BaseValidation()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineValidation<Engine, 192113843633948ull>();
}
DECLARE_UNITTEST(TestRanlux48BaseValidation);

void TestRanlux48BaseMin()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestRanlux48BaseMin);

void TestRanlux48BaseMax()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestRanlux48BaseMax);

void TestRanlux48BaseSaveRestore()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestRanlux48BaseSaveRestore);

void TestRanlux48BaseEqual()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestRanlux48BaseEqual);

#if defined(__INTEL_COMPILER) && 1800 >= __INTEL_COMPILER
void TestRanlux48BaseUnequal()
{
  // ICPC has a known failure with this test.
  // See nvbug 200414000.
  KNOWN_FAILURE;
}
#else
void TestRanlux48BaseUnequal()
{
  typedef thrust::random::ranlux48_base Engine;

  TestEngineUnequal<Engine>();
}
#endif
DECLARE_UNITTEST(TestRanlux48BaseUnequal);

void TestMinstdRandValidation()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineValidation<Engine, 399268537u>();
}
DECLARE_UNITTEST(TestMinstdRandValidation);

void TestMinstdRandMin()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestMinstdRandMin);

void TestMinstdRandMax()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestMinstdRandMax);

void TestMinstdRandSaveRestore()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestMinstdRandSaveRestore);

void TestMinstdRandEqual()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestMinstdRandEqual);

void TestMinstdRandUnequal()
{
  typedef thrust::random::minstd_rand Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestMinstdRandUnequal);

void TestMinstdRand0Validation()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineValidation<Engine, 1043618065u>();
}
DECLARE_UNITTEST(TestMinstdRand0Validation);

void TestMinstdRand0Min()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestMinstdRand0Min);

void TestMinstdRand0Max()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestMinstdRand0Max);

void TestMinstdRand0SaveRestore()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestMinstdRand0SaveRestore);

void TestMinstdRand0Equal()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestMinstdRand0Equal);

void TestMinstdRand0Unequal()
{
  typedef thrust::random::minstd_rand0 Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestMinstdRand0Unequal);

void TestTaus88Validation()
{
  typedef thrust::random::taus88 Engine;

  TestEngineValidation<Engine, 3535848941ull>();
}
DECLARE_UNITTEST(TestTaus88Validation);

void TestTaus88Min()
{
  typedef thrust::random::taus88 Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestTaus88Min);

void TestTaus88Max()
{
  typedef thrust::random::taus88 Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestTaus88Max);

void TestTaus88SaveRestore()
{
  typedef thrust::random::taus88 Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestTaus88SaveRestore);

void TestTaus88Equal()
{
  typedef thrust::random::taus88 Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestTaus88Equal);

void TestTaus88Unequal()
{
  typedef thrust::random::taus88 Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestTaus88Unequal);

void TestRanlux24Validation()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineValidation<Engine, 9901578>();
}
DECLARE_UNITTEST(TestRanlux24Validation);

void TestRanlux24Min()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestRanlux24Min);

void TestRanlux24Max()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestRanlux24Max);

void TestRanlux24SaveRestore()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestRanlux24SaveRestore);

void TestRanlux24Equal()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestRanlux24Equal);

void TestRanlux24Unequal()
{
  typedef thrust::random::ranlux24 Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestRanlux24Unequal);

void TestRanlux48Validation()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineValidation<Engine, 88229545517833ull>();
}
DECLARE_UNITTEST(TestRanlux48Validation);

void TestRanlux48Min()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineMin<Engine>();
}
DECLARE_UNITTEST(TestRanlux48Min);

void TestRanlux48Max()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineMax<Engine>();
}
DECLARE_UNITTEST(TestRanlux48Max);

void TestRanlux48SaveRestore()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineSaveRestore<Engine>();
}
DECLARE_UNITTEST(TestRanlux48SaveRestore);

void TestRanlux48Equal()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineEqual<Engine>();
}
DECLARE_UNITTEST(TestRanlux48Equal);

void TestRanlux48Unequal()
{
  typedef thrust::random::ranlux48 Engine;

  TestEngineUnequal<Engine>();
}
DECLARE_UNITTEST(TestRanlux48Unequal);

THRUST_DISABLE_MSVC_WARNING_BEGIN(4305) // truncation warning
template <typename Distribution, typename Validator>
void ValidateDistributionCharacteristic()
{
  typedef typename Validator::random_engine Engine;

  // test default-constructed Distribution

  // test host
  thrust::host_vector<bool> h(1);
  thrust::generate(h.begin(), h.end(), Validator(Distribution()));

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::device_vector<bool> d(1);
  thrust::generate(d.begin(), d.end(), Validator(Distribution()));

  ASSERT_EQUAL(true, d[0]);

  // test distribution & engine with comparable ranges
  // only do this if they have the same result_type
  if (::cuda::std::is_same<typename Distribution::result_type, typename Engine::result_type>::value)
  {
    // test Distribution with same range as engine

    // test host
    thrust::generate(h.begin(), h.end(), Validator(Distribution(Engine::min, Engine::max)));

    ASSERT_EQUAL(true, h[0]);

    // test device
    thrust::generate(d.begin(), d.end(), Validator(Distribution(Engine::min, Engine::max)));

    ASSERT_EQUAL(true, d[0]);

    // test Distribution with smaller range than engine

    // test host
    typename Distribution::result_type engine_range = Engine::max - Engine::min;
    thrust::generate(h.begin(), h.end(), Validator(Distribution(engine_range / 3, (2 * engine_range) / 3)));

    ASSERT_EQUAL(true, h[0]);

    // test device
    thrust::generate(d.begin(), d.end(), Validator(Distribution(engine_range / 3, (2 * engine_range) / 3)));

    ASSERT_EQUAL(true, d[0]);
  }

  // test Distribution with a very small range

  // test host
  thrust::generate(h.begin(), h.end(), Validator(Distribution(1, 6)));

  ASSERT_EQUAL(true, h[0]);

  // test device
  thrust::generate(d.begin(), d.end(), Validator(Distribution(1, 6)));

  ASSERT_EQUAL(true, d[0]);
}
THRUST_DISABLE_MSVC_WARNING_END(4305)

template <typename Distribution>
void TestDistributionSaveRestore()
{
  // create a default distribution
  Distribution d0(7, 13);

  // save it
  std::stringstream ss;
  ss << d0;

  // restore old state
  Distribution d1;
  ss >> d1;

  ASSERT_EQUAL(d0, d1);
}

void TestUniformIntDistributionMin()
{
  typedef thrust::random::uniform_int_distribution<int> int_dist;
  typedef thrust::random::uniform_int_distribution<unsigned int> uint_dist;

  ValidateDistributionCharacteristic<int_dist, ValidateDistributionMin<int_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<uint_dist, ValidateDistributionMin<uint_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestUniformIntDistributionMin);

void TestUniformIntDistributionMax()
{
  typedef thrust::random::uniform_int_distribution<int> int_dist;
  typedef thrust::random::uniform_int_distribution<unsigned int> uint_dist;

  ValidateDistributionCharacteristic<int_dist, ValidateDistributionMax<int_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<uint_dist, ValidateDistributionMax<uint_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestUniformIntDistributionMax);

void TestUniformIntDistributionSaveRestore()
{
  typedef thrust::random::uniform_int_distribution<int> int_dist;
  typedef thrust::random::uniform_int_distribution<unsigned int> uint_dist;

  TestDistributionSaveRestore<int_dist>();
  TestDistributionSaveRestore<uint_dist>();
}
DECLARE_UNITTEST(TestUniformIntDistributionSaveRestore);

void TestUniformRealDistributionMin()
{
  typedef thrust::random::uniform_real_distribution<float> float_dist;
  typedef thrust::random::uniform_real_distribution<double> double_dist;

  ValidateDistributionCharacteristic<float_dist, ValidateDistributionMin<float_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<double_dist, ValidateDistributionMin<double_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestUniformRealDistributionMin);

void TestUniformRealDistributionMax()
{
  typedef thrust::random::uniform_real_distribution<float> float_dist;
  typedef thrust::random::uniform_real_distribution<double> double_dist;

  ValidateDistributionCharacteristic<float_dist, ValidateDistributionMax<float_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<double_dist, ValidateDistributionMax<double_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestUniformRealDistributionMax);

void TestUniformRealDistributionSaveRestore()
{
  typedef thrust::random::uniform_real_distribution<float> float_dist;
  typedef thrust::random::uniform_real_distribution<double> double_dist;

  TestDistributionSaveRestore<float_dist>();
  TestDistributionSaveRestore<double_dist>();
}
DECLARE_UNITTEST(TestUniformRealDistributionSaveRestore);

void TestNormalDistributionMin()
{
  typedef thrust::random::normal_distribution<float> float_dist;
  typedef thrust::random::normal_distribution<double> double_dist;

  ValidateDistributionCharacteristic<float_dist, ValidateDistributionMin<float_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<double_dist, ValidateDistributionMin<double_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestNormalDistributionMin);

void TestNormalDistributionMax()
{
  typedef thrust::random::normal_distribution<float> float_dist;
  typedef thrust::random::normal_distribution<double> double_dist;

  ValidateDistributionCharacteristic<float_dist, ValidateDistributionMax<float_dist, thrust::minstd_rand>>();
  ValidateDistributionCharacteristic<double_dist, ValidateDistributionMax<double_dist, thrust::minstd_rand>>();
}
DECLARE_UNITTEST(TestNormalDistributionMax);

void TestNormalDistributionSaveRestore()
{
  typedef thrust::random::normal_distribution<float> float_dist;
  typedef thrust::random::normal_distribution<double> double_dist;

  TestDistributionSaveRestore<float_dist>();
  TestDistributionSaveRestore<double_dist>();
}
DECLARE_UNITTEST(TestNormalDistributionSaveRestore);
