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

#ifndef BOOST_MULTIPRECISION_MPC_HPP
#define BOOST_MULTIPRECISION_MPC_HPP

#include <boost/multiprecision/number.hpp>
#include <boost/cstdint.hpp>
#include <boost/multiprecision/detail/digits.hpp>
#include <boost/multiprecision/traits/is_variable_precision.hpp>
#include <boost/multiprecision/mpfr.hpp>
#include <boost/multiprecision/logged_adaptor.hpp>
#include <boost/functional/hash_fwd.hpp>
#include <mpc.h>
#include <cmath>
#include <algorithm>
#include <complex>

#ifndef BOOST_MULTIPRECISION_MPFI_DEFAULT_PRECISION
#  define BOOST_MULTIPRECISION_MPFI_DEFAULT_PRECISION 20
#endif

namespace boost{
namespace multiprecision{
namespace backends{

template <unsigned digits10>
struct mpc_complex_backend;

} // namespace backends

template <unsigned digits10>
struct number_category<backends::mpc_complex_backend<digits10> > : public mpl::int_<number_kind_complex>{};

namespace backends{

namespace detail{

inline void mpc_copy_precision(mpc_t dest, const mpc_t src)
{
   mpfr_prec_t p_dest = mpc_get_prec(dest);
   mpfr_prec_t p_src = mpc_get_prec(src);
   if (p_dest != p_src)
      mpc_set_prec(dest, p_src);
}
inline void mpc_copy_precision(mpc_t dest, const mpc_t src1, const mpc_t src2)
{
   mpfr_prec_t p_dest = mpc_get_prec(dest);
   mpfr_prec_t p_src1 = mpc_get_prec(src1);
   mpfr_prec_t p_src2 = mpc_get_prec(src2);
   if (p_src2 > p_src1)
      p_src1 = p_src2;
   if (p_dest != p_src1)
      mpc_set_prec(dest, p_src1);
}

template <unsigned digits10>
struct mpc_complex_imp
{
#ifdef BOOST_HAS_LONG_LONG
   typedef mpl::list<long, boost::long_long_type>                     signed_types;
   typedef mpl::list<unsigned long, boost::ulong_long_type>   unsigned_types;
#else
   typedef mpl::list<long>                                signed_types;
   typedef mpl::list<unsigned long>                       unsigned_types;
#endif
   typedef mpl::list<double, long double>                 float_types;
   typedef long                                           exponent_type;

   mpc_complex_imp()
   {
      mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_ui(m_data, 0u, GMP_RNDN);
   }
   mpc_complex_imp(unsigned digits2)
   {
      mpc_init2(m_data, digits2);
      mpc_set_ui(m_data, 0u, GMP_RNDN);
   }

   mpc_complex_imp(const mpc_complex_imp& o)
   {
      mpc_init2(m_data, mpc_get_prec(o.m_data));
      if(o.m_data[0].re[0]._mpfr_d)
         mpc_set(m_data, o.m_data, GMP_RNDN);
   }
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_imp(mpc_complex_imp&& o) BOOST_NOEXCEPT
   {
      m_data[0] = o.m_data[0];
      o.m_data[0].re[0]._mpfr_d = 0;
   }
#endif
   mpc_complex_imp& operator = (const mpc_complex_imp& o)
   {
      if( (o.m_data[0].re[0]._mpfr_d) && (this != &o) )
      {
         if (m_data[0].re[0]._mpfr_d == 0)
            mpc_init2(m_data, mpc_get_prec(o.m_data));
         mpc_set(m_data, o.m_data, GMP_RNDD);
      }
      return *this;
   }
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_imp& operator = (mpc_complex_imp&& o) BOOST_NOEXCEPT
   {
      mpc_swap(m_data, o.m_data);
      return *this;
   }
#endif
#ifdef BOOST_HAS_LONG_LONG
#ifdef _MPFR_H_HAVE_INTMAX_T
   mpc_complex_imp& operator = (boost::ulong_long_type i)
   {
      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_uj(data(), i, GMP_RNDD);
      return *this;
   }
   mpc_complex_imp& operator = (boost::long_long_type i)
   {
      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_sj(data(), i, GMP_RNDD);
      return *this;
   }
#else
   mpc_complex_imp& operator = (boost::ulong_long_type i)
   {
      mpfr_float_backend<digits10> f(0uL, mpc_get_prec(m_data));
      f = i;
      mpc_set_fr(this->data(), f.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (boost::long_long_type i)
   {
      mpfr_float_backend<digits10> f(0uL, mpc_get_prec(m_data));
      f = i;
      mpc_set_fr(this->data(), f.data(), GMP_RNDN);
      return *this;
   }
#endif
#endif
   mpc_complex_imp& operator = (unsigned long i)
   {
      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_ui(m_data, i, GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (long i)
   {
      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_si(m_data, i, GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (double d)
   {
      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_d(m_data, d, GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (long double d)
   {
      if (m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_ld(m_data, d, GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (mpz_t i)
   {
      if (m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_z(m_data, i, GMP_RNDN);
      return *this;
   }
   mpc_complex_imp& operator = (gmp_int i)
   {
      if (m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));
      mpc_set_z(m_data, i.data(), GMP_RNDN);
      return *this;
   }

   mpc_complex_imp& operator = (const char* s)
   {
      using default_ops::eval_fpclassify;

      if(m_data[0].re[0]._mpfr_d == 0)
         mpc_init2(m_data, multiprecision::detail::digits10_2_2(digits10 ? digits10 : get_default_precision()));

      mpfr_float_backend<digits10> a(0uL, mpc_get_prec(m_data)), b(0uL, mpc_get_prec(m_data));

      if(s && (*s == '('))
      {
         std::string part;
         const char* p = ++s;
         while(*p && (*p != ',') && (*p != ')'))
            ++p;
         part.assign(s, p);
         if(part.size())
            a = part.c_str();
         else
            a = 0uL;
         s = p;
         if(*p && (*p != ')'))
         {
            ++p;
            while(*p && (*p != ')'))
               ++p;
            part.assign(s + 1, p);
         }
         else
            part.erase();
         if(part.size())
            b = part.c_str();
         else
            b = 0uL;
      }
      else
      {
         a = s;
         b = 0uL;
      }

      if(eval_fpclassify(a) == (int)FP_NAN)
      {
         mpc_set_fr(this->data(), a.data(), GMP_RNDN);
      }
      else if(eval_fpclassify(b) == (int)FP_NAN)
      {
         mpc_set_fr(this->data(), b.data(), GMP_RNDN);
      }
      else
      {
         mpc_set_fr_fr(m_data, a.data(), b.data(), GMP_RNDN);
      }
      return *this;
   }
   void swap(mpc_complex_imp& o) BOOST_NOEXCEPT
   {
      mpc_swap(m_data, o.m_data);
   }
   std::string str(std::streamsize digits, std::ios_base::fmtflags f)const
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);

      mpfr_float_backend<digits10> a(0uL, mpc_get_prec(m_data)), b(0uL, mpc_get_prec(m_data));

      mpc_real(a.data(), m_data, GMP_RNDD);
      mpc_imag(b.data(), m_data, GMP_RNDD);

      if(eval_is_zero(b))
         return a.str(digits, f);

      return "(" + a.str(digits, f) + "," + b.str(digits, f) + ")";
   }
   ~mpc_complex_imp() BOOST_NOEXCEPT
   {
      if(m_data[0].re[0]._mpfr_d)
         mpc_clear(m_data);
   }
   void negate() BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);
      mpc_neg(m_data, m_data, GMP_RNDD);
   }
   int compare(const mpc_complex_imp& o)const BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d && o.m_data[0].re[0]._mpfr_d);
      return mpc_cmp(m_data, o.m_data);
   }
   int compare(const mpc_complex_backend<digits10>& o)const BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d && o.m_data[0].re[0]._mpfr_d);
      return mpc_cmp(m_data, o.data());
   }
   int compare(long int i)const BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);
      return mpc_cmp_si(m_data, i);
   }
   int compare(unsigned long int i)const BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);
      static const unsigned long int max_val = (std::numeric_limits<long>::max)();
      if (i > max_val)
      {
         mpc_complex_imp d(mpc_get_prec(m_data));
         d = i;
         return compare(d);
      }
      return mpc_cmp_si(m_data, (long)i);
   }
   template <class V>
   int compare(const V& v)const BOOST_NOEXCEPT
   {
      mpc_complex_imp d(mpc_get_prec(m_data));
      d = v;
      return compare(d);
   }
   mpc_t& data() BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);
      return m_data;
   }
   const mpc_t& data()const BOOST_NOEXCEPT
   {
      BOOST_ASSERT(m_data[0].re[0]._mpfr_d);
      return m_data;
   }
protected:
   mpc_t m_data;
   static unsigned& get_default_precision() BOOST_NOEXCEPT
   {
      static unsigned val = BOOST_MULTIPRECISION_MPFI_DEFAULT_PRECISION;
      return val;
   }
};

} // namespace detail

