/****************************
 *  F=mpfr_t specialization
 ****************************/

#ifndef FPLLL_NR_FP_MPFR_H
#define FPLLL_NR_FP_MPFR_H

FPLLL_BEGIN_NAMESPACE

/* MPFR specialization */

/* constructor */
template <> inline FP_NR<mpfr_t>::FP_NR() { mpfr_init(data); }

template <> inline FP_NR<mpfr_t>::FP_NR(const FP_NR<mpfr_t> &f)
{
  mpfr_init_set(data, f.data, GMP_RNDN);
}

template <> inline FP_NR<mpfr_t>::~FP_NR() { mpfr_clear(data); }

template <> inline unsigned int FP_NR<mpfr_t>::get_prec() { return mpfr_get_default_prec(); }

template <> inline unsigned int FP_NR<mpfr_t>::set_prec(unsigned int prec)
{
  int old_prec = get_prec();
  mpfr_set_default_prec(prec);
  return old_prec;
}

/* return data */
template <> inline double FP_NR<mpfr_t>::get_d(mp_rnd_t rnd) const { return mpfr_get_d(data, rnd); }

template <> inline void FP_NR<mpfr_t>::get_mpfr(mpfr_t r, mp_rnd_t rnd) const
{
  mpfr_set(r, data, rnd);
}

template <> inline void FP_NR<mpfr_t>::set_mpfr(mpfr_t r, mp_rnd_t rnd) { mpfr_set(data, r, rnd); }

template <> inline long FP_NR<mpfr_t>::get_si() const { return mpfr_get_si(data, GMP_RNDN); }

template <> inline long FP_NR<mpfr_t>::exponent() const { return mpfr_get_exp(data); }

template <> inline long FP_NR<mpfr_t>::get_si_exp(long &expo) const
{
  if (mpfr_zero_p(data))
  {
    expo = 0;
  }
  else
  {
    expo = max(exponent() - numeric_limits<long>::digits, 0L);
  }
  mpfr_t &nc_data = const_cast<mpfr_t &>(data);
  mpfr_div_2si(nc_data, nc_data, expo, GMP_RNDN);
  long result = mpfr_get_si(nc_data, GMP_RNDZ);
  mpfr_mul_2si(nc_data, nc_data, expo, GMP_RNDN);
  return result;
}

template <> inline long FP_NR<mpfr_t>::get_si_exp_we(long &expo, long /*expo_add*/) const
{
  return get_si_exp(expo);  // NOTE: expo_add = 0
}

/*  comparison */
template <> inline int FP_NR<mpfr_t>::cmp(const FP_NR<mpfr_t> &a) const
{
  return mpfr_cmp(data, a.data);
}

template <> inline int FP_NR<mpfr_t>::cmp(double a) const { return mpfr_cmp_d(data, a); }

template <> inline int FP_NR<mpfr_t>::sgn() const { return mpfr_sgn(data); }

/*operators */
template <> inline FP_NR<mpfr_t> &FP_NR<mpfr_t>::operator=(const FP_NR<mpfr_t> &a)
{
  mpfr_set(data, a.data, GMP_RNDN);
  return *this;
}

template <> inline FP_NR<mpfr_t> &FP_NR<mpfr_t>::operator=(double a)
{
  mpfr_set_d(data, a, GMP_RNDN);
  return *this;
}

template <> inline FP_NR<mpfr_t> &FP_NR<mpfr_t>::operator=(const char *s)
{
  mpfr_set_str(data, s, 10, GMP_RNDN);
  return *this;
}

template <> inline FP_NR<mpfr_t> &FP_NR<mpfr_t>::operator=(mpfr_t &a)
{
  mpfr_set(data, a, GMP_RNDN);
  return *this;
}

template <> inline bool FP_NR<mpfr_t>::operator<=(const FP_NR<mpfr_t> &a) const
{
  return mpfr_cmp(data, a.data) <= 0;
}

template <> inline bool FP_NR<mpfr_t>::operator<=(double a) const
{
  return mpfr_cmp_d(data, a) <= 0;
}

template <> inline bool FP_NR<mpfr_t>::operator>=(const FP_NR<mpfr_t> &a) const
{
  return mpfr_cmp(data, a.data) >= 0;
}

template <> inline bool FP_NR<mpfr_t>::operator>=(double a) const
{
  return mpfr_cmp_d(data, a) >= 0;
}

template <> inline bool FP_NR<mpfr_t>::operator<(const FP_NR<mpfr_t> &a) const
{
  return mpfr_cmp(data, a.data) < 0;
}

template <> inline bool FP_NR<mpfr_t>::operator<(double a) const { return mpfr_cmp_d(data, a) < 0; }

template <> inline bool FP_NR<mpfr_t>::operator>(const FP_NR<mpfr_t> &a) const
{
  return mpfr_cmp(data, a.data) > 0;
}

template <> inline bool FP_NR<mpfr_t>::operator>(double a) const { return mpfr_cmp_d(data, a) > 0; }

template <> inline bool FP_NR<mpfr_t>::is_zero() const { return mpfr_zero_p(data); }

template <> inline int FP_NR<mpfr_t>::is_nan() const { return mpfr_nan_p(data); }

template <> inline int FP_NR<mpfr_t>::is_finite() const { return mpfr_number_p(data); }

/* arithmetic */
template <>
inline void FP_NR<mpfr_t>::add(const FP_NR<mpfr_t> &a, const FP_NR<mpfr_t> &b, mp_rnd_t rnd)
{
  mpfr_add(data, a.data, b.data, rnd);
}

