///////////////////////////////////////////////////////////////////////////////
//  Copyright 2011 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_MATH_MP_TOMMATH_BACKEND_HPP
#define BOOST_MATH_MP_TOMMATH_BACKEND_HPP

#include <boost/cstdint.hpp>
#include <boost/functional/hash_fwd.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/multiprecision/detail/integer_ops.hpp>
#include <boost/multiprecision/number.hpp>
#include <boost/multiprecision/rational_adaptor.hpp>
#include <boost/scoped_array.hpp>
#include <cctype>
#include <climits>
#include <cmath>
#include <limits>
#include <tommath.h>

namespace boost {
namespace multiprecision {
namespace backends {

namespace detail {

inline void check_tommath_result(unsigned v) {
  if (v != MP_OKAY) {
    BOOST_THROW_EXCEPTION(std::runtime_error(mp_error_to_string(v)));
  }
}

} // namespace detail

struct tommath_int;

void eval_multiply(tommath_int &t, const tommath_int &o);
void eval_add(tommath_int &t, const tommath_int &o);

struct tommath_int {
  typedef mpl::list<boost::int32_t, boost::long_long_type> signed_types;
  typedef mpl::list<boost::uint32_t, boost::ulong_long_type> unsigned_types;
  typedef mpl::list<long double> float_types;

  tommath_int() { detail::check_tommath_result(mp_init(&m_data)); }
  tommath_int(const tommath_int &o) {
    detail::check_tommath_result(
        mp_init_copy(&m_data, const_cast<::mp_int *>(&o.m_data)));
  }
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
  tommath_int(tommath_int &&o) BOOST_NOEXCEPT {
    m_data = o.m_data;
    o.m_data.dp = 0;
  }
  tommath_int &operator=(tommath_int &&o) {
    mp_exch(&m_data, &o.m_data);
    return *this;
  }
#endif
  tommath_int &operator=(const tommath_int &o) {
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    if (o.m_data.dp)
      detail::check_tommath_result(
          mp_copy(const_cast<::mp_int *>(&o.m_data), &m_data));
    return *this;
  }
  tommath_int &operator=(boost::ulong_long_type i) {
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    boost::ulong_long_type mask =
        ((1uLL << std::numeric_limits<unsigned>::digits) - 1);
    unsigned shift = 0;
    ::mp_int t;
    detail::check_tommath_result(mp_init(&t));
    mp_zero(&m_data);
    while (i) {
      detail::check_tommath_result(
          mp_set_int(&t, static_cast<unsigned>(i & mask)));
      if (shift)
        detail::check_tommath_result(mp_mul_2d(&t, shift, &t));
      detail::check_tommath_result((mp_add(&m_data, &t, &m_data)));
      shift += std::numeric_limits<unsigned>::digits;
      i >>= std::numeric_limits<unsigned>::digits;
    }
    mp_clear(&t);
    return *this;
  }
  tommath_int &operator=(boost::long_long_type i) {
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    bool neg = i < 0;
    *this = boost::multiprecision::detail::unsigned_abs(i);
    if (neg)
      detail::check_tommath_result(mp_neg(&m_data, &m_data));
    return *this;
  }
  //
  // Note that although mp_set_int takes an unsigned long as an argument
  // it only sets the first 32-bits to the result, and ignores the rest.
  // So use uint32_t as the largest type to pass to this function.
  //
  tommath_int &operator=(boost::uint32_t i) {
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    detail::check_tommath_result((mp_set_int(&m_data, i)));
    return *this;
  }
  tommath_int &operator=(boost::int32_t i) {
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    bool neg = i < 0;
    *this = boost::multiprecision::detail::unsigned_abs(i);
    if (neg)
      detail::check_tommath_result(mp_neg(&m_data, &m_data));
    return *this;
  }
  tommath_int &operator=(long double a) {
    using std::floor;
    using std::frexp;
    using std::ldexp;

    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));

    if (a == 0) {
      detail::check_tommath_result(mp_set_int(&m_data, 0));
      return *this;
    }

    if (a == 1) {
      detail::check_tommath_result(mp_set_int(&m_data, 1));
      return *this;
    }

    BOOST_ASSERT(!(boost::math::isinf)(a));
    BOOST_ASSERT(!(boost::math::isnan)(a));

    int e;
    long double f, term;
    detail::check_tommath_result(mp_set_int(&m_data, 0u));
    ::mp_int t;
    detail::check_tommath_result(mp_init(&t));

    f = frexp(a, &e);

    static const int shift = std::numeric_limits<int>::digits - 1;