template <unsigned digits10>
struct mpc_complex_backend : public detail::mpc_complex_imp<digits10>
{
   mpc_complex_backend() : detail::mpc_complex_imp<digits10>() {}
   mpc_complex_backend(const mpc_complex_backend& o) : detail::mpc_complex_imp<digits10>(o) {}
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_backend(mpc_complex_backend&& o) : detail::mpc_complex_imp<digits10>(static_cast<detail::mpc_complex_imp<digits10>&&>(o)) {}
#endif
   template <unsigned D>
   mpc_complex_backend(const mpc_complex_backend<D>& val, typename enable_if_c<D <= digits10>::type* = 0)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D>
   explicit mpc_complex_backend(const mpc_complex_backend<D>& val, typename disable_if_c<D <= digits10>::type* = 0)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D>
   mpc_complex_backend(const mpfr_float_backend<D>& val, typename enable_if_c<D <= digits10>::type* = 0)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_fr(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D>
   explicit mpc_complex_backend(const mpfr_float_backend<D>& val, typename disable_if_c<D <= digits10>::type* = 0)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend(const mpc_t val)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend(const std::complex<float>& val)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   mpc_complex_backend(const std::complex<double>& val)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   mpc_complex_backend(const std::complex<long double>& val)
       : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_ld_ld(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   mpc_complex_backend(mpz_srcptr val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_z(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpz_srcptr val)
   {
      mpc_set_z(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(gmp_int const& val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_z(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend& operator=(gmp_int const& val)
   {
      mpc_set_z(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpf_srcptr val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_f(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpf_srcptr val)
   {
      mpc_set_f(this->m_data, val, GMP_RNDN);
      return *this;
   }
   template <unsigned D10>
   mpc_complex_backend(gmp_float<D10> const& val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_f(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D10>
   mpc_complex_backend& operator=(gmp_float<D10> const& val)
   {
      mpc_set_f(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpq_srcptr val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_q(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpq_srcptr val)
   {
      mpc_set_q(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(gmp_rational const& val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_q(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend& operator=(gmp_rational const& val)
   {
      mpc_set_q(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpfr_srcptr val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_fr(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpfr_srcptr val)
   {
      mpc_set_fr(this->m_data, val, GMP_RNDN);
      return *this;
   }
   template <unsigned D10, mpfr_allocation_type AllocationType>
   mpc_complex_backend(mpfr_float_backend<D10, AllocationType> const& val) : detail::mpc_complex_imp<digits10>()
   {
      mpc_set_fr(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D10, mpfr_allocation_type AllocationType>
   mpc_complex_backend& operator=(mpfr_float_backend<D10, AllocationType> const& val)
   {
      mpc_set_fr(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const mpc_complex_backend& o)
   {
      *static_cast<detail::mpc_complex_imp<digits10>*>(this) = static_cast<detail::mpc_complex_imp<digits10> const&>(o);
      return *this;
   }
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_backend& operator=(mpc_complex_backend&& o) BOOST_NOEXCEPT
   {
      *static_cast<detail::mpc_complex_imp<digits10>*>(this) = static_cast<detail::mpc_complex_imp<digits10>&&>(o);
      return *this;
   }
#endif
   template <class V>
   mpc_complex_backend& operator=(const V& v)
   {
      *static_cast<detail::mpc_complex_imp<digits10>*>(this) = v;
      return *this;
   }
   mpc_complex_backend& operator=(const mpc_t val)
   {
      mpc_set(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<float>& val)
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<double>& val)
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<long double>& val)
   {
      mpc_set_ld_ld(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   // We don't change our precision here, this is a fixed precision type:
   template <unsigned D>
   mpc_complex_backend& operator=(const mpc_complex_backend<D>& val)
   {
      mpc_set(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
};

template <>
struct mpc_complex_backend<0> : public detail::mpc_complex_imp<0>
{
   mpc_complex_backend() : detail::mpc_complex_imp<0>() {}
   mpc_complex_backend(const mpc_t val)
      : detail::mpc_complex_imp<0>(mpc_get_prec(val))
   {
      mpc_set(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend(const mpc_complex_backend& o) : detail::mpc_complex_imp<0>(o) {}
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_backend(mpc_complex_backend&& o) BOOST_NOEXCEPT : detail::mpc_complex_imp<0>(static_cast<detail::mpc_complex_imp<0>&&>(o)) {}
#endif
   mpc_complex_backend(const mpc_complex_backend& o, unsigned digits10)
      : detail::mpc_complex_imp<0>(multiprecision::detail::digits10_2_2(digits10))
   {
      mpc_set(this->m_data, o.data(), GMP_RNDN);
   }
   template <unsigned D>
   mpc_complex_backend(const mpc_complex_backend<D>& val)
      : detail::mpc_complex_imp<0>(mpc_get_prec(val.data()))
   {
      mpc_set(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned D>
   mpc_complex_backend(const mpfr_float_backend<D>& val)
      : detail::mpc_complex_imp<0>(mpfr_get_prec(val.data()))
   {
      mpc_set_fr(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend(mpz_srcptr val) : detail::mpc_complex_imp<0>()
   {
      mpc_set_z(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpz_srcptr val)
   {
      mpc_set_z(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(gmp_int const& val) : detail::mpc_complex_imp<0>()
   {
      mpc_set_z(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend& operator=(gmp_int const& val)
   {
      mpc_set_z(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpf_srcptr val) : detail::mpc_complex_imp<0>((unsigned)mpf_get_prec(val))
   {
      mpc_set_f(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpf_srcptr val)
   {
      if ((mp_bitcnt_t)mpc_get_prec(data()) != mpf_get_prec(val))
      {
         mpc_complex_backend t(val);
         t.swap(*this);
      }
      else
         mpc_set_f(this->m_data, val, GMP_RNDN);
      return *this;
   }
   template <unsigned digits10>
   mpc_complex_backend(gmp_float<digits10> const& val) : detail::mpc_complex_imp<0>((unsigned)mpf_get_prec(val.data()))
   {
      mpc_set_f(this->m_data, val.data(), GMP_RNDN);
   }
   template <unsigned digits10>
   mpc_complex_backend& operator=(gmp_float<digits10> const& val)
   {
      if (mpc_get_prec(data()) != mpf_get_prec(val.data()))
      {
         mpc_complex_backend t(val);
         t.swap(*this);
      }
      else
         mpc_set_f(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpq_srcptr val) : detail::mpc_complex_imp<0>()
   {
      mpc_set_q(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpq_srcptr val)
   {
      mpc_set_q(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(gmp_rational const& val) : detail::mpc_complex_imp<0>()
   {
      mpc_set_q(this->m_data, val.data(), GMP_RNDN);
   }
   mpc_complex_backend& operator=(gmp_rational const& val)
   {
      mpc_set_q(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(mpfr_srcptr val) : detail::mpc_complex_imp<0>(mpfr_get_prec(val))
   {
      mpc_set_fr(this->m_data, val, GMP_RNDN);
   }
   mpc_complex_backend& operator=(mpfr_srcptr val)
   {
      if (mpc_get_prec(data()) != mpfr_get_prec(val))
      {
         mpc_complex_backend t(val);
         t.swap(*this);
      }
      else
         mpc_set_fr(this->m_data, val, GMP_RNDN);
      return *this;
   }
   mpc_complex_backend(const std::complex<float>& val)
      : detail::mpc_complex_imp<0>()
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   mpc_complex_backend(const std::complex<double>& val)
      : detail::mpc_complex_imp<0>()
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   mpc_complex_backend(const std::complex<long double>& val)
      : detail::mpc_complex_imp<0>()
   {
      mpc_set_ld_ld(this->m_data, val.real(), val.imag(), GMP_RNDN);
   }
   // Construction with precision:
   template <class T, class U>
   mpc_complex_backend(const T& a, const U& b, unsigned digits10)
      : detail::mpc_complex_imp<0>(multiprecision::detail::digits10_2_2(digits10))
   {
      // We can't use assign_components here because it copies the precision of
      // a and b, not digits10....
      mpfr_float ca(a), cb(b);
      mpc_set_fr_fr(this->data(), ca.backend().data(), cb.backend().data(), GMP_RNDN);
   }
   template <unsigned N>
   mpc_complex_backend(const mpfr_float_backend<N>& a, const mpfr_float_backend<N>& b, unsigned digits10)
      : detail::mpc_complex_imp<0>(multiprecision::detail::digits10_2_2(digits10))
   {
      mpc_set_fr_fr(this->data(), a.data(), b.data(), GMP_RNDN);
   }

   mpc_complex_backend& operator=(const mpc_complex_backend& o)
   {
      if (this != &o)
      {
         detail::mpc_copy_precision(this->m_data, o.data());
         mpc_set(this->m_data, o.data(), GMP_RNDN);
      }
      return *this;
   }
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   mpc_complex_backend& operator=(mpc_complex_backend&& o) BOOST_NOEXCEPT
   {
      *static_cast<detail::mpc_complex_imp<0>*>(this) = static_cast<detail::mpc_complex_imp<0> &&>(o);
      return *this;
   }
#endif
   template <class V>
   mpc_complex_backend& operator=(const V& v)
   {
      *static_cast<detail::mpc_complex_imp<0>*>(this) = v;
      return *this;
   }
   mpc_complex_backend& operator=(const mpc_t val)
   {
      mpc_set_prec(this->m_data, mpc_get_prec(val));
      mpc_set(this->m_data, val, GMP_RNDN);
      return *this;
   }
   template <unsigned D>
   mpc_complex_backend& operator=(const mpc_complex_backend<D>& val)
   {
      mpc_set_prec(this->m_data, mpc_get_prec(val.data()));
      mpc_set(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   template <unsigned D>
   mpc_complex_backend& operator=(const mpfr_float_backend<D>& val)
   {
      mpc_set_prec(this->m_data, mpfr_get_prec(val.data()));
      mpc_set_fr(this->m_data, val.data(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<float>& val)
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<double>& val)
   {
      mpc_set_d_d(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   mpc_complex_backend& operator=(const std::complex<long double>& val)
   {
      mpc_set_ld_ld(this->m_data, val.real(), val.imag(), GMP_RNDN);
      return *this;
   }
   static unsigned default_precision() BOOST_NOEXCEPT
   {
      return get_default_precision();
   }
   static void default_precision(unsigned v) BOOST_NOEXCEPT
   {
      get_default_precision() = v;
   }
   unsigned precision()const BOOST_NOEXCEPT
   {
      return multiprecision::detail::digits2_2_10(mpc_get_prec(this->m_data));
   }
   void precision(unsigned digits10) BOOST_NOEXCEPT
   {
      mpfr_prec_round(mpc_realref(this->m_data), multiprecision::detail::digits10_2_2((digits10)), GMP_RNDN);
      mpfr_prec_round(mpc_imagref(this->m_data), multiprecision::detail::digits10_2_2((digits10)), GMP_RNDN);
   }
};

template <unsigned digits10, class T>
inline typename enable_if<is_arithmetic<T>, bool>::type eval_eq(const mpc_complex_backend<digits10>& a, const T& b) BOOST_NOEXCEPT
{
   return a.compare(b) == 0;
}
template <unsigned digits10, class T>
inline typename enable_if<is_arithmetic<T>, bool>::type eval_lt(const mpc_complex_backend<digits10>& a, const T& b) BOOST_NOEXCEPT
{
   return a.compare(b) < 0;
}
template <unsigned digits10, class T>
inline typename enable_if<is_arithmetic<T>, bool>::type eval_gt(const mpc_complex_backend<digits10>& a, const T& b) BOOST_NOEXCEPT
{
   return a.compare(b) > 0;
}

template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& result, const mpc_complex_backend<D2>& o)
{
   mpc_add(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& result, const mpfr_float_backend<D2>& o)
{
   mpc_add_fr(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& result, const mpc_complex_backend<D2>& o)
{
   mpc_sub(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& result, const mpfr_float_backend<D2>& o)
{
   mpc_sub_fr(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& result, const mpc_complex_backend<D2>& o)
{
   if((void*)&result == (void*)&o)
      mpc_sqr(result.data(), o.data(), GMP_RNDN);
   else
      mpc_mul(result.data(), result.data(), o.data(), GMP_RNDN);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& result, const mpfr_float_backend<D2>& o)
{
   mpc_mul_fr(result.data(), result.data(), o.data(), GMP_RNDN);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& result, const mpc_complex_backend<D2>& o)
{
   mpc_div(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& result, const mpfr_float_backend<D2>& o)
{
   mpc_div_fr(result.data(), result.data(), o.data(), GMP_RNDD);
}
template <unsigned digits10>
inline void eval_add(mpc_complex_backend<digits10>& result, unsigned long i)
{
   mpc_add_ui(result.data(), result.data(), i, GMP_RNDN);
}
template <unsigned digits10>
inline void eval_subtract(mpc_complex_backend<digits10>& result, unsigned long i)
{
   mpc_sub_ui(result.data(), result.data(), i, GMP_RNDN);
}
template <unsigned digits10>
inline void eval_multiply(mpc_complex_backend<digits10>& result, unsigned long i)
{
   mpc_mul_ui(result.data(), result.data(), i, GMP_RNDN);
}
template <unsigned digits10>
inline void eval_divide(mpc_complex_backend<digits10>& result, unsigned long i)
{
   mpc_div_ui(result.data(), result.data(), i, GMP_RNDN);
}
template <unsigned digits10>
inline void eval_add(mpc_complex_backend<digits10>& result, long i)
{
   if(i > 0)
      mpc_add_ui(result.data(), result.data(), i, GMP_RNDN);
   else
      mpc_sub_ui(result.data(), result.data(), boost::multiprecision::detail::unsigned_abs(i), GMP_RNDN);
}
template <unsigned digits10>
inline void eval_subtract(mpc_complex_backend<digits10>& result, long i)
{
   if(i > 0)
      mpc_sub_ui(result.data(), result.data(), i, GMP_RNDN);
   else
      mpc_add_ui(result.data(), result.data(), boost::multiprecision::detail::unsigned_abs(i), GMP_RNDN);
}
template <unsigned digits10>
inline void eval_multiply(mpc_complex_backend<digits10>& result, long i)
{
   mpc_mul_ui(result.data(), result.data(), boost::multiprecision::detail::unsigned_abs(i), GMP_RNDN);
   if(i < 0)
      mpc_neg(result.data(), result.data(), GMP_RNDN);
}
template <unsigned digits10>
inline void eval_divide(mpc_complex_backend<digits10>& result, long i)
{
   mpc_div_ui(result.data(), result.data(), boost::multiprecision::detail::unsigned_abs(i), GMP_RNDN);
   if(i < 0)
      mpc_neg(result.data(), result.data(), GMP_RNDN);
}
//
// Specialised 3 arg versions of the basic operators:
//
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_add(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_add(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_add(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpfr_float_backend<D3>& y)
{
   mpc_add_fr(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_add(mpc_complex_backend<D1>& a, const mpfr_float_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_add_fr(a.data(), y.data(), x.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, unsigned long y)
{
   mpc_add_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, long y)
{
   if(y < 0)
      mpc_sub_ui(a.data(), x.data(), boost::multiprecision::detail::unsigned_abs(y), GMP_RNDD);
   else
      mpc_add_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& a, unsigned long x, const mpc_complex_backend<D2>& y)
{
   mpc_add_ui(a.data(), y.data(), x, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_add(mpc_complex_backend<D1>& a, long x, const mpc_complex_backend<D2>& y)
{
   if(x < 0)
   {
      mpc_ui_sub(a.data(), boost::multiprecision::detail::unsigned_abs(x), y.data(), GMP_RNDN);
      mpc_neg(a.data(), a.data(), GMP_RNDD);
   }
   else
      mpc_add_ui(a.data(), y.data(), x, GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_subtract(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_sub(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_subtract(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpfr_float_backend<D3>& y)
{
   mpc_sub_fr(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_subtract(mpc_complex_backend<D1>& a, const mpfr_float_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_fr_sub(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, unsigned long y)
{
   mpc_sub_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, long y)
{
   if(y < 0)
      mpc_add_ui(a.data(), x.data(), boost::multiprecision::detail::unsigned_abs(y), GMP_RNDD);
   else
      mpc_sub_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& a, unsigned long x, const mpc_complex_backend<D2>& y)
{
   mpc_ui_sub(a.data(), x, y.data(), GMP_RNDN);
}
template <unsigned D1, unsigned D2>
inline void eval_subtract(mpc_complex_backend<D1>& a, long x, const mpc_complex_backend<D2>& y)
{
   if(x < 0)
   {
      mpc_add_ui(a.data(), y.data(), boost::multiprecision::detail::unsigned_abs(x), GMP_RNDD);
      mpc_neg(a.data(), a.data(), GMP_RNDD);
   }
   else
      mpc_ui_sub(a.data(), x, y.data(), GMP_RNDN);
}

template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_multiply(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   if((void*)&x == (void*)&y)
      mpc_sqr(a.data(), x.data(), GMP_RNDD);
   else
      mpc_mul(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_multiply(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpfr_float_backend<D3>& y)
{
   mpc_mul_fr(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_multiply(mpc_complex_backend<D1>& a, const mpfr_float_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_mul_fr(a.data(), y.data(), x.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, unsigned long y)
{
   mpc_mul_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, long y)
{
   if(y < 0)
   {
      mpc_mul_ui(a.data(), x.data(), boost::multiprecision::detail::unsigned_abs(y), GMP_RNDD);
      a.negate();
   }
   else
      mpc_mul_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& a, unsigned long x, const mpc_complex_backend<D2>& y)
{
   mpc_mul_ui(a.data(), y.data(), x, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_multiply(mpc_complex_backend<D1>& a, long x, const mpc_complex_backend<D2>& y)
{
   if(x < 0)
   {
      mpc_mul_ui(a.data(), y.data(), boost::multiprecision::detail::unsigned_abs(x), GMP_RNDD);
      mpc_neg(a.data(), a.data(), GMP_RNDD);
   }
   else
      mpc_mul_ui(a.data(), y.data(), x, GMP_RNDD);
}

template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_divide(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_div(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_divide(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, const mpfr_float_backend<D3>& y)
{
   mpc_div_fr(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2, unsigned D3>
inline void eval_divide(mpc_complex_backend<D1>& a, const mpfr_float_backend<D2>& x, const mpc_complex_backend<D3>& y)
{
   mpc_fr_div(a.data(), x.data(), y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, unsigned long y)
{
   mpc_div_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& a, const mpc_complex_backend<D2>& x, long y)
{
   if(y < 0)
   {
      mpc_div_ui(a.data(), x.data(), boost::multiprecision::detail::unsigned_abs(y), GMP_RNDD);
      a.negate();
   }
   else
      mpc_div_ui(a.data(), x.data(), y, GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& a, unsigned long x, const mpc_complex_backend<D2>& y)
{
   mpc_ui_div(a.data(), x, y.data(), GMP_RNDD);
}
template <unsigned D1, unsigned D2>
inline void eval_divide(mpc_complex_backend<D1>& a, long x, const mpc_complex_backend<D2>& y)
{
   if(x < 0)
   {
      mpc_ui_div(a.data(), boost::multiprecision::detail::unsigned_abs(x), y.data(), GMP_RNDD);
      mpc_neg(a.data(), a.data(), GMP_RNDD);
   }
   else
      mpc_ui_div(a.data(), x, y.data(), GMP_RNDD);
}

template <unsigned digits10>
inline bool eval_is_zero(const mpc_complex_backend<digits10>& val) BOOST_NOEXCEPT
{
   return (0 != mpfr_zero_p(mpc_realref(val.data()))) && (0 != mpfr_zero_p(mpc_imagref(val.data())));
}
template <unsigned digits10>
inline int eval_get_sign(const mpc_complex_backend<digits10>&)
{
   BOOST_STATIC_ASSERT_MSG(digits10 == UINT_MAX, "Complex numbers have no sign bit."); // designed to always fail
   return 0;
}

template <unsigned digits10>
inline void eval_convert_to(unsigned long* result, const mpc_complex_backend<digits10>& val)
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}
template <unsigned digits10>
inline void eval_convert_to(long* result, const mpc_complex_backend<digits10>& val)
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}
#ifdef _MPFR_H_HAVE_INTMAX_T
template <unsigned digits10>
inline void eval_convert_to(boost::ulong_long_type* result, const mpc_complex_backend<digits10>& val)
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}
template <unsigned digits10>
inline void eval_convert_to(boost::long_long_type* result, const mpc_complex_backend<digits10>& val)
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}
#endif
template <unsigned digits10>
inline void eval_convert_to(double* result, const mpc_complex_backend<digits10>& val) BOOST_NOEXCEPT
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}
template <unsigned digits10>
inline void eval_convert_to(long double* result, const mpc_complex_backend<digits10>& val) BOOST_NOEXCEPT
{
   if (0 == mpfr_zero_p(mpc_imagref(val.data())))
   {
      BOOST_THROW_EXCEPTION(std::runtime_error("Could not convert imaginary number to scalar."));
   }
   mpfr_float_backend<digits10> t;
   mpc_real(t.data(), val.data(), GMP_RNDN);
   eval_convert_to(result, t);
}

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, const mpfr_float_backend<D2, AllocationType>& a, const mpfr_float_backend<D2, AllocationType>& b)
{
   //
   // This is called from class number's constructors, so if we have variable
   // precision, then copy the precision of the source variables.
   //
   if (!D1)
   {
      unsigned long prec = std::max(mpfr_get_prec(a.data()), mpfr_get_prec(b.data()));
      mpc_set_prec(result.data(), prec);
   }
   using default_ops::eval_fpclassify;
   if(eval_fpclassify(a) == (int)FP_NAN)
   {
      mpc_set_fr(result.data(), a.data(), GMP_RNDN);
   }
   else if(eval_fpclassify(b) == (int)FP_NAN)
   {
      mpc_set_fr(result.data(), b.data(), GMP_RNDN);
   }
   else
   {
      mpc_set_fr_fr(result.data(), a.data(), b.data(), GMP_RNDN);
   }
}

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, unsigned long a, unsigned long b)
{
   mpc_set_ui_ui(result.data(), a, b, GMP_RNDN);
}

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, long a, long b)
{
   mpc_set_si_si(result.data(), a, b, GMP_RNDN);
}

#if defined(BOOST_HAS_LONG_LONG) && defined(_MPFR_H_HAVE_INTMAX_T)
template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, unsigned long long a, unsigned long long b)
{
   mpc_set_uj_uj(result.data(), a, b, GMP_RNDN);
}

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, long long a, long long b)
{
   mpc_set_sj_sj(result.data(), a, b, GMP_RNDN);
}
#endif

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, double a, double b)
{
   if ((boost::math::isnan)(a))
   {
      mpc_set_d(result.data(), a, GMP_RNDN);
   }
   else if ((boost::math::isnan)(b))
   {
      mpc_set_d(result.data(), b, GMP_RNDN);
   }
   else
   {
      mpc_set_d_d(result.data(), a, b, GMP_RNDN);
   }
}

template <unsigned D1, unsigned D2, mpfr_allocation_type AllocationType>
inline void assign_components(mpc_complex_backend<D1>& result, long double a, long double b)
{
   if ((boost::math::isnan)(a))
   {
      mpc_set_d(result.data(), a, GMP_RNDN);
   }
   else if ((boost::math::isnan)(b))
   {
      mpc_set_d(result.data(), b, GMP_RNDN);
   }
   else
   {
      mpc_set_ld_ld(result.data(), a, b, GMP_RNDN);
   }
}

//
// Native non-member operations:
//
template <unsigned Digits10>
inline void eval_sqrt(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& val)
{
   mpc_sqrt(result.data(), val.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_pow(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& b, const mpc_complex_backend<Digits10>& e)
{
   mpc_pow(result.data(), b.data(), e.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_exp(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_exp(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_log(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_log(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_log10(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_log10(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_sin(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_sin(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_cos(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_cos(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_tan(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_tan(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_asin(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_asin(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_acos(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_acos(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_atan(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_atan(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_sinh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_sinh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_cosh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_cosh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_tanh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_tanh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_asinh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_asinh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_acosh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_acosh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_atanh(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_atanh(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_conj(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_conj(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_proj(mpc_complex_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpc_proj(result.data(), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_real(mpfr_float_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpfr_set_prec(result.data(), mpfr_get_prec(mpc_realref(arg.data())));
   mpfr_set(result.data(), mpc_realref(arg.data()), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_imag(mpfr_float_backend<Digits10>& result, const mpc_complex_backend<Digits10>& arg)
{
   mpfr_set_prec(result.data(), mpfr_get_prec(mpc_imagref(arg.data())));
   mpfr_set(result.data(), mpc_imagref(arg.data()), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const mpfr_float_backend<Digits10>& arg)
{
   mpfr_set(mpc_imagref(result.data()), arg.data(), GMP_RNDN);
}

template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const mpfr_float_backend<Digits10>& arg)
{
   mpfr_set(mpc_realref(result.data()), arg.data(), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const gmp_int& arg)
{
   mpfr_set_z(mpc_realref(result.data()), arg.data(), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const gmp_rational& arg)
{
   mpfr_set_q(mpc_realref(result.data()), arg.data(), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const unsigned& arg)
{
   mpfr_set_ui(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const unsigned long& arg)
{
   mpfr_set_ui(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const int& arg)
{
   mpfr_set_si(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const long& arg)
{
   mpfr_set_si(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const float& arg)
{
   mpfr_set_flt(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const double& arg)
{
   mpfr_set_d(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const long double& arg)
{
   mpfr_set_ld(mpc_realref(result.data()), arg, GMP_RNDN);
}
#if defined(BOOST_HAS_LONG_LONG) && defined(_MPFR_H_HAVE_INTMAX_T)
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const unsigned long long& arg)
{
   mpfr_set_uj(mpc_realref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_real(mpc_complex_backend<Digits10>& result, const long long& arg)
{
   mpfr_set_sj(mpc_realref(result.data()), arg, GMP_RNDN);
}
#endif

template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const gmp_int& arg)
{
   mpfr_set_z(mpc_imagref(result.data()), arg.data(), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const gmp_rational& arg)
{
   mpfr_set_q(mpc_imagref(result.data()), arg.data(), GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const unsigned& arg)
{
   mpfr_set_ui(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const unsigned long& arg)
{
   mpfr_set_ui(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const int& arg)
{
   mpfr_set_si(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const long& arg)
{
   mpfr_set_si(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const float& arg)
{
   mpfr_set_flt(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const double& arg)
{
   mpfr_set_d(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const long double& arg)
{
   mpfr_set_ld(mpc_imagref(result.data()), arg, GMP_RNDN);
}
#if defined(BOOST_HAS_LONG_LONG) && defined(_MPFR_H_HAVE_INTMAX_T)
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const unsigned long long& arg)
{
   mpfr_set_uj(mpc_imagref(result.data()), arg, GMP_RNDN);
}
template <unsigned Digits10>
inline void eval_set_imag(mpc_complex_backend<Digits10>& result, const long long& arg)
{
   mpfr_set_sj(mpc_imagref(result.data()), arg, GMP_RNDN);
}
#endif

template <unsigned Digits10>
inline std::size_t hash_value(const mpc_complex_backend<Digits10>& val)
{
   std::size_t result = 0;
   std::size_t len = val.data()[0].re[0]._mpfr_prec / mp_bits_per_limb;
   if(val.data()[0].re[0]._mpfr_prec % mp_bits_per_limb)
      ++len;
   for(std::size_t i = 0; i < len; ++i)
      boost::hash_combine(result, val.data()[0].re[0]._mpfr_d[i]);
   boost::hash_combine(result, val.data()[0].re[0]._mpfr_exp);
   boost::hash_combine(result, val.data()[0].re[0]._mpfr_sign);

   len = val.data()[0].im[0]._mpfr_prec / mp_bits_per_limb;
   if(val.data()[0].im[0]._mpfr_prec % mp_bits_per_limb)
      ++len;
   for(std::size_t i = 0; i < len; ++i)
      boost::hash_combine(result, val.data()[0].im[0]._mpfr_d[i]);
   boost::hash_combine(result, val.data()[0].im[0]._mpfr_exp);
   boost::hash_combine(result, val.data()[0].im[0]._mpfr_sign);
   return result;
}

} // namespace backends

#ifdef BOOST_NO_SFINAE_EXPR

namespace detail{

template<unsigned D1, unsigned D2>
struct is_explicitly_convertible<backends::mpc_complex_backend<D1>, backends::mpc_complex_backend<D2> > : public mpl::true_ {};

}
#endif

namespace detail
{
   template<>
   struct is_variable_precision<backends::mpc_complex_backend<0> > : public true_type {};
}

template<>
struct number_category<detail::canonical<mpc_t, backends::mpc_complex_backend<0> >::type> : public mpl::int_<number_kind_floating_point>{};

using boost::multiprecision::backends::mpc_complex_backend;

typedef number<mpc_complex_backend<50> >    mpc_complex_50;
typedef number<mpc_complex_backend<100> >   mpc_complex_100;
typedef number<mpc_complex_backend<500> >   mpc_complex_500;
typedef number<mpc_complex_backend<1000> >  mpc_complex_1000;
typedef number<mpc_complex_backend<0> >     mpc_complex;

template <unsigned Digits10, expression_template_option ExpressionTemplates>
struct component_type<number<mpc_complex_backend<Digits10>, ExpressionTemplates> >
{
   typedef number<mpfr_float_backend<Digits10>, ExpressionTemplates> type;
};

template <unsigned Digits10, expression_template_option ExpressionTemplates>
struct component_type<number<logged_adaptor<mpc_complex_backend<Digits10> >, ExpressionTemplates> >
{
   typedef number<mpfr_float_backend<Digits10>, ExpressionTemplates> type;
};

template <unsigned Digits10, expression_template_option ExpressionTemplates>
struct complex_result_from_scalar<number<mpfr_float_backend<Digits10>, ExpressionTemplates> >
{
   typedef number<mpc_complex_backend<Digits10>, ExpressionTemplates> type;
};

} // namespace multiprecision

}  // namespaces

#endif


//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_archetypes.hpp>
#include <boost/graph/adjacency_matrix.hpp>
#include <boost/concept/assert.hpp>

int main(int, char*[])
{
    using namespace boost;
    // Check adjacency_matrix without properties
    {
        typedef adjacency_matrix< directedS > Graph;
        BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((MutableGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyMatrixConcept< Graph >));
    }
    {
        typedef adjacency_matrix< undirectedS > Graph;
        BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((MutableGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyMatrixConcept< Graph >));
    }
    // Check adjacency_matrix with properties
    {
        typedef adjacency_matrix< directedS, property< vertex_color_t, int >,
            property< edge_weight_t, float > >
            Graph;
        typedef graph_traits< Graph >::vertex_descriptor Vertex;
        typedef graph_traits< Graph >::edge_descriptor Edge;
        BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyMatrixConcept< Graph >));
        BOOST_CONCEPT_ASSERT((VertexMutablePropertyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeMutablePropertyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT(
            (ReadablePropertyGraphConcept< Graph, Vertex, vertex_index_t >));
        BOOST_CONCEPT_ASSERT(
            (PropertyGraphConcept< Graph, Vertex, vertex_color_t >));
        BOOST_CONCEPT_ASSERT(
            (PropertyGraphConcept< Graph, Edge, edge_weight_t >));
    }
    {
        typedef adjacency_matrix< undirectedS, property< vertex_color_t, int >,
            property< edge_weight_t, float > >
            Graph;
        typedef graph_traits< Graph >::vertex_descriptor Vertex;
        typedef graph_traits< Graph >::edge_descriptor Edge;
        BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeListGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((AdjacencyMatrixConcept< Graph >));
        BOOST_CONCEPT_ASSERT((VertexMutablePropertyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT((EdgeMutablePropertyGraphConcept< Graph >));
        BOOST_CONCEPT_ASSERT(
            (ReadablePropertyGraphConcept< Graph, Vertex, vertex_index_t >));
        BOOST_CONCEPT_ASSERT(
            (PropertyGraphConcept< Graph, Vertex, vertex_color_t >));
        BOOST_CONCEPT_ASSERT(
            (PropertyGraphConcept< Graph, Edge, edge_weight_t >));
    }
    return 0;
}


//
//  Copyright Markus Rickert 2008
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include <boost/numeric/bindings/traits/c_array2.hpp>
#include <boost/numeric/bindings/traits/dense_traits.hpp>
#include <boost/numeric/bindings/traits/std_valarray2.hpp>
#include <boost/numeric/bindings/traits/std_vector2.hpp>
#include <boost/numeric/bindings/traits/ublas_matrix.hpp>
//#include <boost/numeric/bindings/traits/ublas_vector.hpp>
#include <boost/numeric/bindings/traits/ublas_vector2.hpp>

int
main(int argc, char** argv)
{
	// matrix
	boost::numeric::ublas::matrix<double> a(3, 4);
	for (std::size_t i = 0; i < a.size1(); ++i) for (std::size_t j = 0; j < a.size2(); ++j) a(i, j) = i * a.size2() + j;
	std::cout << "A=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(a) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(a) << std::endl;
	std::cout << a << std::endl;

	// matrix matrix_range
	boost::numeric::ublas::matrix_range<
		boost::numeric::ublas::matrix<double>
	> a2 = boost::numeric::ublas::subrange(a, 1, 3, 0, 2);
	std::cout << "A2=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(a2) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(a2) << std::endl;
	std::cout << a2 << std::endl;

	// matrix column_major
	boost::numeric::ublas::matrix<double, boost::numeric::ublas::column_major> b(3, 4);
	std::cout << "B=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(b) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(b) << std::endl;

	// bounded_matrix
	boost::numeric::ublas::bounded_matrix<double, 3, 4> c;
	std::cout << "C=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(c) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(c) << std::endl;

	// bounded_matrix matrix_range
	boost::numeric::ublas::matrix_range<
		boost::numeric::ublas::bounded_matrix<double, 3, 4>
	> c2 = boost::numeric::ublas::subrange(c, 0, 1, 0, 2);
	std::cout << "C2=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(c2) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(c2) << std::endl;

	// bounded_matrix column_major
	boost::numeric::ublas::bounded_matrix<double, 3, 4, boost::numeric::ublas::column_major> d;
	std::cout << "D=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(d) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(d) << std::endl;

	// vector
	boost::numeric::ublas::vector<double> e(3);
	std::cout << "e=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(e) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(e) << std::endl;

	// vector vector_range
	boost::numeric::ublas::vector_range<
		boost::numeric::ublas::vector<double>
	> f = boost::numeric::ublas::subrange(e, 0, 2);
	std::cout << "f=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(f) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(f) << std::endl;

	// bounded_vector
	boost::numeric::ublas::bounded_vector<double, 3> h;
	std::cout << "h=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(h) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(h) << std::endl;

	// bounded_vector vector_range
	boost::numeric::ublas::vector_range<
		boost::numeric::ublas::bounded_vector<double, 3>
	> i = boost::numeric::ublas::subrange(h, 0, 2);
	std::cout << "i=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(i) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(i) << std::endl;

	// std::vector
	std::vector<double> k(3);
	std::cout << "k=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(k) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(k) << std::endl;

	// std::valarray
	std::valarray<double> l(3);
	std::cout << "l=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(l) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(l) << std::endl;

	// array
	double m[3];
	std::cout << "m=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(m) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(m) << std::endl;

	// matrix
	boost::numeric::ublas::matrix<double> n(3, 1);
	std::cout << "N=";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride1(n) << ",";
	std::cout << boost::numeric::bindings::traits::dense_matrix_stride2(n) << std::endl;
	std::cout << boost::numeric::bindings::traits::matrix_stride1(n) << ",";
	std::cout << boost::numeric::bindings::traits::matrix_stride2(n) << std::endl;

	return 0;
}


#include <fstream>
#include <iostream>
#include <numeric>
#include <string>
#include <unordered_map>
#include <vector>

#include <boost/range/irange.hpp>

using uint64 = std::uint64_t;

// a convenient way to track which letter is most frequently used
struct CharFrequencyTracker {
	char letter;
	uint64 count;
};

auto operator<(const CharFrequencyTracker& lhs, const CharFrequencyTracker& rhs) {
	return (lhs.count < rhs.count);
}

template<typename M>
auto map_reset_values(M&& map) {
	for(auto& pair : map) {
	    pair.second = {};
	}
}

int main() {

	const auto filename = std::string{"message.txt"};
	auto file = std::fstream{filename};

	if(file.is_open()) {

		// in order to transpose the input line by line, we will need a string for each column,
		// so that we can add the letter from the same column to the same string on each line
		auto transposed_msg = std::vector<std::string>{};

		auto line = std::string{};

		// initialize the transposed message
		file >> line;
		for(const auto letter : line) {
			transposed_msg.push_back({letter});
		}

		while(file >> line) {
			for(const auto i : boost::irange(line.size())) {
				transposed_msg[i] += line[i];
			}
		}

		const auto begin = transposed_msg.begin();
		const auto end   = transposed_msg.end();
		const auto init  = std::string{};

		using LetterCounts = std::unordered_map<char, uint64>;

		// a mutable lambda will let us keep the state of letter_counts,
		// even if it's called again with new arguments
		const auto count = [letter_counts = LetterCounts{}] (auto& msg, const auto& column) mutable {

			// we can still use the keys collected so far,
			// only the values need to be reset to default
			map_reset_values(letter_counts);

			for(const auto letter : column) {
				++letter_counts[letter];
			}

			auto most_frequent_letter = CharFrequencyTracker{};

			for(const auto letter : letter_counts) {
				most_frequent_letter = std::max(most_frequent_letter, {letter.first, letter.second});
			}

			return (msg += most_frequent_letter.letter);
		};

		const auto message = std::accumulate(begin, end, init, count);

		std::cout << message << std::endl;

	} else {
		std::cerr << "Error! Could not open file \"" << filename << "\"!" << std::endl;
	}

	return 0;
}


/*
 * SISet.cpp
 *
 *  Created on: May 24, 2011
 *      Author: joe
 */
#include <set>
#include <list>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <cstdlib>
#include <boost/foreach.hpp>
#include <iterator>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/bernoulli_distribution.hpp>
#include <boost/random/uniform_int.hpp>
#include "SISet.h"
#include "SpanInterval.h"
#include "Log.h"
#include "infix_ostream_iterator.h"

SISet::SISet(const SpanInterval& si, bool forceLiquid,
            const Interval& maxInterval)
    : set_(), forceLiquid_(forceLiquid), maxInterval_(maxInterval) {
    set_.push_back(si);
}

std::set<SpanInterval> SISet::asSet() const {
    return std::set<SpanInterval>(set_.begin(), set_.end());
}

SISet SISet::compliment() const {
    if (size() == 0) {
        // compliment is max interval
        SISet max(forceLiquid_, maxInterval_);
        max.add(SpanInterval(maxInterval_));
        return max;
    }

    // {A U B U C U.. }^c = A^c I B^c I ...
    // {A U B U ..} intersect {C U D U } .. intersect D
    // expensive operation!  faster way to do this?
    std::list<std::list<SpanInterval> > intersections;
    for (std::list<SpanInterval>::const_iterator it = set_.begin(); it != set_.end(); it++) {
        std::list<SpanInterval> compliment;
        if (forceLiquid_) {
            it->liqCompliment(SpanInterval(maxInterval_), std::inserter(compliment, compliment.end()));
        } else {
            it->compliment(SpanInterval(maxInterval_), std::inserter(compliment, compliment.end()));
        }
        intersections.push_back(compliment);
    }

    // now we have a list of set unions that we need to intersect; perform pairwise intersection
    while (intersections.size() > 1) {
        // merge the first two sets
        std::list<SpanInterval> first = intersections.front();
        intersections.pop_front();
        std::list<SpanInterval> second = intersections.front();
        intersections.pop_front();
        std::list<SpanInterval> intersected;
        for (std::list<SpanInterval>::const_iterator lIt = first.begin(); lIt != first.end(); lIt++) {
            for (std::list<SpanInterval>::const_iterator sIt = second.begin(); sIt != second.end(); sIt++) {
                boost::optional<SpanInterval> intersect = intersection(*lIt, *sIt);
                if (intersect) {
                    intersected.push_back(*intersect);
                }
            }
        }
        if (!intersected.empty()) {
            intersections.push_front(intersected);
        }
    }
    if (intersections.empty()) {
        return SISet(forceLiquid_, maxInterval_);
    }
    return SISet(intersections.front().begin(), intersections.front().end(), forceLiquid_, maxInterval_);
}

Interval SISet::maxInterval() const {
    return maxInterval_;
}

unsigned int SISet::size() const {
    // look, this doesn't mean much if the list isn't disjoint
    SISet copy(*this);
    copy.makeDisjoint();

    unsigned int sum = 0;
    BOOST_FOREACH(SpanInterval sp, copy.set_) {
        sum += sp.size();
    }
    return sum;
}

unsigned int SISet::liqSize() const {
    SISet copy(*this);
    copy.makeDisjoint();

    unsigned int sum = 0;
    BOOST_FOREACH(SpanInterval sp, copy.set_) {
        sum += sp.liqSize();
    }
    return sum;
}

// O(n^2) for every call :(  perhaps set a flag instead?
bool SISet::isDisjoint() const {
    for (std::list<SpanInterval>::const_iterator fIt = set_.begin(); fIt != set_.end(); fIt++) {
        for (std::list<SpanInterval>::const_iterator sIt = fIt; sIt != set_.end(); sIt++) {
            // dont compare to yourself
            if (sIt == fIt) {
                continue;
            }
            if (intersection(*fIt, *sIt)) {
                return false;
            }
        }
    }
    return true;
}

void SISet::setMaxInterval(const Interval& maxInterval) {
    maxInterval_ = maxInterval;
    std::list<SpanInterval> resized;
    for (std::list<SpanInterval>::iterator it = set_.begin(); it != set_.end(); it++) {
        SpanInterval si = *it;
        boost::optional<SpanInterval> siOpt = intersection(si, SpanInterval(maxInterval, maxInterval));
        if (siOpt) {
            resized.push_back(siOpt.get());
        }
    }
    set_.swap(resized);
}

void SISet::add(const SpanInterval &s) {
    boost::optional<SpanInterval> sCopy = intersection(s, SpanInterval(maxInterval_));
    if (!sCopy) return;
    SpanInterval sp = *sCopy;
    if (forceLiquid_ && !sp.isLiquid()) {
        std::runtime_error e("tried to add a non-liquid SI to a liquid SI");
        throw e;
    }
    if (forceLiquid_) {
        // we have to merge this with pre-existing intervals
        for (std::list<SpanInterval>::iterator it = set_.begin(); it != set_.end();) {
            SpanInterval b = *it;
            Interval sInt = sp.start();
            Interval bInt = b.start();
            if (meets(sInt, bInt) || meetsI(sInt, bInt)
                    || overlaps(sInt, bInt) || overlapsI(sInt, bInt)
                    || starts(sInt, bInt) || startsI(sInt, bInt)
                    || finishes(sInt, bInt) || finishesI(sInt, bInt)
                    || during(sInt, bInt) || duringI(sInt, bInt)) {
                // they intersect, pull it out
                std::list<SpanInterval>::iterator toErase(it);
                it++;
                set_.erase(toErase);

                // now reset scopy to be the newly resized span interval
                unsigned int i = (b.start().start() < sp.start().start()
                        ? b.start().start() : sp.start().start());
                unsigned int j = (b.start().finish() > sp.start().finish()
                        ? b.start().finish() : sp.start().finish());

                sp.setStart(Interval(i,j));
                sp.setFinish(Interval(i,j));
            } else {
                it++;
            }
        }
        // now insert our element into the list
        set_.push_back(sp);
    } else {
        set_.push_back(sp);
    }
}

void SISet::add(const SISet &b) {
    for (std::list<SpanInterval>::const_iterator it = b.set_.begin(); it != b.set_.end(); it++) {
        add(*it);
    }
}

void SISet::makeDisjoint() {
    if (forceLiquid_) {
        // we are already disjoint
        return;
        /*
        // all our intervals are liquid so we can make them disjoint by
        // merging them
        for (std::list<SpanInterval>::const_iterator fIt = set_.begin(); fIt != set_.end(); fIt++) {
            std::list<SpanInterval>::const_iterator sIt = fIt;
            sIt++;
            if (sIt != set_.end() && !intersection(*fIt, *sIt).isEmpty()) {
                // merge the two
                unsigned int start = fIt->start().start();
                unsigned int end = sIt->finish().finish();
                SpanInterval merged(start, end, start, end, maxInterval_);
                set_.erase(sIt);    // invalidates sIt
                set_.erase(fIt);    // invalidates fIt
                fIt = set_.insert(merged).first;
            }
        }
        */
    } else {
        std::list<SpanInterval> setCopy;

        BOOST_FOREACH(SpanInterval siToAdd, set_) {
            SISet sisetToAdd(false, maxInterval_);
            sisetToAdd.add(siToAdd);
            // subtract whats already stored and only add if there is some left over
            BOOST_FOREACH(SpanInterval siAlreadyIn, setCopy) {
                sisetToAdd.subtract(siAlreadyIn);
            }
            setCopy.insert(setCopy.end(), sisetToAdd.set_.begin(), sisetToAdd.set_.end());
        }
        set_ = setCopy;

        /*
        // scan over all pairs, looking for intersections
        for (std::set<SpanInterval>::const_iterator fIt = set_.begin(); fIt != set_.end(); fIt++) {
            for (std::set<SpanInterval>::const_iterator sIt = fIt; sIt != set_.end(); sIt++) {
                // don't compare to yourself
                if (sIt == fIt) {
                    continue;
                }
                SpanInterval intersect = intersection(*fIt, *sIt);
                if (!intersect.isEmpty()) {

                    // remove it from the second set
                    std::set<SpanInterval> leftover;
                    sIt->subtract(intersect, leftover);

                    std::set<SpanInterval>::const_iterator toRemove = sIt;
                    sIt--;
                    set_.erase(toRemove);

                    BOOST_FOREACH(SpanInterval sp, leftover) {
                        if (!sp.isEmpty()) sp = sp.normalize().get();
                        if (!sp.isEmpty()) set_.insert(sp);
                    }
                }
            }
        }
        */
    }
    // don't trust myself - remove this later as an optimization step
    if (!isDisjoint()) {
        LOG_PRINT(LOG_ERROR) << "set is supposed to be disjoint, but isnt!  set: " << this->toString() << ", forceliquid: " << forceLiquid_;
        std::runtime_error error("inside SISet::makeDisjoint() - set was attempted to make disjoint but isn't!");
        throw error;
    }
}

void SISet::setForceLiquid(bool forceLiquid) {
    if (forceLiquid && !forceLiquid_) {
        // first, make it disjoint
        makeDisjoint();

        std::list<SpanInterval> newSet;
        BOOST_FOREACH(SpanInterval sp, set_) {
            sp = sp.toLiquidExc();
            if (!sp.isEmpty()) {
                sp.normalize();
                newSet.push_back(sp);
            }
        }
        set_.swap(newSet);
    }
    forceLiquid_ = forceLiquid;
};

void SISet::subtract(const SpanInterval& si) {
    std::list<SpanInterval> newSet;

    if (set_.size() == 0) return;
    if (si.size() == 0) return;

    BOOST_FOREACH(SpanInterval siInSet, set_) {
        if (intersection(siInSet, si)) {
            if (forceLiquid_) siInSet.liqSubtract(si, back_inserter(newSet));
            else siInSet.subtract(si, back_inserter(newSet));
        } else {
            newSet.push_back(siInSet);
        }
    }
    set_ = newSet;
}

void SISet::subtract(const SISet& sis) {
    // TODO why not just subtract each item individually??

    //LOG_PRINT(LOG_DEBUG) << "called SISet::subtract with *this=" << this->toString() << " and sis=" << sis.toString();
    std::list<SISet> toIntersect;

    if (set_.size() == 0) return;
    if (sis.size() == 0) return;

    BOOST_FOREACH(SpanInterval b, sis.set_) {
        SISet copy(*this);
        //copy.setForceLiquid(false);
    //  LOG_PRINT(LOG_DEBUG) << "copy size:" << copy.set_.size();
        copy.subtract(b);
    //  LOG_PRINT(LOG_DEBUG) << "this \\ b (where b = "<< b.toString() <<") = " << copy.toString();
        toIntersect.push_back(copy);
    }
    if (toIntersect.size() == 0) {
        set_.clear();
        return;
    }
    // now collapse the intersect set
    while (toIntersect.size() > 1) {
        SISet a = toIntersect.front();
        toIntersect.pop_front();
        SISet b = toIntersect.front();
        toIntersect.pop_front();

        SISet intersected = intersection(a, b);
    //  LOG_PRINT(LOG_DEBUG) << "a=" << a.toString() <<", b=" << b.toString() << ", a intersect b = "<< intersected.toString();
        toIntersect.push_front(intersected);
    }
    set_ = toIntersect.front().set_;
    //LOG_PRINT(LOG_DEBUG) << "final value: " << this->toString();
}

const SISet SISet::satisfiesRelation(const Interval::INTERVAL_RELATION& rel) const {
    SISet newSet(false, maxInterval_);
    newSet.clear();

    for (std::list<SpanInterval>::const_iterator it = set_.begin(); it != set_.end(); it++) {
        boost::optional<SpanInterval> siOpt = it->satisfiesRelation(rel, SpanInterval(maxInterval_));
        if (siOpt) newSet.add(siOpt.get());
    }

    return newSet;
}

SISet SISet::randomSISet(bool forceLiquid, const Interval& maxInterval, boost::mt19937& rng) {
    SISet set(forceLiquid, maxInterval);
    if (!forceLiquid) {
        LOG_PRINT(LOG_ERROR) << "generating random SISets for non-liquid events is not yet implemented!";
        return set;
    }

    // for liquid, we just sample each timepoint and build up a spanning interval
    unsigned int curStart = 0;
    unsigned int curFinish = 0;
    bool hasData = false;
    boost::bernoulli_distribution<> flip(0.5);
    for (unsigned int point = maxInterval.start(); point <= maxInterval.finish(); point++) {
        bool sampleThis = flip(rng);
        if (sampleThis) {
            if (!hasData) {
                curStart = point;
                hasData = true;
            }
            curFinish = point;
        } else {
            if (hasData) {
                // add it in
                set.add(SpanInterval(curStart, curFinish, curStart, curFinish));
                hasData = false;
            }
        }
    }
    if (hasData) set.add(SpanInterval(curStart, curFinish, curStart, curFinish));   // one final check in case we hit the end of the list without adding
    return set;

    /*
    int bitsLeft = 15;  // TODO: we assume max is 32767, always true?
    int random = rand();
    unsigned int curStart = 0;
    unsigned int curEnd = 0;
    bool buildingSI = false;
    for (unsigned int i = maxInterval.start(); i <= maxInterval.finish(); i++) {
        if (random % 2) {
            if (buildingSI) {
                curEnd = i;
            } else {
                buildingSI = true;
                curStart = i;
                curEnd = i;
            }
        } else {
            if (buildingSI) {
                buildingSI = false;
                start.add(SpanInterval(curStart, curEnd, curStart, curEnd));
            }
        }
        bitsLeft--;
        if (bitsLeft == 0) {
            random = rand();
            bitsLeft = 15;
        } else {
            random = random >> 1;
        }
    }
    return start;
    */
}

SpanInterval SISet::randomSI(boost::mt19937& rng) const {
    if (size() == 0) {
        LOG_PRINT(LOG_ERROR) << "called randomSI() on an empty SISet.";
        std::runtime_error e("called randomSI() on an empty SISet.");
        throw e;
    }
    // choose a random number from 0 to size
    boost::uniform_int<std::size_t> setFlip(0, set_.size()-1);
    std::size_t index = setFlip(rng);
    std::list<SpanInterval>::const_iterator it = set_.begin();
    while (index != 0) {
        it++;
        index--;
    }
    return *it;
}

std::string SISet::toString() const {
    std::stringstream sstream;
    sstream << *this;
    return sstream.str();
}

std::ostream& operator<<(std::ostream& o, const SISet& s) {
    o << "{";
    std::list<SpanInterval> copy(s.set_);
    copy.sort();

    infix_ostream_iterator<SpanInterval> oIt(o, ", ");
    std::copy(copy.begin(), copy.end(), oIt);
    o << "}";
    return o;
}

SISet intersection(const SISet& a, const SISet& b) {
    SISet result(a.forceLiquid(), a.maxInterval_);  // TODO: better way?
    //result.setMaxInterval(a.maxInterval_);  // TODO: better way?
    if (a.forceLiquid() && b.forceLiquid()) {
        result.setForceLiquid(true);
    } else {
        result.setForceLiquid(false);
    }
    // pairwise intersection - ugh
    BOOST_FOREACH(SpanInterval siA, a.set_) {
        BOOST_FOREACH(SpanInterval siB, b.set_) {
            boost::optional<SpanInterval> intersect = intersection(siA, siB);
            if (intersect) {
                result.add(*intersect);
            }
        }
    }
    return result;
};

SISet intersection(const SISet& a, const SpanInterval& si) {
    // cheat for now
    SISet copy(false, a.maxInterval_);
    copy.add(si);
    return intersection(a, copy);
}

SISet span(const SpanInterval& a, const SpanInterval& b, const Interval& maxInterval) {
    unsigned int j = std::min(a.start().finish(), b.start().finish());
    unsigned int k = std::max(a.finish().start(), b.finish().start());

    SISet set(false, maxInterval);

    set.add(SpanInterval(a.start().start(), j, k, a.finish().finish()));
    set.add(SpanInterval(a.start().start(), j, k, b.finish().finish()));
    set.add(SpanInterval(b.start().start(), j, k, a.finish().finish()));
    set.add(SpanInterval(b.start().start(), j, k, b.finish().finish()));

    return set;
};

SISet composedOf(const SpanInterval& i, const SpanInterval& j, Interval::INTERVAL_RELATION rel, const SpanInterval& universe) {
    if (!universe.isLiquid()) throw std::invalid_argument("composedOf() requires universe to be liquid; could be supported in future");
    SISet empty(false, universe.start());

    if (rel == Interval::EQUALS) {
        boost::optional<SpanInterval> intersect = intersection(i, j);
        SISet result(false, universe.start());
        if (intersect) result.add(*intersect);
        return result;
    }

    boost::optional<SpanInterval> iPrimeOpt = j.satisfiesRelation(inverseRelation(rel), universe);
    if (!iPrimeOpt) return empty;
    SpanInterval iPrime = iPrimeOpt.get();
    boost::optional<SpanInterval> jPrimeOpt = i.satisfiesRelation(rel, universe);
    if (!jPrimeOpt) return empty;
    SpanInterval jPrime = jPrimeOpt.get();

    boost::optional<SpanInterval> iIntersectOpt = intersection(iPrime, i);
    boost::optional<SpanInterval> jIntersectOpt = intersection(jPrime, j);

    if (!iIntersectOpt || !jIntersectOpt) return empty;

    SpanInterval iIntersect = iIntersectOpt.get();
    SpanInterval jIntersect = jIntersectOpt.get();
//  std::cout << "iIntersect = " << iIntersect.toString() << std::endl;
//  std::cout << "jIntersect = " << jIntersect.toString() << std::endl;

    // TODO this seems right but we are now iterating over pairs of intervals!
/*
    SISet spanned;
    for (SpanInterval::iterator iIt = iIntersect.begin(); iIt != iIntersect.end(); iIt++) {
        for (SpanInterval::iterator jIt = jIntersect.begin(); jIt != jIntersect.end(); jIt++) {
            if (relationHolds(*iIt, rel, *jIt)) {

                Interval interval = span(*iIt, *jIt);
                spanned.add(SpanInterval(interval.start(), interval.start(),
                        interval.finish(), interval.finish(), i.maxInterval()));
            }
        }
    }
    */

    SISet spanned = span(iIntersect, jIntersect, universe.start());
//  std::cout << "returning spanned = " << spanned.toString() << std::endl;
    return spanned;
}

bool equalByInterval(const SISet& a, const SISet& b) {
    // this is expensive
    std::set<Interval> aIntervals;
    std::set<Interval> bIntervals;

    for (std::list<SpanInterval>::const_iterator it = a.set_.begin(); it != a.set_.end(); it++) {
        SpanInterval si = *it;
        aIntervals.insert(si.begin(), si.end());
    }
    for (std::list<SpanInterval>::const_iterator it = b.set_.begin(); it != b.set_.end(); it++) {
        SpanInterval si = *it;
        bIntervals.insert(si.begin(), si.end());
    }

    if (    std::includes(aIntervals.begin(), aIntervals.end(),
                bIntervals.begin(), bIntervals.end())
         &&
            std::includes(bIntervals.begin(), bIntervals.end(),
                aIntervals.begin(), aIntervals.end())
        ) return true;
    return false;
}

unsigned long hammingDistance(const SISet& a, const SISet& b) {
    // (a ^ !b) v (!a ^ b)
    SISet aComp = a.compliment();
    SISet bComp = b.compliment();

    SISet abC = intersection(a, bComp);
    SISet aCb = intersection(aComp, b);
    SISet result(abC);
    result.add(aCb);
    if (a.forceLiquid() && b.forceLiquid()) {
        return result.liqSize();
    }
    return result.size();

}

/* UNFINISHED CODE
static SISet SISet::asNormalized(const SISet& set) {
    SISet disjointSet = set;
    if (!disjointSet.isDisjoint()) disjointSet.makeDisjoint();
    if (disjointSet.forceLiquid()) return disjointSet;  // liquid sets are already normalized
    SISet norm(disjointSet.forceLiquid_, disjointSet.maxInterval_);

    for (std::list<SpanInterval>::const_iterator it = disjointSet.set_.begin(); it != disjointSet.set_.end(); it++) {
        SpanInterval si = *it;
        for (std::list<SpanInterval>::iterator setIt = norm.set_.begin();
                setIt != norm.set_.end();) {
            SpanInterval si2 = *setIt;
            // check to see if they can be combined

        }
    }
}

namespace {
    bool canMerge(const SpanInterval& left, const SpanInterval& right) {
        //if (left.start() == right.start() ||
         //
    }
}
*/


// Copyright 2015-2017 Peter Dimov.
//
// Distributed under the Boost Software License, Version 1.0.
//
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt

#include <boost/mp11/function.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/core/lightweight_test_trait.hpp>
#include <type_traits>

int main()
{
    using boost::mp11::mp_or;
    using boost::mp11::mp_true;
    using boost::mp11::mp_false;
    using boost::mp11::mp_int;
    using boost::mp11::mp_size_t;

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_true>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<-7>>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<7>>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_true, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_true>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<0>>, mp_false>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<5>>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<-4>, void>, mp_true>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<7>, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<4>>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<0>>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_true, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_true, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false, mp_true>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false, mp_false>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_true, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_true, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false, mp_true, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false, mp_false, mp_true>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_false, mp_false, mp_false, mp_false>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<1>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<2>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<0>, mp_int<3>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<0>, mp_int<0>, mp_int<4>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_int<0>, mp_int<0>, mp_int<0>, mp_int<0>, mp_int<0>>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<1>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<2>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<0>, mp_size_t<3>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<0>, mp_size_t<0>, mp_size_t<4>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_size_t<0>, mp_size_t<0>, mp_size_t<0>, mp_size_t<0>>, mp_false>));

    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_int<0>, mp_false, mp_size_t<141>, void, void, void>, mp_true>));
    BOOST_TEST_TRAIT_TRUE((std::is_same<mp_or<mp_size_t<0>, mp_int<0>, mp_false>, mp_false>));

    return boost::report_errors();
}


#define BOOST_AUTO_TEST_MAIN
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/test_tools.hpp>

// These rely on boost includes
#include "container_test.h"
#include "vector_test.h"
#include "matrix_test.h"
#include "quaternion_test.h"

BOOST_AUTO_TEST_SUITE(tiny_types);

BOOST_AUTO_TEST_CASE(default_types_testing)
{
  container_test::run<1,1>();
  container_test::run<2,2>();
  container_test::run<4,4>();
  container_test::run<8,8>();
  container_test::run<8,1>();
  container_test::run<1,8>();

  vector_test::run<1>();
  vector_test::run<2>();
  vector_test::run<3>();
  vector_test::run<4>();
  vector_test::run<6>();

  matrix_test::run<1,1>();
  matrix_test::run<2,2>();
  matrix_test::run<3,3>();
  matrix_test::run<4,4>();
  matrix_test::run<6,6>();
  matrix_test::run<3,4>();
  matrix_test::run<4,3>();

  quaternion_test::run();
}

BOOST_AUTO_TEST_SUITE_END();


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_BOX_HPP
#define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_BOX_HPP

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/strategies/covered_by.hpp>
#include <boost/geometry/strategies/within.hpp>

namespace geofeatures_boost {} namespace boost = geofeatures_boost; namespace geofeatures_boost { namespace geometry { namespace strategy
{

namespace within
{

struct within_range
{
    template <typename Value1, typename Value2>
    static inline bool apply(Value1 const& value, Value2 const& min_value, Value2 const& max_value)
    {
        return value > min_value && value < max_value;
    }
};

struct covered_by_range
{
    template <typename Value1, typename Value2>
    static inline bool apply(Value1 const& value, Value2 const& min_value, Value2 const& max_value)
    {
        return value >= min_value && value <= max_value;
    }
};

template
<
    typename SubStrategy,
    typename Point,
    typename Box,
    std::size_t Dimension,
    std::size_t DimensionCount
>
struct relate_point_box_loop
{
    static inline bool apply(Point const& point, Box const& box)
    {
        if (! SubStrategy::apply(get<Dimension>(point),
                    get<min_corner, Dimension>(box),
                    get<max_corner, Dimension>(box))
            )
        {
            return false;
        }

        return relate_point_box_loop
            <
                SubStrategy,
                Point, Box,
                Dimension + 1, DimensionCount
            >::apply(point, box);
    }
};

template
<
    typename SubStrategy,
    typename Point,
    typename Box,
    std::size_t DimensionCount
>
struct relate_point_box_loop<SubStrategy, Point, Box, DimensionCount, DimensionCount>
{
    static inline bool apply(Point const& , Box const& )
    {
        return true;
    }
};

template
<
    typename Point,
    typename Box,
    typename SubStrategy = within_range
>
struct point_in_box
{
    static inline bool apply(Point const& point, Box const& box)
    {
        return relate_point_box_loop
            <
                SubStrategy,
                Point, Box,
                0, dimension<Point>::type::value
            >::apply(point, box);
    }
};

} // namespace within

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace within { namespace services
{

template <typename Point, typename Box>
struct default_strategy
    <
        point_tag, box_tag,
        point_tag, areal_tag,
        cartesian_tag, cartesian_tag,
        Point, Box
    >
{
    typedef within::point_in_box<Point, Box> type;
};

}} // namespace within::services

namespace covered_by { namespace services
{

template <typename Point, typename Box>
struct default_strategy
    <
        point_tag, box_tag,
        point_tag, areal_tag,
        cartesian_tag, cartesian_tag,
        Point, Box
    >
{
    typedef within::point_in_box
                <
                    Point, Box,
                    within::covered_by_range
                > type;
};

}} // namespace covered_by::services

#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}}} // namespace geofeatures_boost::geometry::strategy

#endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_BOX_HPP


#include "precompiled.h"

#pragma hdrstop

#include "Camera.h"

Camera::Camera()
  : m_transform(new Transform())
{

}

Camera::~Camera()
{

}

Transform* Camera::getTransform() const
{
  return m_transform.get();
}

const Frustum& Camera::getFrustum()
{
  if (m_needFrustumUpdate || m_transform->isCacheOutdated()) {
    m_frustum = Frustum::extractFromViewProjection(getViewMatrix(), getProjectionMatrix());

    m_needFrustumUpdate = false;
  }

  return m_frustum;
}

void Camera::setAspectRatio(float ratio)
{
  m_aspectRatio = ratio;

  resetProjectionCache();
}

float Camera::getAspectRatio() const
{
  return m_aspectRatio;
}

void Camera::setNearClipDistance(float distance)
{
  m_nearClipDistance = distance;

  resetProjectionCache();
}

float Camera::getNearClipDistance() const
{
  return m_nearClipDistance;
}

void Camera::setFarClipDistance(float distance)
{
  m_farClipDistance = distance;

  resetProjectionCache();
}

float Camera::getFarClipDistance() const
{
  return m_farClipDistance;
}

void Camera::setFOVy(float fov)
{
  m_FOVy = fov;

  resetProjectionCache();
}

float Camera::getFOVy() const
{
  return m_FOVy;
}

#include <boost/stacktrace.hpp>
#include <boost/exception/error_info.hpp>

glm::mat4x4 Camera::getProjectionMatrix()
{
  if (m_needProjectionMatrixCacheUpdate) {
    m_projectionMatrixCache = glm::perspective(m_FOVy, m_aspectRatio, m_nearClipDistance, m_farClipDistance);

    m_needFrustumUpdate = true;
    m_needProjectionMatrixCacheUpdate = false;
  }

  return m_projectionMatrixCache;
}

glm::mat4x4 Camera::getViewMatrix()
{
  if (m_transform->isCacheOutdated()) {
    m_viewMatrixCache = glm::lookAt(m_transform->getPosition(),
      m_transform->getPosition() + m_transform->getFrontDirection(),
      m_transform->getUpDirection());

    // Trigger transformation cache update
    (void)m_transform->getTransformationMatrix();

    m_needFrustumUpdate = true;
  }

  return m_viewMatrixCache;
}

void Camera::resetProjectionCache()
{
  m_needProjectionMatrixCacheUpdate = true;
  m_needFrustumUpdate = true;
}


#include <iostream>
#include <fstream>

#ifdef __GNUC__
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#include <filesystem>
namespace fs = std::tr2::sys;
#endif

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/face.hpp>

#define CONFIG_NUM_WORKERS 2
#define CONFIG_FACE_CLASS 41

cv::Ptr<cv::face::BasicFaceRecognizer> make_recognizer(int argc, char *argv[]) {
	std::vector<cv::Mat> images;
	std::vector<int>     labels;

	// iterate through subdirectories to locate .pgm files
	fs::path p(argc > 1 ? argv[1] : "../assets/faces");
	for(const auto &entry : fs::recursive_directory_iterator{p}) {
		if(fs::is_regular_file(entry.status())) {
			if(entry.path().extension() == ".pgm") {
				auto str = entry.path().parent_path().stem().string();
				int label = atoi(str.c_str() + 1);
				images.push_back(cv::imread(entry.path().string().c_str(), 0));
				labels.push_back(label);
			}
		}
	}

	auto recognizer = cv::face::createEigenFaceRecognizer();
	recognizer->train(images, labels);
	return recognizer;
}

void camera_loop(boost::shared_ptr<boost::asio::io_service> service,
                 cv::VideoCapture vid,
                 cv::Ptr<cv::face::BasicFaceRecognizer> recognizer,
                 unsigned int count) {
	cv::Mat frame;
	vid >> frame;

	/* calculate maximum dimensions matching target aspect ratio of 92x112 */
	float actual_aspect = (float)frame.cols / (float)frame.rows;
	float target_aspect = 92.0f / 112.0f;
	int target_width = frame.cols, target_height = frame.rows;
	if(actual_aspect > target_aspect) {
		target_width = target_height * target_aspect;
	} else {
		target_height = target_width / target_aspect;
	}

	/* crop frame using frame center as origin */
	cv::Rect rect(frame.cols / 2 - target_width / 2,
	              frame.rows / 2 - target_height / 2,
	              target_width, target_height);
	cv::Mat cropped_frame = frame(rect);

	/* convert to grayscale and scale to 92x112 */
	cv::Mat gray_frame, resized_frame;
	cv::cvtColor(cropped_frame, gray_frame, CV_RGB2GRAY);
	cv::resize(gray_frame, resized_frame, cv::Size(92, 112));

	/* keep running counter of successful recognitions
	 * decrement counter per unsuccessful frame to allow for partial error
	 */
	int predicted = recognizer->predict(resized_frame);
	if(predicted == CONFIG_FACE_CLASS) {
		if(count < 10) { ++count; }
	} else {
		if(count > 0) { --count; }
	}

	/* red    = not recognized
	 * yellow = partially recognized
	 * green  = fully recognized
	 */
	cv::Scalar color(0, 255, 255);
	switch(count) {
	case 0:
		color = cv::Scalar(0, 0, 255);
		break;
	case 10:
		color = cv::Scalar(0, 255, 0);
		break;
	}

	/* flip frame to display in a more sensible manner */
	cv::Mat flipped_frame;
	cv::flip(cropped_frame, flipped_frame, 1);

	/* draw colored border around frame to be displayed
	 * border surrounded by 2px of black to strengthen border contrast
	 */
	cv::Rect color_rect(0, 0, flipped_frame.cols, flipped_frame.rows);
	cv::rectangle(flipped_frame, color_rect, cv::Scalar(0), 10);
	cv::rectangle(flipped_frame, color_rect, color, 8);
	cv::rectangle(flipped_frame, color_rect, cv::Scalar(0), 2);

	cv::imshow("optflow", flipped_frame);
	cv::waitKey(1000/60);
	service->post(std::bind(camera_loop, service, vid, recognizer, count));
}

void camera_main(boost::shared_ptr<boost::asio::io_service> service,
                 cv::Ptr<cv::face::BasicFaceRecognizer> recognizer) {
	cv::VideoCapture vid(0);
	if(!vid.isOpened()) {
		throw std::runtime_error("failed to open video capture device");
	}
	cv::namedWindow("optflow");
	service->post(std::bind(camera_loop, service, vid, recognizer, 0));
}

void worker_main(boost::shared_ptr<boost::asio::io_service> service) {
	service->run();
}

int main(int argc, char *argv[]) {
	std::cout << "training..." << std::endl;
	auto recognizer = make_recognizer(argc, argv);
	std::cout << "done" << std::endl;

	auto service = boost::make_shared<boost::asio::io_service>();
	auto work    = boost::make_shared<boost::asio::io_service::work>(*service);
	auto strand  = boost::make_shared<boost::asio::io_service::strand>(*service);
	boost::thread_group workers;
	for(unsigned int w = 0; w < CONFIG_NUM_WORKERS; ++w) {
		workers.create_thread(boost::bind(worker_main, service));
	}
	service->post(boost::bind(camera_main, service, recognizer));
	work.reset();
	workers.join_all();
	return 0;
}