template <>
inline void FP_NR<mpfr_t>::sub(const FP_NR<mpfr_t> &a, const FP_NR<mpfr_t> &b, mp_rnd_t rnd)
{
  mpfr_sub(data, a.data, b.data, rnd);
}

template <>
inline void FP_NR<mpfr_t>::mul(const FP_NR<mpfr_t> &a, const FP_NR<mpfr_t> &b, mp_rnd_t rnd)
{
  mpfr_mul(data, a.data, b.data, rnd);
}

template <> inline void FP_NR<mpfr_t>::mul_d(const FP_NR<mpfr_t> &a, double b, mp_rnd_t rnd)
{
  mpfr_mul_d(data, a.data, b, rnd);
}

template <>
inline void FP_NR<mpfr_t>::mul_mpfr(const FP_NR<mpfr_t> &a, const mpfr_t b, mp_rnd_t rnd)
{
  mpfr_mul(data, a.data, b, rnd);
}

template <> inline void FP_NR<mpfr_t>::mul_2si(const FP_NR<mpfr_t> &a, long b)
{
  mpfr_mul_2si(data, a.data, b, GMP_RNDN);
}

template <>
inline void FP_NR<mpfr_t>::div(const FP_NR<mpfr_t> &a, const FP_NR<mpfr_t> &b, mp_rnd_t rnd)
{
  mpfr_div(data, a.data, b.data, rnd);
}

template <> inline void FP_NR<mpfr_t>::div_d(const FP_NR<mpfr_t> &a, const double b, mp_rnd_t rnd)
{
  mpfr_div_d(data, a.data, b, rnd);
}

template <>
inline void FP_NR<mpfr_t>::addmul(const FP_NR<mpfr_t> &b, const FP_NR<mpfr_t> &c, mp_rnd_t rnd)
{
  mpfr_fma(data, b.data, c.data, data, rnd);
}

template <>
inline void FP_NR<mpfr_t>::submul(const FP_NR<mpfr_t> &b, const FP_NR<mpfr_t> &c, mp_rnd_t rnd)
{
  mpfr_fms(data, b.data, c.data, data, rnd);
  mpfr_neg(data, data, GMP_RNDN);  // Exact
}

template <> inline void FP_NR<mpfr_t>::pow_si(const FP_NR<mpfr_t> &a, long b, mp_rnd_t rnd)
{
  mpfr_pow_si(data, a.data, b, rnd);
}

template <> inline void FP_NR<mpfr_t>::exponential(const FP_NR<mpfr_t> &a, mp_rnd_t rnd)
{
  mpfr_exp(data, a.data, rnd);
}

template <> inline void FP_NR<mpfr_t>::log(const FP_NR<mpfr_t> &a, mp_rnd_t rnd)
{
  mpfr_log(data, a.data, rnd);
}

template <> inline void FP_NR<mpfr_t>::sqrt(const FP_NR<mpfr_t> &a, mp_rnd_t rnd)
{
  mpfr_sqrt(data, a.data, rnd);
}

template <> inline void FP_NR<mpfr_t>::root(const FP_NR<mpfr_t> &a, unsigned int k, mp_rnd_t rnd)
{
#if MPFR_VERSION_MAJOR >= 4
  mpfr_rootn_ui(data, a.data, k, rnd);
#else   // MPFR_VERSION_MAJOR >= 4
  mpfr_root(data, a.data, k, rnd);
#endif  // MPFR_VERSION_MAJOR >= 4
}

template <> inline void FP_NR<mpfr_t>::neg(const FP_NR<mpfr_t> &a)
{
  mpfr_neg(data, a.data, GMP_RNDN);
}

template <> inline void FP_NR<mpfr_t>::abs(const FP_NR<mpfr_t> &a)
{
  mpfr_abs(data, a.data, GMP_RNDN);
}
template <> inline void FP_NR<mpfr_t>::rnd(const FP_NR<mpfr_t> &a) { mpfr_round(data, a.data); }
template <> inline void FP_NR<mpfr_t>::rnd_we(const FP_NR<mpfr_t> &a, long /*expo_add*/)
{
  mpfr_round(data, a.data);  // NOTE: expo_add = 0
}
template <> inline void FP_NR<mpfr_t>::floor(const FP_NR<mpfr_t> &a) { mpfr_floor(data, a.data); }

template <> inline void FP_NR<mpfr_t>::ceil(const FP_NR<mpfr_t> &a) { mpfr_ceil(data, a.data); }

template <> inline void FP_NR<mpfr_t>::set_nan() { mpfr_set_nan(data); }

template <> inline void FP_NR<mpfr_t>::swap(FP_NR<mpfr_t> &a) { mpfr_swap(data, a.data); }

/* the hypot function for Givens rotations */
template <>
inline void FP_NR<mpfr_t>::hypot(const FP_NR<mpfr_t> &a, const FP_NR<mpfr_t> &b, mp_rnd_t rnd)
{
  mpfr_hypot(data, a.data, b.data, rnd);
}

/* operators FP_NR<mpfr_t> */
template <> inline ostream &operator<<(ostream &os, const FP_NR<mpfr_t> &x)
{
  mp_exp_t e;
  char *s = mpfr_get_str(NULL, &e, 10, os.precision(), x.get_data(), GMP_RNDN);
  char *p = s;
  if (*p == '-')
  {
    os << *p;
    p++;
  }
  if (*p == '@' || *p == 0)
    os << p;
  else if (*p == '0')
    os << *p;
  else
  {
    os << *p << '.' << p + 1;
    if (e - 1 != 0)
      os << 'e' << e - 1;
  }
  mpfr_free_str(s);
  return os;
}

FPLLL_END_NAMESPACE

#endif