    while (f) {
      // extract int sized bits from f:
      f = ldexp(f, shift);
      term = floor(f);
      e -= shift;
      detail::check_tommath_result(mp_mul_2d(&m_data, shift, &m_data));
      if (term > 0) {
        detail::check_tommath_result(mp_set_int(&t, static_cast<int>(term)));
        detail::check_tommath_result(mp_add(&m_data, &t, &m_data));
      } else {
        detail::check_tommath_result(mp_set_int(&t, static_cast<int>(-term)));
        detail::check_tommath_result(mp_sub(&m_data, &t, &m_data));
      }
      f -= term;
    }
    if (e > 0)
      detail::check_tommath_result(mp_mul_2d(&m_data, e, &m_data));
    else if (e < 0) {
      tommath_int t2;
      detail::check_tommath_result(mp_div_2d(&m_data, -e, &m_data, &t2.data()));
    }
    mp_clear(&t);
    return *this;
  }
  tommath_int &operator=(const char *s) {
    //
    // We don't use libtommath's own routine because it doesn't error check the
    // input :-(
    //
    if (m_data.dp == 0)
      detail::check_tommath_result(mp_init(&m_data));
    std::size_t n = s ? std::strlen(s) : 0;
    *this = static_cast<boost::uint32_t>(0u);
    unsigned radix = 10;
    bool isneg = false;
    if (n && (*s == '-')) {
      --n;
      ++s;
      isneg = true;
    }
    if (n && (*s == '0')) {
      if ((n > 1) && ((s[1] == 'x') || (s[1] == 'X'))) {
        radix = 16;
        s += 2;
        n -= 2;
      } else {
        radix = 8;
        n -= 1;
      }
    }
    if (n) {
      if (radix == 8 || radix == 16) {
        unsigned shift = radix == 8 ? 3 : 4;
        unsigned block_count = DIGIT_BIT / shift;
        unsigned block_shift = shift * block_count;
        boost::ulong_long_type val, block;
        while (*s) {
          block = 0;
          for (unsigned i = 0; (i < block_count); ++i) {
            if (*s >= '0' && *s <= '9')
              val = *s - '0';
            else if (*s >= 'a' && *s <= 'f')
              val = 10 + *s - 'a';
            else if (*s >= 'A' && *s <= 'F')
              val = 10 + *s - 'A';
            else
              val = 400;
            if (val > radix) {
              BOOST_THROW_EXCEPTION(std::runtime_error(
                  "Unexpected content found while parsing character string."));
            }
            block <<= shift;
            block |= val;
            if (!*++s) {
              // final shift is different:
              block_shift = (i + 1) * shift;
              break;
            }
          }
          detail::check_tommath_result(
              mp_mul_2d(&data(), block_shift, &data()));
          if (data().used)
            data().dp[0] |= block;
          else
            *this = block;
        }
      } else {
        // Base 10, we extract blocks of size 10^9 at a time, that way
        // the number of multiplications is kept to a minimum:
        boost::uint32_t block_mult = 1000000000;
        while (*s) {
          boost::uint32_t block = 0;
          for (unsigned i = 0; i < 9; ++i) {
            boost::uint32_t val;
            if (*s >= '0' && *s <= '9')
              val = *s - '0';
            else
              BOOST_THROW_EXCEPTION(std::runtime_error(
                  "Unexpected character encountered in input."));
            block *= 10;
            block += val;
            if (!*++s) {
              static const boost::uint32_t block_multiplier[9] = {
                  10,      100,      1000,      10000,     100000,
                  1000000, 10000000, 100000000, 1000000000};
              block_mult = block_multiplier[i];
              break;
            }
          }
          tommath_int t;
          t = block_mult;
          eval_multiply(*this, t);
          t = block;
          eval_add(*this, t);
        }
      }
    }
    if (isneg)
      this->negate();
    return *this;
  }
  std::string str(std::streamsize /*digits*/, std::ios_base::fmtflags f) const {
    BOOST_ASSERT(m_data.dp);
    int base = 10;
    if ((f & std::ios_base::oct) == std::ios_base::oct)
      base = 8;
    else if ((f & std::ios_base::hex) == std::ios_base::hex)
      base = 16;
    //
    // sanity check, bases 8 and 16 are only available for positive numbers:
    //
    if ((base != 10) && m_data.sign)
      BOOST_THROW_EXCEPTION(
          std::runtime_error("Formatted output in bases 8 or 16 is only "
                             "available for positive numbers"));
    int s;
    detail::check_tommath_result(
        mp_radix_size(const_cast<::mp_int *>(&m_data), base, &s));
    boost::scoped_array<char> a(new char[s + 1]);
    detail::check_tommath_result(
        mp_toradix_n(const_cast<::mp_int *>(&m_data), a.get(), base, s + 1));
    std::string result = a.get();
    if (f & std::ios_base::uppercase)
      for (size_t i = 0; i < result.length(); ++i)
        result[i] = std::toupper(result[i]);
    if ((base != 10) && (f & std::ios_base::showbase)) {
      int pos = result[0] == '-' ? 1 : 0;
      const char *pp =
          base == 8 ? "0" : (f & std::ios_base::uppercase) ? "0X" : "0x";
      result.insert(static_cast<std::string::size_type>(pos), pp);
    }
    if ((f & std::ios_base::showpos) && (result[0] != '-'))
      result.insert(static_cast<std::string::size_type>(0), 1, '+');
    return result;
  }
  ~tommath_int() {
    if (m_data.dp)
      mp_clear(&m_data);
  }
  void negate() {
    BOOST_ASSERT(m_data.dp);
    mp_neg(&m_data, &m_data);
  }
  int compare(const tommath_int &o) const {
    BOOST_ASSERT(m_data.dp && o.m_data.dp);
    return mp_cmp(const_cast<::mp_int *>(&m_data),
                  const_cast<::mp_int *>(&o.m_data));
  }
  template <class V> int compare(V v) const {
    tommath_int d;
    tommath_int t(*this);
    detail::check_tommath_result(mp_shrink(&t.data()));
    d = v;
    return t.compare(d);
  }
  ::mp_int &data() {
    BOOST_ASSERT(m_data.dp);
    return m_data;
  }
  const ::mp_int &data() const {
    BOOST_ASSERT(m_data.dp);
    return m_data;
  }
  void swap(tommath_int &o) BOOST_NOEXCEPT { mp_exch(&m_data, &o.data()); }

protected:
  ::mp_int m_data;
};

#define BOOST_MP_TOMMATH_BIT_OP_CHECK(x)                                       \
  if (SIGN(&x.data()))                                                         \
  BOOST_THROW_EXCEPTION(std::runtime_error(                                    \
      "Bitwise operations on libtommath negative valued integers are "         \
      "disabled as they produce unpredictable results"))

int eval_get_sign(const tommath_int &val);

inline void eval_add(tommath_int &t, const tommath_int &o) {
  detail::check_tommath_result(
      mp_add(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
}
inline void eval_subtract(tommath_int &t, const tommath_int &o) {
  detail::check_tommath_result(
      mp_sub(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
}
inline void eval_multiply(tommath_int &t, const tommath_int &o) {
  detail::check_tommath_result(
      mp_mul(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
}
inline void eval_divide(tommath_int &t, const tommath_int &o) {
  using default_ops::eval_is_zero;
  tommath_int temp;
  if (eval_is_zero(o))
    BOOST_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
  detail::check_tommath_result(mp_div(
      &t.data(), const_cast<::mp_int *>(&o.data()), &t.data(), &temp.data()));
}
inline void eval_modulus(tommath_int &t, const tommath_int &o) {
  using default_ops::eval_is_zero;
  if (eval_is_zero(o))
    BOOST_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
  bool neg = eval_get_sign(t) < 0;
  bool neg2 = eval_get_sign(o) < 0;
  detail::check_tommath_result(
      mp_mod(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
  if ((neg != neg2) && (eval_get_sign(t) != 0)) {
    t.negate();
    detail::check_tommath_result(
        mp_add(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
    t.negate();
  } else if (neg && (t.compare(o) == 0)) {
    mp_zero(&t.data());
  }
}
template <class UI> inline void eval_left_shift(tommath_int &t, UI i) {
  detail::check_tommath_result(
      mp_mul_2d(&t.data(), static_cast<unsigned>(i), &t.data()));
}
template <class UI> inline void eval_right_shift(tommath_int &t, UI i) {
  using default_ops::eval_decrement;
  using default_ops::eval_increment;
  bool neg = eval_get_sign(t) < 0;
  tommath_int d;
  if (neg)
    eval_increment(t);
  detail::check_tommath_result(
      mp_div_2d(&t.data(), static_cast<unsigned>(i), &t.data(), &d.data()));
  if (neg)
    eval_decrement(t);
}
template <class UI>
inline void eval_left_shift(tommath_int &t, const tommath_int &v, UI i) {
  detail::check_tommath_result(mp_mul_2d(const_cast<::mp_int *>(&v.data()),
                                         static_cast<unsigned>(i), &t.data()));
}
/*
template <class UI>
inline void eval_right_shift(tommath_int& t, const tommath_int& v, UI i)
{
   tommath_int d;
   detail::check_tommath_result(mp_div_2d(const_cast< ::mp_int*>(&v.data()),
static_cast<unsigned long>(i), &t.data(), &d.data()));
}
*/
inline void eval_bitwise_and(tommath_int &result, const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(result);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(mp_and(
      &result.data(), const_cast<::mp_int *>(&v.data()), &result.data()));
}

inline void eval_bitwise_or(tommath_int &result, const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(result);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(
      mp_or(&result.data(), const_cast<::mp_int *>(&v.data()), &result.data()));
}

inline void eval_bitwise_xor(tommath_int &result, const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(result);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(mp_xor(
      &result.data(), const_cast<::mp_int *>(&v.data()), &result.data()));
}

inline void eval_add(tommath_int &t, const tommath_int &p,
                     const tommath_int &o) {
  detail::check_tommath_result(mp_add(const_cast<::mp_int *>(&p.data()),
                                      const_cast<::mp_int *>(&o.data()),
                                      &t.data()));
}
inline void eval_subtract(tommath_int &t, const tommath_int &p,
                          const tommath_int &o) {
  detail::check_tommath_result(mp_sub(const_cast<::mp_int *>(&p.data()),
                                      const_cast<::mp_int *>(&o.data()),
                                      &t.data()));
}
inline void eval_multiply(tommath_int &t, const tommath_int &p,
                          const tommath_int &o) {
  detail::check_tommath_result(mp_mul(const_cast<::mp_int *>(&p.data()),
                                      const_cast<::mp_int *>(&o.data()),
                                      &t.data()));
}
inline void eval_divide(tommath_int &t, const tommath_int &p,
                        const tommath_int &o) {
  using default_ops::eval_is_zero;
  tommath_int d;
  if (eval_is_zero(o))
    BOOST_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
  detail::check_tommath_result(mp_div(const_cast<::mp_int *>(&p.data()),
                                      const_cast<::mp_int *>(&o.data()),
                                      &t.data(), &d.data()));
}
inline void eval_modulus(tommath_int &t, const tommath_int &p,
                         const tommath_int &o) {
  using default_ops::eval_is_zero;
  if (eval_is_zero(o))
    BOOST_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
  bool neg = eval_get_sign(p) < 0;
  bool neg2 = eval_get_sign(o) < 0;
  detail::check_tommath_result(mp_mod(const_cast<::mp_int *>(&p.data()),
                                      const_cast<::mp_int *>(&o.data()),
                                      &t.data()));
  if ((neg != neg2) && (eval_get_sign(t) != 0)) {
    t.negate();
    detail::check_tommath_result(
        mp_add(&t.data(), const_cast<::mp_int *>(&o.data()), &t.data()));
    t.negate();
  } else if (neg && (t.compare(o) == 0)) {
    mp_zero(&t.data());
  }
}

inline void eval_bitwise_and(tommath_int &result, const tommath_int &u,
                             const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(u);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(mp_and(const_cast<::mp_int *>(&u.data()),
                                      const_cast<::mp_int *>(&v.data()),
                                      &result.data()));
}

inline void eval_bitwise_or(tommath_int &result, const tommath_int &u,
                            const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(u);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(mp_or(const_cast<::mp_int *>(&u.data()),
                                     const_cast<::mp_int *>(&v.data()),
                                     &result.data()));
}

inline void eval_bitwise_xor(tommath_int &result, const tommath_int &u,
                             const tommath_int &v) {
  BOOST_MP_TOMMATH_BIT_OP_CHECK(u);
  BOOST_MP_TOMMATH_BIT_OP_CHECK(v);
  detail::check_tommath_result(mp_xor(const_cast<::mp_int *>(&u.data()),
                                      const_cast<::mp_int *>(&v.data()),
                                      &result.data()));
}
/*
inline void eval_complement(tommath_int& result, const tommath_int& u)
{
   //
   // Although this code works, it doesn't really do what the user might
expect....
   // and it's hard to see how it ever could.  Disabled for now:
   //
   result = u;
   for(int i = 0; i < result.data().used; ++i)
   {
      result.data().dp[i] = MP_MASK & ~(result.data().dp[i]);
   }
   //
   // We now need to pad out the left of the value with 1's to round up to a
whole number of
   // CHAR_BIT * sizeof(mp_digit) units.  Otherwise we'll end up with a very
strange number of
   // bits set!
   //
   unsigned shift = result.data().used * DIGIT_BIT;    // How many bits we're
actually using
   // How many bits we actually need, reduced by one to account for a mythical
sign bit: int padding = result.data().used *
std::numeric_limits<mp_digit>::digits - shift - 1; while(padding >=
std::numeric_limits<mp_digit>::digits) padding -=
std::numeric_limits<mp_digit>::digits;

   // Create a mask providing the extra bits we need and add to result:
   tommath_int mask;
   mask = static_cast<boost::long_long_type>((1u << padding) - 1);
   eval_left_shift(mask, shift);
   add(result, mask);
}
*/
inline bool eval_is_zero(const tommath_int &val) {
  return mp_iszero(&val.data());
}
inline int eval_get_sign(const tommath_int &val) {
  return mp_iszero(&val.data()) ? 0 : SIGN(&val.data()) ? -1 : 1;
}
/*
template <class A>
inline void eval_convert_to(A* result, const tommath_int& val)
{
   *result = boost::lexical_cast<A>(val.str(0, std::ios_base::fmtflags(0)));
}
inline void eval_convert_to(char* result, const tommath_int& val)
{
   *result = static_cast<char>(boost::lexical_cast<int>(val.str(0,
std::ios_base::fmtflags(0))));
}
inline void eval_convert_to(unsigned char* result, const tommath_int& val)
{
   *result = static_cast<unsigned char>(boost::lexical_cast<unsigned>(val.str(0,
std::ios_base::fmtflags(0))));
}
inline void eval_convert_to(signed char* result, const tommath_int& val)
{
   *result = static_cast<signed char>(boost::lexical_cast<int>(val.str(0,
std::ios_base::fmtflags(0))));
}
*/
inline void eval_abs(tommath_int &result, const tommath_int &val) {
  detail::check_tommath_result(
      mp_abs(const_cast<::mp_int *>(&val.data()), &result.data()));
}
inline void eval_gcd(tommath_int &result, const tommath_int &a,
                     const tommath_int &b) {
  detail::check_tommath_result(mp_gcd(const_cast<::mp_int *>(&a.data()),
                                      const_cast<::mp_int *>(&b.data()),
                                      const_cast<::mp_int *>(&result.data())));
}
inline void eval_lcm(tommath_int &result, const tommath_int &a,
                     const tommath_int &b) {
  detail::check_tommath_result(mp_lcm(const_cast<::mp_int *>(&a.data()),
                                      const_cast<::mp_int *>(&b.data()),
                                      const_cast<::mp_int *>(&result.data())));
}
inline void eval_powm(tommath_int &result, const tommath_int &base,
                      const tommath_int &p, const tommath_int &m) {
  if (eval_get_sign(p) < 0) {
    BOOST_THROW_EXCEPTION(
        std::runtime_error("powm requires a positive exponent."));
  }
  detail::check_tommath_result(mp_exptmod(
      const_cast<::mp_int *>(&base.data()), const_cast<::mp_int *>(&p.data()),
      const_cast<::mp_int *>(&m.data()), &result.data()));
}

inline void eval_qr(const tommath_int &x, const tommath_int &y, tommath_int &q,
                    tommath_int &r) {
  detail::check_tommath_result(mp_div(const_cast<::mp_int *>(&x.data()),
                                      const_cast<::mp_int *>(&y.data()),
                                      &q.data(), &r.data()));
}

inline unsigned eval_lsb(const tommath_int &val) {
  int c = eval_get_sign(val);
  if (c == 0) {
    BOOST_THROW_EXCEPTION(std::range_error("No bits were set in the operand."));
  }
  if (c < 0) {
    BOOST_THROW_EXCEPTION(
        std::range_error("Testing individual bits in negative values is not "
                         "supported - results are undefined."));
  }
  return mp_cnt_lsb(const_cast<::mp_int *>(&val.data()));
}

inline unsigned eval_msb(const tommath_int &val) {
  int c = eval_get_sign(val);
  if (c == 0) {
    BOOST_THROW_EXCEPTION(std::range_error("No bits were set in the operand."));
  }
  if (c < 0) {
    BOOST_THROW_EXCEPTION(
        std::range_error("Testing individual bits in negative values is not "
                         "supported - results are undefined."));
  }
  return mp_count_bits(const_cast<::mp_int *>(&val.data())) - 1;
}

template <class Integer>
inline typename enable_if<is_unsigned<Integer>, Integer>::type
eval_integer_modulus(const tommath_int &x, Integer val) {
  static const mp_digit m = (static_cast<mp_digit>(1) << DIGIT_BIT) - 1;
  if (val <= m) {
    mp_digit d;
    detail::check_tommath_result(mp_mod_d(const_cast<::mp_int *>(&x.data()),
                                          static_cast<mp_digit>(val), &d));
    return d;
  } else {
    return default_ops::eval_integer_modulus(x, val);
  }
}
template <class Integer>
inline typename enable_if<is_signed<Integer>, Integer>::type
eval_integer_modulus(const tommath_int &x, Integer val) {
  return eval_integer_modulus(x,
                              boost::multiprecision::detail::unsigned_abs(val));
}

inline std::size_t hash_value(const tommath_int &val) {
  std::size_t result = 0;
  std::size_t len = val.data().used;
  for (std::size_t i = 0; i < len; ++i)
    boost::hash_combine(result, val.data().dp[i]);
  boost::hash_combine(result, val.data().sign);
  return result;
}

} // namespace backends

using boost::multiprecision::backends::tommath_int;

template <>
struct number_category<tommath_int> : public mpl::int_<number_kind_integer> {};

typedef number<tommath_int> tom_int;
typedef rational_adaptor<tommath_int> tommath_rational;
typedef number<tommath_rational> tom_rational;

} // namespace multiprecision
} // namespace boost

namespace std {

template <boost::multiprecision::expression_template_option ExpressionTemplates>
class numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>> {
  typedef boost::multiprecision::number<boost::multiprecision::tommath_int,
                                        ExpressionTemplates>
      number_type;

public:
  BOOST_STATIC_CONSTEXPR bool is_specialized = true;
  //
  // Largest and smallest numbers are bounded only by available memory, set
  // to zero:
  //
  static number_type(min)() { return number_type(); }
  static number_type(max)() { return number_type(); }
  static number_type lowest() { return (min)(); }
  BOOST_STATIC_CONSTEXPR int digits = INT_MAX;
  BOOST_STATIC_CONSTEXPR int digits10 = (INT_MAX / 1000) * 301L;
  BOOST_STATIC_CONSTEXPR int max_digits10 = digits10 + 3;
  BOOST_STATIC_CONSTEXPR bool is_signed = true;
  BOOST_STATIC_CONSTEXPR bool is_integer = true;
  BOOST_STATIC_CONSTEXPR bool is_exact = true;
  BOOST_STATIC_CONSTEXPR int radix = 2;
  static number_type epsilon() { return number_type(); }
  static number_type round_error() { return number_type(); }
  BOOST_STATIC_CONSTEXPR int min_exponent = 0;
  BOOST_STATIC_CONSTEXPR int min_exponent10 = 0;
  BOOST_STATIC_CONSTEXPR int max_exponent = 0;
  BOOST_STATIC_CONSTEXPR int max_exponent10 = 0;
  BOOST_STATIC_CONSTEXPR bool has_infinity = false;
  BOOST_STATIC_CONSTEXPR bool has_quiet_NaN = false;
  BOOST_STATIC_CONSTEXPR bool has_signaling_NaN = false;
  BOOST_STATIC_CONSTEXPR float_denorm_style has_denorm = denorm_absent;
  BOOST_STATIC_CONSTEXPR bool has_denorm_loss = false;
  static number_type infinity() { return number_type(); }
  static number_type quiet_NaN() { return number_type(); }
  static number_type signaling_NaN() { return number_type(); }
  static number_type denorm_min() { return number_type(); }
  BOOST_STATIC_CONSTEXPR bool is_iec559 = false;
  BOOST_STATIC_CONSTEXPR bool is_bounded = false;
  BOOST_STATIC_CONSTEXPR bool is_modulo = false;
  BOOST_STATIC_CONSTEXPR bool traps = false;
  BOOST_STATIC_CONSTEXPR bool tinyness_before = false;
  BOOST_STATIC_CONSTEXPR float_round_style round_style = round_toward_zero;
};

#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION

template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::digits;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::digits10;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::max_digits10;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_signed;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_integer;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_exact;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::radix;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::min_exponent;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::min_exponent10;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::max_exponent;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST int numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::max_exponent10;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::has_infinity;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::has_quiet_NaN;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<
    boost::multiprecision::number<boost::multiprecision::tommath_int,
                                  ExpressionTemplates>>::has_signaling_NaN;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST float_denorm_style
    numeric_limits<boost::multiprecision::number<
        boost::multiprecision::tommath_int, ExpressionTemplates>>::has_denorm;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::has_denorm_loss;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_iec559;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_bounded;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::is_modulo;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::traps;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST bool numeric_limits<boost::multiprecision::number<
    boost::multiprecision::tommath_int, ExpressionTemplates>>::tinyness_before;
template <boost::multiprecision::expression_template_option ExpressionTemplates>
BOOST_CONSTEXPR_OR_CONST float_round_style
    numeric_limits<boost::multiprecision::number<
        boost::multiprecision::tommath_int, ExpressionTemplates>>::round_style;

#endif
} // namespace std

#endif


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <array>
#include <boost/functional/hash.hpp>  // IWYU pragma: keep
#include <cstddef>
#include <limits>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>

#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/SizeOfElement.hpp"
#include "Domain/Structure/Element.hpp"  // IWYU pragma: keep
#include "Domain/Structure/OrientationMapHelpers.hpp"
#include "Domain/Tags.hpp"  // IWYU pragma: keep
#include "Evolution/DiscontinuousGalerkin/Limiters/HwenoImpl.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/MinmodTci.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/MinmodType.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/SimpleWenoImpl.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoGridHelpers.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoHelpers.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoType.hpp"
#include "NumericalAlgorithms/Interpolation/RegularGridInterpolant.hpp"
#include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Options/Options.hpp"
#include "Utilities/Algorithm.hpp"
#include "Utilities/ErrorHandling/Assert.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeArray.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"

/// \cond
template <size_t VolumeDim>
class Direction;
template <size_t VolumeDim>
class ElementId;

namespace PUP {
class er;
}  // namespace PUP

namespace Limiters {
template <size_t VolumeDim, typename TagsToLimit>
class Weno;
}  // namespace Limiters
/// \endcond

namespace Limiters {
/// \ingroup LimitersGroup
/// \brief A compact-stencil WENO limiter for DG
///
/// Implements the simple WENO limiter of \cite Zhong2013 and the Hermite WENO
/// (HWENO) limiter of \cite Zhu2016 for an arbitrary set of tensors. These
/// limiters require communication only between nearest-neighbor elements, but
/// preserve the full order of the DG solution when the solution is smooth.
/// Full volume data is communicated between neighbors.
///
/// The limiter uses the minmod-based TVB troubled-cell indicator (TCI) of
/// \cite Cockburn1999 to identify elements that need limiting. The simple
/// WENO implementation follows the paper: it checks the TCI independently
/// to each tensor component, so that only certain tensor components may be
/// limited. The HWENO implementation checks the TCI for all tensor components,
/// and if any single component is troubled, then all components are limited.
/// Note that the HWENO paper, because it specializes the limiter to the
/// Newtonian Euler fluid system, uses a more sophisticated TCI that is adapted
/// to the particulars of the fluid system. We instead use the TVB indicator
/// because it is easily applied to a general set of tensors.
///
/// For each tensor component to limit, the new solution is obtained by WENO
/// reconstruction --- a linear combination of the local DG solution and a
/// "modified" solution from each neighbor element. For the simple WENO limiter,
/// the modified solution is obtained by simply extrapolating the neighbor
/// solution onto the troubled element. For the HWENO limiter, the modified
/// solution is obtained by a least-squares fit to the solution across multiple
/// neighboring elements.
///
/// To reconstruct the WENO solution from the local solution and the modified
/// neighbor solutions, the standard WENO procedure is followed. We use the
/// oscillation indicator of \cite Dumbser2007, modified for use on the
/// square/cube grids of SpECTRE. We favor this indicator because portions of
/// the work can be precomputed, leading to an oscillation measure that is
/// efficient to evaluate.
///
/// \warning
/// Limitations:
/// - Does not support non-Legendre bases; this is ASSERTed.
/// - Does not support h- or p-refinement; this is ASSERTed.
/// - Does not support curved elements; this is not enforced.
template <size_t VolumeDim, typename... Tags>
class Weno<VolumeDim, tmpl::list<Tags...>> {
 public:
  /// \brief The WenoType
  ///
  /// One of `Limiters::WenoType`. See the `Limiters::Weno`
  /// documentation for details.
  struct Type {
    using type = WenoType;
    static constexpr Options::String help = {"Type of WENO limiter"};
  };
  /// \brief The linear weight given to each neighbor
  ///
  /// This linear weight gets combined with the oscillation indicator to
  /// compute the weight for each WENO estimated solution. The standard value
  /// in the literature is 0.001; larger values may be better suited for
  /// problems with strong shocks, and smaller values may be better suited to
  /// smooth problems.
  struct NeighborWeight {
    using type = double;
    static type lower_bound() noexcept { return 1e-6; }
    static type upper_bound() noexcept { return 0.1; }
    static constexpr Options::String help = {
        "Linear weight for each neighbor element's solution"};
  };
  /// \brief The TVB constant for the minmod TCI
  ///
  /// See `Limiters::Minmod` documentation for details.
  struct TvbConstant {
    using type = double;
    static type lower_bound() noexcept { return 0.0; }
    static constexpr Options::String help = {"TVB constant 'm'"};
  };
  /// \brief Turn the limiter off
  ///
  /// This option exists to temporarily disable the limiter for debugging
  /// purposes. For problems where limiting is not needed, the preferred
  /// approach is to not compile the limiter into the executable.
  struct DisableForDebugging {
    using type = bool;
    static type suggested_value() noexcept { return false; }
    static constexpr Options::String help = {"Disable the limiter"};
  };
  using options =
      tmpl::list<Type, NeighborWeight, TvbConstant, DisableForDebugging>;
  static constexpr Options::String help = {"A WENO limiter for DG"};

  Weno(WenoType weno_type, double neighbor_linear_weight, double tvb_constant,
       bool disable_for_debugging = false) noexcept;

  Weno() noexcept = default;
  Weno(const Weno& /*rhs*/) = default;
  Weno& operator=(const Weno& /*rhs*/) = default;
  Weno(Weno&& /*rhs*/) noexcept = default;
  Weno& operator=(Weno&& /*rhs*/) noexcept = default;
  ~Weno() = default;

  // NOLINTNEXTLINE(google-runtime-references)
  void pup(PUP::er& p) noexcept;

  /// \brief Data to send to neighbor elements
  struct PackagedData {
    Variables<tmpl::list<Tags...>> volume_data;
    tuples::TaggedTuple<::Tags::Mean<Tags>...> means;
    Mesh<VolumeDim> mesh;
    std::array<double, VolumeDim> element_size =
        make_array<VolumeDim>(std::numeric_limits<double>::signaling_NaN());

    // NOLINTNEXTLINE(google-runtime-references)
    void pup(PUP::er& p) noexcept {
      p | volume_data;
      p | means;
      p | mesh;
      p | element_size;
    }
  };

  using package_argument_tags =
      tmpl::list<Tags..., domain::Tags::Mesh<VolumeDim>,
                 domain::Tags::SizeOfElement<VolumeDim>>;

  /// \brief Package data for sending to neighbor elements
  void package_data(gsl::not_null<PackagedData*> packaged_data,
                    const typename Tags::type&... tensors,
                    const Mesh<VolumeDim>& mesh,
                    const std::array<double, VolumeDim>& element_size,
                    const OrientationMap<VolumeDim>& orientation_map) const
      noexcept;

  using limit_tags = tmpl::list<Tags...>;
  using limit_argument_tags =
      tmpl::list<domain::Tags::Mesh<VolumeDim>,
                 domain::Tags::Element<VolumeDim>,
                 domain::Tags::SizeOfElement<VolumeDim>>;

  /// \brief Limit the solution on the element
  bool operator()(
      const gsl::not_null<std::add_pointer_t<typename Tags::type>>... tensors,
      const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
      const std::array<double, VolumeDim>& element_size,
      const std::unordered_map<
          std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, PackagedData,
          boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>&
          neighbor_data) const noexcept;

 private:
  template <size_t LocalDim, typename LocalTagList>
  // NOLINTNEXTLINE(readability-redundant-declaration) false positive
  friend bool operator==(const Weno<LocalDim, LocalTagList>& lhs,
                         const Weno<LocalDim, LocalTagList>& rhs) noexcept;

  WenoType weno_type_;
  double neighbor_linear_weight_;
  double tvb_constant_;
  bool disable_for_debugging_;
};

template <size_t VolumeDim, typename... Tags>
Weno<VolumeDim, tmpl::list<Tags...>>::Weno(
    const WenoType weno_type, const double neighbor_linear_weight,
    const double tvb_constant, const bool disable_for_debugging) noexcept
    : weno_type_(weno_type),
      neighbor_linear_weight_(neighbor_linear_weight),
      tvb_constant_(tvb_constant),
      disable_for_debugging_(disable_for_debugging) {}

template <size_t VolumeDim, typename... Tags>
// NOLINTNEXTLINE(google-runtime-references)
void Weno<VolumeDim, tmpl::list<Tags...>>::pup(PUP::er& p) noexcept {
  p | weno_type_;
  p | neighbor_linear_weight_;
  p | tvb_constant_;
  p | disable_for_debugging_;
}

template <size_t VolumeDim, typename... Tags>
void Weno<VolumeDim, tmpl::list<Tags...>>::package_data(
    const gsl::not_null<PackagedData*> packaged_data,
    const typename Tags::type&... tensors, const Mesh<VolumeDim>& mesh,
    const std::array<double, VolumeDim>& element_size,
    const OrientationMap<VolumeDim>& orientation_map) const noexcept {
  // By always initializing the PackagedData Variables member, we avoid an
  // assertion that arises from having a default-constructed Variables in a
  // disabled limiter. There is a performance cost, because the package_data()
  // function does non-zero work even for a disabled limiter... but since the
  // limiter should never be disabled in a production simulation, this cost
  // should never matter.
  (packaged_data->volume_data).initialize(mesh.number_of_grid_points());

  if (UNLIKELY(disable_for_debugging_)) {
    // Do not initialize packaged_data
    // (except for the Variables member "volume_data", see above)
    return;
  }

  const auto wrap_compute_means = [&mesh, &packaged_data](
                                      auto tag, const auto tensor) noexcept {
    for (size_t i = 0; i < tensor.size(); ++i) {
      // Compute the mean using the local orientation of the tensor and mesh.
      get<::Tags::Mean<decltype(tag)>>(packaged_data->means)[i] =
          mean_value(tensor[i], mesh);
    }
    return '0';
  };
  expand_pack(wrap_compute_means(Tags{}, tensors)...);

  packaged_data->element_size =
      orientation_map.permute_from_neighbor(element_size);

  const auto wrap_copy_tensor = [&packaged_data](auto tag,
                                                 const auto tensor) noexcept {
    get<decltype(tag)>(packaged_data->volume_data) = tensor;
    return '0';
  };
  expand_pack(wrap_copy_tensor(Tags{}, tensors)...);
  packaged_data->volume_data = orient_variables(
      packaged_data->volume_data, mesh.extents(), orientation_map);

  packaged_data->mesh = orientation_map(mesh);
}

template <size_t VolumeDim, typename... Tags>
bool Weno<VolumeDim, tmpl::list<Tags...>>::operator()(
    const gsl::not_null<std::add_pointer_t<typename Tags::type>>... tensors,
    const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
    const std::array<double, VolumeDim>& element_size,
    const std::unordered_map<
        std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, PackagedData,
        boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>&
        neighbor_data) const noexcept {
  if (UNLIKELY(disable_for_debugging_)) {
    // Do not modify input tensors
    return false;
  }

  // Check that basis is LGL or LG
  // A Legendre basis is assumed for the oscillation indicator (used in both
  // SimpleWeno and Hweno) and in the Hweno reconstruction.
  ASSERT(mesh.basis() == make_array<VolumeDim>(Spectral::Basis::Legendre),
         "Unsupported basis: " << mesh);
  ASSERT(mesh.quadrature() ==
                 make_array<VolumeDim>(Spectral::Quadrature::GaussLobatto) or
             mesh.quadrature() ==
                 make_array<VolumeDim>(Spectral::Quadrature::Gauss),
         "Unsupported quadrature: " << mesh);

  // Enforce restrictions on h-refinement, p-refinement
  if (UNLIKELY(alg::any_of(element.neighbors(),
                           [](const auto& direction_neighbors) noexcept {
                             return direction_neighbors.second.size() != 1;
                           }))) {
    ERROR("The Weno limiter does not yet support h-refinement");
    // Removing this limitation will require:
    // - Generalizing the computation of the modified neighbor solutions.
    // - Generalizing the WENO weighted sum for multiple neighbors in each
    //   direction.
  }
  alg::for_each(neighbor_data, [&mesh](const auto& neighbor_and_data) noexcept {
    if (UNLIKELY(neighbor_and_data.second.mesh != mesh)) {
      ERROR("The Weno limiter does not yet support p-refinement");
      // Removing this limitation will require generalizing the
      // computation of the modified neighbor solutions.
    }
  });

  if (weno_type_ == WenoType::Hweno) {
    // Troubled-cell detection for HWENO flags the element for limiting if any
    // component of any tensor needs limiting.
    const bool cell_is_troubled =
        Tci::tvb_minmod_indicator<VolumeDim, PackagedData, Tags...>(
            tvb_constant_, (*tensors)..., mesh, element, element_size,
            neighbor_data);
    if (not cell_is_troubled) {
      // No limiting is needed
      return false;
    }

    std::unordered_map<
        std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, DataVector,
        boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
        modified_neighbor_solution_buffer{};
    for (const auto& neighbor_and_data : neighbor_data) {
      const auto& neighbor = neighbor_and_data.first;
      modified_neighbor_solution_buffer.insert(
          make_pair(neighbor, DataVector(mesh.number_of_grid_points())));
    }

    EXPAND_PACK_LEFT_TO_RIGHT(Weno_detail::hweno_impl<Tags>(
        make_not_null(&modified_neighbor_solution_buffer), tensors,
        neighbor_linear_weight_, mesh, element, neighbor_data));
    return true;  // cell_is_troubled

  } else if (weno_type_ == WenoType::SimpleWeno) {
    // Buffers and pre-computations for TCI
    Minmod_detail::BufferWrapper<VolumeDim> tci_buffer(mesh);
    const auto effective_neighbor_sizes =
        Minmod_detail::compute_effective_neighbor_sizes(element, neighbor_data);

    // Buffers for simple WENO implementation
    std::unordered_map<
        std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
        intrp::RegularGrid<VolumeDim>,
        boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
        interpolator_buffer{};
    std::unordered_map<
        std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, DataVector,
        boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
        modified_neighbor_solution_buffer{};

    bool some_component_was_limited = false;

    const auto wrap_minmod_tci_and_simple_weno_impl =
        [this, &some_component_was_limited, &tci_buffer, &interpolator_buffer,
         &modified_neighbor_solution_buffer, &mesh, &element, &element_size,
         &neighbor_data,
         &effective_neighbor_sizes](auto tag, const auto tensor) noexcept {
          for (size_t tensor_storage_index = 0;
               tensor_storage_index < tensor->size(); ++tensor_storage_index) {
            // Check TCI
            const auto effective_neighbor_means =
                Minmod_detail::compute_effective_neighbor_means<decltype(tag)>(
                    tensor_storage_index, element, neighbor_data);
            const bool component_needs_limiting = Tci::tvb_minmod_indicator(
                make_not_null(&tci_buffer), tvb_constant_,
                (*tensor)[tensor_storage_index], mesh, element, element_size,
                effective_neighbor_means, effective_neighbor_sizes);

            if (component_needs_limiting) {
              if (modified_neighbor_solution_buffer.empty()) {
                // Allocate the neighbor solution buffers only if the limiter is
                // triggered. This reduces allocation when no limiting occurs.
                for (const auto& neighbor_and_data : neighbor_data) {
                  const auto& neighbor = neighbor_and_data.first;
                  modified_neighbor_solution_buffer.insert(make_pair(
                      neighbor, DataVector(mesh.number_of_grid_points())));
                }
              }
              Weno_detail::simple_weno_impl<decltype(tag)>(
                  make_not_null(&interpolator_buffer),
                  make_not_null(&modified_neighbor_solution_buffer), tensor,
                  neighbor_linear_weight_, tensor_storage_index, mesh, element,
                  neighbor_data);
              some_component_was_limited = true;
            }
          }
          return '0';
        };
    expand_pack(wrap_minmod_tci_and_simple_weno_impl(Tags{}, tensors)...);
    return some_component_was_limited;  // cell_is_troubled
  } else {
    ERROR("WENO limiter not implemented for WenoType: " << weno_type_);
  }

  return false;  // cell_is_troubled
}

template <size_t LocalDim, typename LocalTagList>
bool operator==(const Weno<LocalDim, LocalTagList>& lhs,
                const Weno<LocalDim, LocalTagList>& rhs) noexcept {
  return lhs.weno_type_ == rhs.weno_type_ and
         lhs.neighbor_linear_weight_ == rhs.neighbor_linear_weight_ and
         lhs.tvb_constant_ == rhs.tvb_constant_ and
         lhs.disable_for_debugging_ == rhs.disable_for_debugging_;
}

template <size_t VolumeDim, typename TagList>
bool operator!=(const Weno<VolumeDim, TagList>& lhs,
                const Weno<VolumeDim, TagList>& rhs) noexcept {
  return not(lhs == rhs);
}

}  // namespace Limiters


//=============================================================================================================
/**
* @file     mne_project_to_surface.cpp
* @author   Jana Kiesel <jana.kiesel@tu-ilmenau.de>;
*           Matti Hamalainen <msh@nmr.mgh.harvard.edu>
* @version  1.0
* @date     August, 2016
*
* @section  LICENSE
*
* Copyright (C) 2016, Jana Kiesel and Matti Hamalainen. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    MNEProjectToSurface class definition.
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "mne_project_to_surface.h"

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include <mne/mne_bem_surface.h>
#include <mne/mne_surface.h>

//*************************************************************************************************************
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================

//*************************************************************************************************************
//=============================================================================================================
// Eigen INCLUDES
//=============================================================================================================

#include <Eigen/Geometry>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace MNELIB;
using namespace Eigen;

//*************************************************************************************************************
//=============================================================================================================
// DEFINE GLOBAL METHODS
//=============================================================================================================

//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

MNEProjectToSurface::MNEProjectToSurface()
: r1(MatrixX3f::Zero(1,3))
, r12(MatrixX3f::Zero(1,3))
, r13(MatrixX3f::Zero(1,3))
, nn(MatrixX3f::Zero(1,3))
, a(VectorXf::Zero(1))
, b(VectorXf::Zero(1))
, c(VectorXf::Zero(1))
, det(VectorXf::Zero(1))
{

}

//*************************************************************************************************************

MNEProjectToSurface::MNEProjectToSurface(const MNEBemSurface &p_MNEBemSurf)
: r1(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, r12(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, r13(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, nn(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, a(VectorXf::Zero(p_MNEBemSurf.ntri))
, b(VectorXf::Zero(p_MNEBemSurf.ntri))
, c(VectorXf::Zero(p_MNEBemSurf.ntri))
, det(VectorXf::Zero(p_MNEBemSurf.ntri))
{
    for (int i = 0; i < p_MNEBemSurf.ntri; ++i)
    {
        r1.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,0));
        r12.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,1)) - r1.row(i);
        r13.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,2)) - r1.row(i);
        a(i) = r12.row(i) * r12.row(i).transpose();
        b(i) = r13.row(i) * r13.row(i).transpose();
        c(i) = r12.row(i) * r13.row(i).transpose();
    }

    if (!(p_MNEBemSurf.tri_nn.isZero(0)))
    {
        nn = p_MNEBemSurf.tri_nn.cast<float>();
    }
    else
    {
        for (int i = 0; i < p_MNEBemSurf.ntri; ++i)
        {
            nn.row(i) = r12.row(i).transpose().cross(r13.row(i).transpose()).transpose();
        }
    }
    det = (a.array()*b.array() - c.array()*c.array()).matrix();
}

//*************************************************************************************************************

MNEProjectToSurface::MNEProjectToSurface(const MNESurface &p_MNESurf)
: r1(MatrixX3f::Zero(p_MNESurf.ntri,3))
, r12(MatrixX3f::Zero(p_MNESurf.ntri,3))
, r13(MatrixX3f::Zero(p_MNESurf.ntri,3))
, nn(MatrixX3f::Zero(p_MNESurf.ntri,3))
, a(VectorXf::Zero(p_MNESurf.ntri))
, b(VectorXf::Zero(p_MNESurf.ntri))
, c(VectorXf::Zero(p_MNESurf.ntri))
, det(VectorXf::Zero(p_MNESurf.ntri))
{
    for (int i = 0; i < p_MNESurf.ntri; ++i)
    {
        r1.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,0));
        r12.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,1)) - r1.row(i);
        r13.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,2)) - r1.row(i);
        nn.row(i) = r12.row(i).transpose().cross(r13.row(i).transpose()).transpose();
        a(i) = r12.row(i) * r12.row(i).transpose();
        b(i) = r13.row(i) * r13.row(i).transpose();
        c(i) = r12.row(i) * r13.row(i).transpose();
    }

    det = (a.array()*b.array() - c.array()*c.array()).matrix();
}

//*************************************************************************************************************

bool MNEProjectToSurface::mne_find_closest_on_surface(const MatrixXf &r, const int np, MatrixXf &rTri,
                                                      VectorXi &nearest, VectorXf &dist)
{
    nearest.resize(np);
    dist.resize(np);
    if (this->r1.isZero(0))
    {
        qDebug() << "No surface loaded to make the projection./n";
        return false;
    }
    int bestTri = -1;
    float bestDist = -1;
    Vector3f rTriK;
    for (int k = 0; k < np; ++k)
    {
        /*
         * To do: decide_search_restriction for the use in an iterative closest point to plane algorithm
         * For now it's OK to go through all triangles.
         */
        if (!this->mne_project_to_surface(r.row(k).transpose(), rTriK, bestTri, bestDist))
        {
            qDebug() << "The projection of point number " << k << " didn't work./n";
            return false;
        }
        rTri.row(k) = rTriK.transpose();
        nearest[k] = bestTri;
        dist[k] = bestDist;
    }
    return true;
}

//*************************************************************************************************************

bool MNEProjectToSurface::mne_project_to_surface(const Vector3f &r, Vector3f &rTri, int &bestTri, float &bestDist)
{
    float p = 0, q = 0, p0 = 0, q0 = 0, dist0 = 0;
    bestDist = 0.0f;
    bestTri = -1;
    for (int tri = 0; tri < a .size(); ++tri)
    {
        if (!this->nearest_triangle_point(r, tri, p0, q0, dist0))
        {
            qDebug() << "The projection on triangle " << tri << " didn't work./n";
            return false;
        }

        if ((bestTri < 0) || (std::fabs(dist0) < std::fabs(bestDist)))
        {
            bestDist = dist0;
            p = p0;
            q = q0;
            bestTri = tri;
        }
    }

    if (bestTri >= 0)
    {
        if (!this->project_to_triangle(rTri, p, q, bestTri))
        {
            qDebug() << "The coordinate transform to cartesian system didn't work./n";
            return false;
        }
        return true;
    }

    qDebug() << "No best Triangle found./n";
    return false;
}

//*************************************************************************************************************

bool MNEProjectToSurface::nearest_triangle_point(const Vector3f &r, const int tri, float &p, float &q, float &dist)
{
    //Calculate some helpers
    Vector3f rr = r - this->r1.row(tri).transpose(); //Vector from triangle corner #1 to r
    float v1 = this->r12.row(tri)*rr;
    float v2 = this->r13.row(tri)*rr;

    //Calculate the orthogonal projection of the point r on the plane
    dist = this->nn.row(tri)*rr;
    p = (this->b(tri)*v1 - this->c(tri)*v2)/det(tri);
    q = (this->a(tri)*v2 - this->c(tri)*v1)/det(tri);

    //If the point projects into the triangle we are done
    if (p >= 0.0 && p <= 1.0 && q >= 0.0 && q <= 1.0 && (p+q) <= 1.0)
         {
        return true;
    }

    /*
     * Tough: must investigate the sides
     * We might do something intelligent here. However, for now it is ok
     * to do it in the hard way
     */
    float p0, q0, t0, dist0, best, bestp, bestq;

    /*
     * Side 1 -> 2
     */
    p0 = p + (q * this->c(tri)) / this->a(tri);
    // Place the point in the corner if it is not on the side
    if (p0 < 0.0)
    {
        p0 = 0.0;
    }
    else if (p0 > 1.0)
    {
        p0 = 1.0;
    }
    q0 = 0;
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);

    best = dist0;
    bestp = p0;
    bestq = q0;
    /*
    * Side 2 -> 3
    */
    t0 = ((a(tri)-c(tri))*(-p) + (b(tri)-c(tri))*q)/(a(tri)+b(tri)-2*c(tri));
    // Place the point in the corner if it is not on the side
    if (t0 < 0.0)
    {
        t0 = 0.0;
    }
    else if (t0 > 1.0)
    {
        t0 = 1.0;
    }
    p0 = 1.0 - t0;
    q0 = t0;
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);
    if (dist0 < best)
    {
        best = dist0;
        bestp = p0;
        bestq = q0;
    }
    /*
    * Side 1 -> 3
    */
    p0 = 0.0;
    q0 = q + (p * c(tri))/b(tri);
    // Place the point in the corner if it is not on the side
    if (q0 < 0.0)
    {
        q0 = 0.0;

    }
    else if (q0 > 1.0)
    {
        q0 = 1.0;
    }
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);
    if (dist0 < best)
    {
        best = dist0;
        bestp = p0;
        bestq = q0;
    }
    dist = best;
    p = bestp;
    q = bestq;
    return true;
}

//*************************************************************************************************************

bool MNEProjectToSurface::project_to_triangle(Vector3f &rTri, const float p, const float q, const int tri)
{
    rTri = this->r1.row(tri) + p*this->r12.row(tri) + q*this->r13.row(tri);
    return true;
}


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef fiber_numerical_quadrature_hpp
#define fiber_numerical_quadrature_hpp

#include "../common/common.hpp"

/** \file
 *
 *  Low-level functions filling arrays of quadrature points and weights. */

#include "element_pair_topology.hpp"

#include "../common/armadillo_fwd.hpp"
#include <boost/tuple/tuple_comparison.hpp>
#include <ostream>

namespace Fiber
{

struct SingleQuadratureDescriptor
{
    int vertexCount;
    int order;

    bool operator<(const SingleQuadratureDescriptor& other) const {
        return std::make_pair(vertexCount, order) <
                std::make_pair(other.vertexCount, other.order);
    }

    bool operator==(const SingleQuadratureDescriptor& other) const {
        return vertexCount == other.vertexCount &&
                order == other.order;
    }

    bool operator!=(const SingleQuadratureDescriptor& other) const {
        return !operator==(other);
    }

    friend std::ostream&
    operator<< (std::ostream& dest, const SingleQuadratureDescriptor& obj)
    {
        dest << obj.vertexCount << " " << obj.order;
        return dest;
    }
};

inline size_t tbb_hasher(const SingleQuadratureDescriptor& d)
{
    return (d.vertexCount - 3) + 2 * d.order;
}

struct DoubleQuadratureDescriptor
{
    ElementPairTopology topology;
    int testOrder;
    int trialOrder;

    bool operator<(const DoubleQuadratureDescriptor& other) const {
        using boost::tuples::make_tuple;
        return make_tuple(topology, testOrder, trialOrder) <
                make_tuple(other.topology, other.testOrder, other.trialOrder);
    }

    bool operator==(const DoubleQuadratureDescriptor& other) const {
        return topology == other.topology &&
                testOrder == other.testOrder &&
                trialOrder == other.trialOrder;
    }

    bool operator!=(const DoubleQuadratureDescriptor& other) const {
        return !operator==(other);
    }

    friend std::ostream&
    operator<< (std::ostream& dest, const DoubleQuadratureDescriptor& obj)
    {
        dest << obj.topology << " " << obj.testOrder << " " << obj.trialOrder;
        return dest;
    }
};

inline size_t tbb_hasher(const DoubleQuadratureDescriptor& d)
{
    const ElementPairTopology& t = d.topology;
    return (t.testVertexCount - 3) + 2 *
            ((t.trialVertexCount - 3) + 2 *
             (t.testSharedVertex0 + 4 *
              (t.trialSharedVertex0 + 4 *
               (t.testSharedVertex1 + 4 *
                (t.trialSharedVertex1 + 4 *
                 (d.testOrder + 256 *
                  d.trialOrder))))));
}

/** \brief Retrieve points and weights for a quadrature over a single element.
 *
 *  \param[in] elementCornerCount
 *    Number of corners of the element to be integrated on.
 *  \param[in] accuracyOrder
 *    Accuracy order of the quadrature, i.e. its degree of exactness.
 *  \param[out] points
 *    Quadrature points.
 *  \param[out] weights
 *    Quadrature weights. */
template <typename ValueType>
void fillSingleQuadraturePointsAndWeights(int elementCornerCount,
                                          int accuracyOrder,
                                          arma::Mat<ValueType>& points,
                                          std::vector<ValueType>& weights);

template <typename ValueType>
void fillDoubleSingularQuadraturePointsAndWeights(
        const DoubleQuadratureDescriptor& desc,
        arma::Mat<ValueType>& testPoints,
        arma::Mat<ValueType>& trialPoints,
        std::vector<ValueType>& weights);

} // namespace Fiber

#endif


#pragma once

#include <data_model.hpp>

#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <map>
#include <sstream>
#include <boost/tuple/tuple.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/copy.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/graphviz.hpp>

namespace verilog
{
  namespace bdd {

    enum class NegP {
      Positive, Negative
    };

    enum class Type {
      Zero, One, Input
    };

    struct Node {
      std::string input_name;
      Type t;
    };

    typedef boost::adjacency_list<
      boost::setS,
      boost::vecS,
      boost::bidirectionalS,
      Node, NegP> GD;

    typedef unsigned int hashcode;

    struct BDD {
      GD graph;

      GD::vertex_descriptor
        /**
         * The nodes representing the sinks of the BDD
         */
        zero,
        one,
        /**
         * The node representing the source of the bdd
         */
        source;

      std::map<hashcode, std::vector<int> > bdd_heap;

      BDD() {
        zero = boost::add_vertex(graph);
        one  = boost::add_vertex(graph);
        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      BDD(const std::string name) {
        zero = boost::add_vertex(graph);
        one  = boost::add_vertex(graph);
        source = add_vertex(name);

        add_edge(source, one, NegP::Positive);
        add_edge(source, zero, NegP::Negative);

        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      /**
       * Creates a copy of a,
       * merges all nodes of b into the copy.
       * calls the conjunction_internal function for the copy.
       */

      void copy_graph(const BDD & o,
          std::map<GD::vertex_descriptor,
                   GD::vertex_descriptor> & node_map
          ) {

        GD::vertex_iterator v, vend;
        for(boost::tie(v, vend) = boost::vertices(o.graph);
            v != vend; ++v) {
          if (o.graph[*v].t == Type::Input) {
            GD::vertex_descriptor n = add_vertex(o.graph[*v].input_name);
            node_map[*v] = n;
          }
        }

        GD::edge_iterator e, eend;
        for(boost::tie(e, eend) = boost::edges(o.graph);
            e != eend; ++e) {
          GD::vertex_descriptor s = boost::source(*e, o.graph);

          GD::vertex_descriptor x;
          GD::vertex_descriptor d = boost::target(*e, o.graph);
          switch(o.graph[d].t) {
            case(Type::Zero):
              x = zero;
              break;
            case(Type::One):
              x = one;
              break;
            default:
              x = node_map[d];
          }
          boost::add_edge(node_map[s], x, o.graph[*e], graph);
        }
      }

      BDD operator&(const BDD & b) {

        std::map<GD::vertex_descriptor,
          GD::vertex_descriptor> node_map_a;

        std::map<GD::vertex_descriptor,
          GD::vertex_descriptor> node_map_b;

        BDD c;
        c.copy_graph(*this, node_map_a);
        c.copy_graph(b, node_map_b);

        c.source = c.conjunction_internal(
            node_map_a[this->source],
            node_map_b[b.source]
            );
        return c;
      }

      private:
      /**
       * Internal function that checks for all the cases
       * Accepts the nodes that must be conjoined
       * and returns the new source.
       */
      GD::vertex_descriptor
        conjunction_internal(
          GD::vertex_descriptor & a,
          GD::vertex_descriptor & b) {
        std::cout << "conjunction_internal " << a << " " << b << "\n";
        add_edge(a, b, NegP::Positive);
        return a;
      }

      GD::vertex_descriptor add_vertex(const std::string name) {
        GD::vertex_descriptor v = boost::add_vertex(graph);
        graph[v].input_name = name;
        graph[v].t          = Type::Input;
        return v;
      }

      void add_edge(
          GD::vertex_descriptor & a,
          GD::vertex_descriptor & b,
          NegP p) {
        boost::add_edge(a, b, p, graph);
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * - changes zero and one
       */
      public:
      void reverse() {
        GD::vertex_descriptor aux = one;
        one = zero;
        zero = aux;

        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * merges the input edges of the node b into node a.
       */
      void merge_input_edges(
          const GD::vertex_descriptor a,
          const GD::vertex_descriptor b) {
        GD::in_edge_iterator e, end;
        for (boost::tie(e, end) = in_edges(b, graph); e != end; ++e) {
            GD::vertex_descriptor s = boost::source(*e, graph);
            boost::add_edge(s, a, graph[*e], graph);
        }
      }

      std::map<int, std::vector<int> > find_layers() {
        // TODO
      }

      // Criar um hash o mais unico possivel para um bdd
      hashcode hash(int v) {
        // TODO
      }

      // Compares BDDs
      friend bool operator<(const BDD & bdd1, const BDD & bdd2) {
        // TODO
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * - minimizes the BDD
       */
      void minimize() {
        // TODO
      }

      friend bool operator==(const BDD & bdd1, const BDD & bdd2) {
        return
          (bdd1.source == bdd2.source) &&
          (bdd1.one == bdd2.one) &&
          (bdd1.zero == bdd2.zero);
        // TODO &&
        //  (bdd1.graph == bdd2.graph);
      }
      friend bool operator!=(const BDD & bdd1, const BDD & bdd2) {
        return
          (bdd1.source != bdd2.source) &&
          (bdd1.one != bdd2.one) &&
          (bdd1.zero != bdd2.zero);
        // TODO&&
        //  (bdd1.graph != bdd2.graph);
      }

      public:
      int size() {
        int i = 0;
        GD::vertex_iterator n, e;
        for(boost::tie(n, e) = boost::vertices(graph);
            n != e; ++n) ++i;
        return i;
      }

      friend std::ostream& operator<<(std::ostream &o, const BDD & b) {

        int i = 0;
        std::map<GD::vertex_descriptor, int> node_map;
        //std::map<GD::vertex_descriptor, GD::vertex_descriptor> node_map;
        GD::vertex_iterator v, vend;

        for(boost::tie(v, vend) = boost::vertices(b.graph);
            v != vend; ++v) {
          node_map[*v] = ++i;
          //node_map[*v] = *v;
        }

        o << "digraph G {\n" ;
        for(boost::tie(v, vend) = boost::vertices(b.graph);
            v != vend; ++v) {
          switch(b.graph[*v].t) {
            case(Type::Input) : {
                o << node_map[*v] << " [label=\""
                  << b.graph[*v].input_name
                  << "\", ";
                if (*v == b.source)
                  o << "shape=doublecircle];\n";
                else
                  o << "shape=circle];\n";
              break;
            }
            case(Type::One) : {
                o << node_map[*v] << " [label=\"1\", shape=box]; \n";
              break;
            }
            case(Type::Zero) : {
                o << node_map[*v] << " [label=\"0\", shape=box]; \n";
              break;
            }
          }
        }

        GD::edge_iterator e, eend;
        for(boost::tie(e, eend) = boost::edges(b.graph);
            e != eend; ++e) {
          GD::vertex_descriptor s = boost::source(*e, b.graph);
          GD::vertex_descriptor d = boost::target(*e, b.graph);

          o << node_map[s] << "->" << node_map[d];
          switch(b.graph[*e]) {
            case(NegP::Positive): {
              o << " [style=solid];\n";
              break;
            }
            case(NegP::Negative): {
              o << " [style=dashed];\n";
              break;
            }
          }
        }

        o << "}\n" ;
        return o;
      }
    };
  }
}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis                          |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford     |
|                                                                             |
|  This program is free software; you can redistribute it and/or modify       |
|  it under the terms of the GNU General Public License as published by       |
|  the Free Software Foundation; either version 2 of the License, or          |
|  (at your option) any later version.                                        |
|                                                                             |
|  This program is distributed in the hope that it will be useful,            |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of             |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              |
|  GNU General Public License for more details.                               |
|                                                                             |
|  You should have received a copy of the GNU General Public License along    |
|  with this program; if not, write to the Free Software Foundation, Inc.,    |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/format.hpp>
#include "ncl/nxsallocatematrix.h"
#include "q_matrix.hpp"
#include "xlikelihood.hpp"
using namespace phycas;

/*----------------------------------------------------------------------------------------------------------------------
|
*/
QMatrix::QMatrix()
  : dimension(0), flat_length(0)
  , qmat(0), qmat_begin(0), qmat_end(0)
  , w(0), w_begin(0), w_end(0)
  , z(0), z_begin(0), z_end(0)
  , fv(0)
  , q_dirty(true)
	{
	// Set up Q matrix representing the JC69 model by default
	rr.assign(6, 1.0);
	pi.assign(4, 0.25);
	sqrtPi.assign(4, 0.5);
	recalcQMatrix();
	}

/*----------------------------------------------------------------------------------------------------------------------
|
*/
QMatrix::~QMatrix()
	{
	clear();
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns object to its just-constructed state, with the exception that the vectors `rr', `pi' and `sqrtPi' are not
|	cleared.
*/
void QMatrix::clearAllExceptFreqsAndRates()
	{
	dimension	= 0;
	flat_length	= 0;
	qmat_begin	= 0;
	qmat_end	= 0;
	w_begin		= 0;
	w_end		= 0;
	z_begin		= 0;
	z_end		= 0;

	dim_vect.clear();

	if (qmat)
		{
		DeleteTwoDArray<double>(qmat);
		qmat = 0;
		}

	if (w)
		{
		delete [] w;
		w = 0;
		}

	if (fv)
		{
		delete [] fv;
		fv = 0;
		}

	if (z)
		{
		DeleteTwoDArray<double>(z);
		z = 0;
		}
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns object to its just-constructed state by calling clearAllExceptFreqsAndRates and then also clearing the
|	`rr', `pi' and `sqrtPi' vectors.
*/
void QMatrix::clear()
	{
	clearAllExceptFreqsAndRates();
	pi.clear();
	sqrtPi.clear();
	rr.clear();
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Accessor function that returns the current value of the data member `dimension', which is the number of rows (and
|	columns) in the Q matrix.
*/
unsigned QMatrix::getDimension()
	{
	return dimension;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Copies elements from the supplied vector `rates' to the data member `rr' and sets `q_dirty' to true.
*/
void QMatrix::setRelativeRates(const std::vector<double> & rates)
	{
	rr.resize(rates.size());
	std::copy(rates.begin(), rates.end(), rr.begin());
	q_dirty = true;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Copies elements from the supplied vector `rates' to the data member `rr' and sets `q_dirty' to true.
*/
void QMatrix::setStateFreqs(const std::vector<double> & freqs)
	{
	pi.resize(freqs.size());
	std::copy(freqs.begin(), freqs.end(), pi.begin());

	sqrtPi.resize(freqs.size());
	std::transform(pi.begin(), pi.end(), sqrtPi.begin(), boost::lambda::bind(static_cast<double(*)(double)>(&std::sqrt), boost::lambda::_1));
	q_dirty = true;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Reallocates all arrays and vectors (except `rr', `pi' and `sqrtPi') according to the value of `new_dim' and sets
|	`dimension' equal to `new_dim' before returning. Assumes `new_dim' is greater than zero. Note that `qmat', `w', `z'
|	and `fv' are allocated but not filled with any values when this function returns.
*/
void QMatrix::redimension(unsigned new_dim)
	{
	PHYCAS_ASSERT(new_dim > 0);
	clearAllExceptFreqsAndRates();

	dimension = new_dim;
	flat_length = new_dim*new_dim;

	expwv.resize(new_dim);

	// Set the shape of a NumArray object that represents Q, P (transition probs), E (eigenvectors) or V (eigenvalues)
	dim_vect.push_back((int)dimension);
	dim_vect.push_back((int)dimension);

	// Create and fill qmat with default (Mk model) values
	qmat = NewTwoDArray<double>(dimension, dimension);
	PHYCAS_ASSERT(qmat);

	qmat_begin		= &qmat[0][0];
	qmat_end		= qmat_begin + flat_length;

	// Create w (array of eigenvalues)
	w = new double[dimension];
	w_begin = &w[0];
	w_end = w_begin + dimension;

	// Create z (two-dimensional array of eigenvectors)
	z = NewTwoDArray<double>(dimension, dimension);
	z_begin		= &z[0][0];
	z_end		= z_begin + flat_length;

	// Create fv (workspace used by EigenRealSymmetric)
	fv = new double[dimension];
	}

/*----------------------------------------------------------------------------------------------------------------------
|	If `q_dirty' is true, calls recalcQMatrixImpl to recompute the `qmat', `z' and `w' data members. If `q_dirty' is
|	false, however, this function returns immediately and thus (because it is declared inline) is computationally
|	inexpensive.
*/
void QMatrix::recalcQMatrix()
	{
	if (!q_dirty)
		return;
	recalcQMatrixImpl();
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the eigenvalues in the data member `w' as a vector. Calls recalcQMatrix first to ensure that `w' is up to
|	date.
*/
std::vector<double> QMatrix::getEigenValues()
	{
	recalcQMatrix();
	std::vector<double> v(dimension, 0.0);
	std::copy(w_begin, w_end, v.begin());
	return v;
	}

/*----------------------------------------------------------------------------------------------------------------------
|
*/
std::string QMatrix::showQMatrix()
	{
	recalcQMatrix();
	return showMatrixImpl(qmat_begin);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Stores a flattened version of the supplied 2-dimensional array `twoDarr', storing the result in the supplied std::vector<double>
|	reference variable `p'. The supplied `twoDarr' should be laid out so that rows occupy contiguous memory.
*/
void QMatrix::flattenTwoDMatrix(std::vector<double> & p, double * * twoDarr, unsigned dim) const
	{
	unsigned flat_length = dim*dim;
	p.resize(flat_length);
	double * twoD_begin = &twoDarr[0][0];
	double * twoD_end   = twoD_begin + flat_length;
	std::copy(twoD_begin, twoD_end, p.begin());
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Uses `pi' and `rr' vectors to create the two-dimensional array `qmat'. The data member `qmat' is reallocated if
|	changes in `pi' and `rr' imply a new dimension (or if `qmat' is NULL). Recomputes eigenvalues and eigenvectors
|	corresponding to the new Q matrix. Assumes `q_dirty' is true, and sets `q_dirty' to false when finished. Throws an
|	exception if lengths of the `pi' and `rr' vectors are incompatible.
*/
void QMatrix::recalcQMatrixImpl()
	{
	PHYCAS_ASSERT(q_dirty);

	// pi, sqrtPi and rr should all be non-empty
	PHYCAS_ASSERT(pi.size() > 0);
	PHYCAS_ASSERT(sqrtPi.size() == pi.size());
	PHYCAS_ASSERT(rr.size() > 0);

	// First check to make sure lengths of `rr' and `pi' are compatible with each other
	unsigned dim_pi = (unsigned)pi.size();
	unsigned dim_rr = (unsigned)((1.0 + std::sqrt(1.0 + 8.0*rr.size()))/2.0); // rr.size() = (n^2 - n)/2, so use quadratic formula to get n
	if (dim_pi != dim_rr)
		{
		throw XLikelihood(boost::str(boost::format("Number of relative rates (%d) and number of state frequencies (%d) specified are incompatible") % dim_rr % dim_pi));
		}

	// If qmat is NULL or dimension differs from dim_pi and dim_rr, reallocate qmat, z, w and dv
	if (!qmat || (dimension != dim_pi))
		redimension(dim_pi);

	// This vector will hold the row sums
	std::vector<double> row_sum(dimension, 0.0);

	unsigned i, j, k = 0;
	double sum_for_scaling = 0.0;
	for (i = 0; i < dimension; ++i)
		{
		double pi_i = pi[i];
		double sqrtPi_i = sqrtPi[i];
		for (j = i + 1; j < dimension; ++j, ++k)
			{
			double rr_k = rr[k];
			double pi_j = pi[j];
			double sqrtPi_j = sqrtPi[j];

			// set value in upper triangle
			qmat[i][j] = rr_k*sqrtPi_i*sqrtPi_j;

			// set value in lower triangle
			qmat[j][i] = qmat[i][j];

			// add to relevant row sums
			row_sum[i] += rr_k*pi_j;
			row_sum[j] += rr_k*pi_i;

			// add to total expected number of substitutions
			sum_for_scaling += 2.0*pi_i*rr_k*pi_j;
			}
		qmat[i][i] = -row_sum[i];
		}

	PHYCAS_ASSERT(sum_for_scaling > 0.0);
	edgelen_scaler = 1.0/sum_for_scaling;

	// Calculate eigenvalues (w) and eigenvectors (z)
	int err_code = EigenRealSymmetric(dimension, qmat, w, z, fv);

	if (err_code != 0)
		{
		clearAllExceptFreqsAndRates();
		throw XLikelihood("Error in the calculation of eigenvectors and eigenvalues of the Q matrix");
		}

	q_dirty = false;
	}

/*----------------------------------------------------------------------------------------------------------------------
|   Recomputes a transition probability matrix for an edge length `edgelen', storing it in `pmat'. If either state
|   frequencies or relative rates have changed, the Q matrix is reconstructed and eigenvalues and eigenvectors
|   recomputed before the transition matrix is recomputed.
*/
void QMatrix::recalcPMat(
  double * * pmat,		/**< is the transition matrix to recalculate */
  double edgelen) 		/**< is the edge length */
	{
	recalcQMatrix();
    double t = edgelen;

    // The next two lines fix the "Rota" bug; see BUGS file for details
    if (t < 1.e-8)
        t = 1.e-8; //TreeNode::edgeLenEpsilon;

	// Adjust the supplied edgelen to account for the fact that the expected number of substitutions
	// implied by the Q matrix is not unity
	double v = t*edgelen_scaler;

    // Precalculate exp to avoid doing the same calculation dimension*dimension times
	for (unsigned k = 0; k < dimension; ++k)
		{
		expwv[k] = std::exp(w[k]*v);
        }

	// Exponentiate eigenvalues and put everything back together again
	// Real symmetric matrices can be diagonalized using Z*exp(D)*Z^T, where Z is the
	// orthogonal matrix of eigenvectors and D is the diagonal matrix of eigenvalues,
	// each multiplied by time (scaled to equal expected number of substitutions)
	for (unsigned i = 0; i < dimension; ++i)
		{
		double sqrtPi_i = sqrtPi[i];
		for (unsigned j = 0; j < dimension; ++j)
			{
			double factor = sqrtPi[j]/sqrtPi_i;
			double Pij = 0.0;
			for (unsigned k = 0; k < dimension; ++k)
				{
				double tmp = z[i][k]*z[j][k]*expwv[k];
				Pij +=  tmp;
				}
			pmat[i][j] = Pij*factor;
			}
		}
	}

/*----------------------------------------------------------------------------------------------------------------------
|
*/
// void QMatrix::recalcPMatrix(
//   std::vector<double> & P,	/**< */
//   double edgelen)			/**< */
// 	{
// 	recalcQMatrix();
//
//     double t = edgelen;
//
//     // The next two lines fix the "Rota" bug; see BUGS file for details
//     if (t < 1.e-8)
//         t = 1.e-8; //TreeNode::edgeLenEpsilon;
//
//     // Adjust the supplied edgelen to account for the fact that the expected number of substitutions
// 	// implied by the Q matrix is not unity
// 	double v = t*edgelen_scaler;
//
// 	P.clear();
// 	P.reserve(flat_length);
//
// 	// Exponentiate eigenvalues and put everything back together again
// 	for (unsigned i = 0; i < dimension; ++i)
// 		{
// 		double sqrtPi_i = sqrtPi[i];
// 		for (unsigned j = 0; j < dimension; ++j)
// 			{
// 			double factor = sqrtPi[j]/sqrtPi_i;
// 			double Pij = 0.0;
// 			for (unsigned k = 0; k < dimension; ++k)
// 				{
// 				double tmp = z[i][k]*z[j][k]*std::exp(w[k]*v);
// 				Pij +=  tmp;
// 				}
// 			P.push_back(Pij*factor);
// 			}
// 		}
// 	}

/*----------------------------------------------------------------------------------------------------------------------
|
*/
std::string QMatrix::showMatrixImpl(const double * q) const
	{
	unsigned i, j;

	// Output one column for row labels and a label for every column of qmat
	std::string s = str(boost::format("%12s ") % " ");
	for (i = 0; i < dimension; ++i)
		{
		s += str(boost::format("%12d ") % i);
		}
	s += "\n";

	// Output rows of q
	double * pq = (double *)q;	//PELIGROSO
	for (i = 0; i < dimension; ++i)
		{
		s += str(boost::format("%12d ") % i);
		for (j = 0; j < dimension; ++j)
			{
			s += str(boost::format("%12.5f ") % *pq++);
			}
		s += "\n";
		}

	return s;
	}

#if defined(PYTHON_ONLY)
#if defined(USING_NUMARRAY)
/*----------------------------------------------------------------------------------------------------------------------
|	Returns the entries in the data member `qmat' as a NumArray. Calls recalcQMatrix first to ensure that `qmat' is up
|	to date.
*/
boost::python::numeric::array QMatrix::getQMatrix()
	{
	recalcQMatrix();
	return num_util::makeNum(qmat_begin, dim_vect);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the eigenvectors in the data member `z' as a NumArray. Calls recalcQMatrix first to ensure that `z' is up
|	to date.
*/
boost::python::numeric::array QMatrix::getEigenVectors()
	{
	recalcQMatrix();
	return num_util::makeNum(z_begin, dim_vect);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the transition probability matrix as a vector of double values (where the 2-dimensional matrix is converted
|   to a 1-dimensional vector by storing rows one after the other. Intended for debugging (not fast).
*/
boost::python::numeric::array QMatrix::getPMatrix(double edgelen)
	{
	double * * pMat = NewTwoDArray<double>(dimension, dimension);
	recalcPMat(pMat, edgelen);
	return num_util::makeNum(pMat, dim_vect);
	}
#else
/*----------------------------------------------------------------------------------------------------------------------
|	Returns the entries in the data member `qmat' as a NumArray. Calls recalcQMatrix first to ensure that `qmat' is up
|	to date.
*/
std::vector<double> QMatrix::getQMatrix()
	{
	recalcQMatrix();
	std::vector<double> p;
	flattenTwoDMatrix(p, qmat, dimension);
	return p;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the eigenvectors in the data member `z' as a NumArray. Calls recalcQMatrix first to ensure that `z' is up
|	to date.
*/
std::vector<double> QMatrix::getEigenVectors()
	{
	recalcQMatrix();
	std::vector<double> p;
	flattenTwoDMatrix(p, z, dimension);
	return p;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the transition probability matrix as a vector of double values (where the 2-dimensional matrix is converted
|   to a 1-dimensional vector by storing rows one after the other. Intended for debugging (not fast).
*/
std::vector<double> QMatrix::getPMatrix(double edgelen)
	{
	double * * pMat = NewTwoDArray<double>(dimension, dimension);
	recalcPMat(pMat, edgelen);
	std::vector<double> p;
	flattenTwoDMatrix(p, pMat, dimension);
	DeleteTwoDArray<double>(pMat);
	return p;
	}
#endif
#endif