///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

//
// Compare arithmetic results using fixed_int to GMP results.
//

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

//
// This ensures all our code gets tested, even though it may
// not be the fastest configuration in normal use:
//
#define BOOST_MP_USE_LIMB_SHIFT

#include <boost/multiprecision/gmp.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include "timer.hpp"
#include "test.hpp"

#ifdef _MSC_VER
#pragma warning(disable : 4127) //  Conditional expression is constant
#endif

#ifndef TEST
#define TEST 0
#endif

template <class T>
T generate_random(unsigned bits_wanted)
{
   static boost::random::mt19937               gen;
   typedef boost::random::mt19937::result_type random_type;

   T        max_val;
   unsigned digits;
   if (std::numeric_limits<T>::is_bounded && (bits_wanted == (unsigned)std::numeric_limits<T>::digits))
   {
      max_val = (std::numeric_limits<T>::max)();
      digits  = std::numeric_limits<T>::digits;
   }
   else
   {
      max_val = T(1) << bits_wanted;
      digits  = bits_wanted;
   }

   unsigned bits_per_r_val = std::numeric_limits<random_type>::digits - 1;
   while ((random_type(1) << bits_per_r_val) > (gen.max)())
      --bits_per_r_val;

   unsigned terms_needed = digits / bits_per_r_val + 1;

   T val = 0;
   for (unsigned i = 0; i < terms_needed; ++i)
   {
      val *= (gen.max)();
      val += gen();
   }
   val %= max_val;
   return val;
}

template <class T>
struct is_checked_cpp_int : public std::integral_constant<bool, false>
{};
template <std::size_t MinBits, std::size_t MaxBits, boost::multiprecision::cpp_integer_type SignType, class Allocator, boost::multiprecision::expression_template_option ET>
struct is_checked_cpp_int<boost::multiprecision::number<boost::multiprecision::cpp_int_backend<MinBits, MaxBits, SignType, boost::multiprecision::checked, Allocator>, ET> > : public std::integral_constant<bool, true>
{};

template <class N>
typename std::enable_if<boost::multiprecision::backends::is_fixed_precision<typename N::backend_type>::value && !is_checked_cpp_int<N>::value>::type test(const N&)
{
   using namespace boost::multiprecision;

   static unsigned last_error_count = 0;

   timer tim;

   do
   {
      // Test modular arithmetic by filling all the bits of our test type:
      mpz_int f = generate_random<mpz_int>(std::numeric_limits<N>::digits + 2);
      mpz_int g = generate_random<mpz_int>(std::numeric_limits<N>::digits + 2);
      mpz_int mask(1);
      mask <<= std::numeric_limits<N>::digits;
      --mask;
      f &= mask;
      g &= mask;
      mpz_int r = (f * g) & mask;

      N f1(f);
      N g1(g);
      N r1 = f1 * g1;
      BOOST_CHECK_EQUAL(r1.str(), r.str());

      if (last_error_count != (unsigned)boost::detail::test_errors())
      {
         last_error_count = boost::detail::test_errors();
         std::cout << std::hex << std::showbase;
         std::cout << f1 << std::endl;
         std::cout << f << std::endl;
         std::cout << g1 << std::endl;
         std::cout << g << std::endl;
         std::cout << r1 << std::endl;
         std::cout << r << std::endl;
      }

      static boost::random::mt19937             gen;
      boost::random::uniform_int_distribution<> d(12, std::numeric_limits<N>::digits);
      f = generate_random<mpz_int>(d(gen));
      g = generate_random<mpz_int>(d(gen));
      r = (f * g) & mask;

      f1 = N(f);
      g1 = N(g);
      r1 = f1 * g1;
      BOOST_CHECK_EQUAL(r1.str(), r.str());

      if (last_error_count != (unsigned)boost::detail::test_errors())
      {
         last_error_count = boost::detail::test_errors();
         std::cout << std::hex << std::showbase;
         std::cout << f1 << std::endl;
         std::cout << f << std::endl;
         std::cout << g1 << std::endl;
         std::cout << g << std::endl;
         std::cout << r1 << std::endl;
         std::cout << r << std::endl;
      }

#ifndef CI_SUPPRESS_KNOWN_ISSUES
      if (tim.elapsed() > 200)
#else
      if (tim.elapsed() > 25)
#endif
      {
         std::cout << "Timeout reached, aborting tests now....\n";
         break;
      }

   } while (true);
   //
   // Special cases:
   //
   mpz_int mask;
   if (std::numeric_limits<N>::is_bounded)
      mask = mpz_int((std::numeric_limits<N>::max)());
   mpz_int a, b;
   N       x, y;

   unsigned upper_limit = std::numeric_limits<N>::is_bounded ? std::numeric_limits<N>::digits - 1 : 8192 * 2;
   for (unsigned i = 1024; i < upper_limit; i *= 2)
   {
      a = 1;
      a <<= i;
      --a;
      x = 1;
      x <<= i;
      --x;

      b = a * a;
      if (std::numeric_limits<N>::is_bounded)
         b &= mask;
      y = x * x;

      BOOST_CHECK_EQUAL(y.str(), b.str());

      if (last_error_count != (unsigned)boost::detail::test_errors())
      {
         last_error_count = boost::detail::test_errors();
         std::cout << std::hex << std::showbase;
         std::cout << a << std::endl;
         std::cout << x << std::endl;
         std::cout << b << std::endl;
         std::cout << y << std::endl;
      }
   }
}
template <class N>
typename std::enable_if<!(boost::multiprecision::backends::is_fixed_precision<typename N::backend_type>::value && !is_checked_cpp_int<N>::value)>::type test(const N&)
{
   using namespace boost::multiprecision;

   static unsigned last_error_count = 0;

   timer tim;

   mpz_int mask;
   if (std::numeric_limits<N>::is_bounded)
      mask = mpz_int((std::numeric_limits<N>::max)());
   do
   {
      // Test modular arithmetic by filling all the bits of our test type:
      static boost::random::mt19937             gen;
      boost::random::uniform_int_distribution<> d(12, std::numeric_limits<N>::is_bounded ? std::numeric_limits<N>::digits : 100000);
      mpz_int                                   f = generate_random<mpz_int>(d(gen));
      mpz_int                                   g = generate_random<mpz_int>(d(gen));
      mpz_int                                   r = f * g;
      if (std::numeric_limits<N>::is_bounded)
         r &= mask;

      N f1(f);
      N g1(g);
      N r1 = f1 * g1;
      BOOST_CHECK_EQUAL(r1.str(), r.str());

      if (last_error_count != (unsigned)boost::detail::test_errors())
      {
         last_error_count = boost::detail::test_errors();
         std::cout << std::hex << std::showbase;
         std::cout << f1 << std::endl;
         std::cout << f << std::endl;
         std::cout << g1 << std::endl;
         std::cout << g << std::endl;
         std::cout << r1 << std::endl;
         std::cout << r << std::endl;
      }

#ifndef CI_SUPPRESS_KNOWN_ISSUES
      if (tim.elapsed() > 200)
#else
      if (tim.elapsed() > 25)
#endif
      {
         std::cout << "Timeout reached, aborting tests now....\n";
         break;
      }

   } while (true);
   //
   // Special cases:
   //
   mpz_int a, b;
   N       x, y;

   unsigned upper_limit = std::numeric_limits<N>::is_bounded ? std::numeric_limits<N>::digits - 1 : 8192 * 2;
   for (unsigned i = 1024; i < upper_limit; i *= 2)
   {
      a = 1;
      a <<= i;
      --a;
      x = 1;
      x <<= i;
      --x;

      b = a * a;
      if (std::numeric_limits<N>::is_bounded)
         b &= mask;
      y = x * x;

      BOOST_CHECK_EQUAL(y.str(), b.str());

      if (last_error_count != (unsigned)boost::detail::test_errors())
      {
         last_error_count = boost::detail::test_errors();
         std::cout << std::hex << std::showbase;
         std::cout << a << std::endl;
         std::cout << x << std::endl;
         std::cout << b << std::endl;
         std::cout << y << std::endl;
      }
   }
}

int main()
{
   using namespace boost::multiprecision;

#if (TEST == 1) || (TEST == 0)
   test(cpp_int());
#endif
#if (TEST == 2) || (TEST == 0)
   test(number<cpp_int_backend<8192, 8192, signed_magnitude, unchecked, void> >());
#endif
#if (TEST == 3) || (TEST == 0)
   test(number<cpp_int_backend<8192, 8192, signed_magnitude, unchecked, std::allocator<char> > >());
#endif
#if (TEST == 4) || (TEST == 0)
   test(number<cpp_int_backend<8192, 8192, unsigned_magnitude, unchecked> >());
#endif
   return boost::report_errors();
}
