/*
    Copyright (C) 2013 Tom Bachmann

    This file is part of FLINT.

    FLINT is free software: you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License (LGPL) as published
    by the Free Software Foundation; either version 2.1 of the License, or
    (at your option) any later version.  See <http://www.gnu.org/licenses/>.
*/

#ifndef FMPQ_POLYXX_H
#define FMPQ_POLYXX_H

#include <cstdlib>
#include <string>

#include "flint.h"
#include "fmpq_poly.h"

#include "fmpqxx.h"

#include "flintxx/expression.h"
#include "flintxx/flint_classes.h"
#include "flintxx/traits.h"

// TODO exhibit this as a specialisation of a generic poly<fmpzxx>
// TODO lazy version of numref?

namespace flint {
// function "declarations"
FLINT_DEFINE_BINOP(fmpq_polyxx_get_coeff)
FLINT_DEFINE_BINOP(fmpq_polyxx_interpolate)
FLINT_DEFINE_UNOP(fmpq_polyxx_den)

// TODO move to stdmath?

// TODO move to stdmath?
FLINT_DEFINE_BINOP(rescale)

namespace detail {
template<class Poly>
struct fmpq_poly_traits
{
    typedef FLINT_UNOP_BUILD_RETTYPE(fmpq_polyxx_den, fmpzxx, Poly) coeff_ref_t;
    typedef coeff_ref_t coeff_srcref_t;

    static coeff_srcref_t den(const Poly& p) {return fmpq_polyxx_den(p);}
};
} // detail

template<class Operation, class Data>
class fmpq_polyxx_expression
    : public expression<derived_wrapper<fmpq_polyxx_expression>,
                            Operation, Data>
{
public:
    typedef expression<derived_wrapper< ::flint::fmpq_polyxx_expression>,
              Operation, Data> base_t;
    typedef detail::fmpq_poly_traits<fmpq_polyxx_expression> poly_traits_t;
    typedef typename poly_traits_t::coeff_ref_t coeff_ref_t;
    typedef typename poly_traits_t::coeff_srcref_t coeff_srcref_t;

    FLINTXX_DEFINE_BASICS(fmpq_polyxx_expression)
    FLINTXX_DEFINE_CTORS(fmpq_polyxx_expression)
    FLINTXX_DEFINE_C_REF(fmpq_polyxx_expression, fmpq_poly_struct, _poly)

    // static methods which only make sense with fmpq_polyxx
    static fmpq_polyxx_expression randtest(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpq_polyxx_expression res;
        fmpq_poly_randtest(res._poly(), state._data(), len, bits);
        return res;
    }
    static fmpq_polyxx_expression randtest_unsigned(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpq_polyxx_expression res;
        fmpq_poly_randtest_unsigned(res._poly(), state._data(), len, bits);
        return res;
    }
    static fmpq_polyxx_expression randtest_not_zero(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpq_polyxx_expression res;
        fmpq_poly_randtest_not_zero(res._poly(), state._data(), len, bits);
        return res;
    }

    // TODO make lazy
    // TODO perhaps as member function (non-static?)
    template<class Poly>
    static fmpq_polyxx_expression get_slice(const Poly& p, slong i, slong j)
    {
        fmpq_polyxx_expression res;
        fmpq_poly_get_slice(res._poly(), p.evaluate()._poly(), i, j);
        return res;
    }

    template<class Fmpq_vec1, class Fmpq_vec2>
    static FLINT_BINOP_ENABLE_RETTYPE(fmpq_polyxx_interpolate,
        Fmpq_vec1, Fmpq_vec2)
    interpolate(const Fmpq_vec1& xs, const Fmpq_vec2& ys)
    {
        return fmpq_polyxx_interpolate(xs, ys);
    }

    static fmpq_polyxx_expression zero() {return fmpq_polyxx_expression();}
    static fmpq_polyxx_expression one()
    {
        fmpq_polyxx_expression res;
        res.set_one();
        return res;
    }

    // These only make sense with immediates
    void realloc(slong alloc) {fmpq_poly_realloc(_poly(), alloc);}
    void fit_length(slong len) {fmpq_poly_fit_length(_poly(), len);}
    void _normalise() {_fmpq_poly_normalise(_poly());}
    void _set_length(slong len) {_fmpq_poly_set_length(_poly(), len);}
    void canonicalise() {fmpq_poly_canonicalise(_poly());}
    bool is_canonical() const {return fmpq_poly_is_canonical(_poly());}
    void set_zero() {fmpq_poly_zero(_poly());}
    void set_one() {fmpq_poly_one(_poly());}

    coeff_ref_t get_coeff_numref(slong n)
    {
        return coeff_ref_t::make(fmpq_poly_numref(_poly()) + n);
    }
    coeff_srcref_t get_coeff_numref(slong n) const
    {
        return coeff_srcref_t::make(fmpq_poly_numref(_poly()) + n);
    }
    coeff_ref_t den() {return poly_traits_t::den(*this);}
    coeff_srcref_t den() const {return poly_traits_t::den(*this);}

    // These only make sense with target immediates
    template<class Fmpz>
    typename mp::enable_if<traits::is_fmpzxx<Fmpz> >::type
    set_coeff(slong n, const Fmpz& x)
    {
        fmpq_poly_set_coeff_fmpz(_poly(), n, x.evaluate()._fmpz());
    }
    template<class Fmpq>
    typename mp::enable_if<traits::is_fmpqxx<Fmpq> >::type
    set_coeff(slong n, const Fmpq& x)
    {
        fmpq_poly_set_coeff_fmpq(_poly(), n, x.evaluate()._fmpq());
    }
    template<class T>
    typename mp::enable_if<traits::is_signed_integer<T> >::type
    set_coeff(slong n, T x)
    {
        fmpq_poly_set_coeff_si(_poly(), n, x);
    }
    template<class T>
    typename mp::enable_if<traits::is_unsigned_integer<T> >::type
    set_coeff(slong n, T x)
    {
        fmpq_poly_set_coeff_ui(_poly(), n, x);
    }

    void truncate(slong n) {fmpq_poly_truncate(_poly(), n);}

    // These cause evaluation
    slong length() const {return fmpq_poly_length(this->evaluate()._poly());}
    slong degree() const {return fmpq_poly_degree(this->evaluate()._poly());}
    bool is_one() const {return fmpq_poly_is_one(this->evaluate()._poly());}
    bool is_zero() const {return fmpq_poly_is_zero(this->evaluate()._poly());}
    bool is_monic() const {return fmpq_poly_is_monic(this->evaluate()._poly());}
    bool is_squarefree() const
        {return fmpq_poly_is_squarefree(this->evaluate()._poly());}

    std::string pretty(const char* x) const
    {
        char* str = fmpq_poly_get_str_pretty(this->evaluate()._poly(), x);
        std::string res(str);
        flint_free(str);
        return res;
    }

    // lazy members
    FLINTXX_DEFINE_MEMBER_BINOP_(operator(), compeval)

    template<class Slong> // NB: template to instantiate lazily
    FLINT_BINOP_ENABLE_RETTYPE(
            fmpq_polyxx_get_coeff, fmpq_polyxx_expression, Slong)
    get_coeff(const Slong& n) const
    {
        return fmpq_polyxx_get_coeff(*this, n);
    }

    FLINTXX_DEFINE_MEMBER_3OP(compose_series)
    FLINTXX_DEFINE_MEMBER_3OP(compose_series_brent_kung)
    FLINTXX_DEFINE_MEMBER_3OP(compose_series_horner)
    FLINTXX_DEFINE_MEMBER_3OP(div_series)
    FLINTXX_DEFINE_MEMBER_3OP(mullow)
    FLINTXX_DEFINE_MEMBER_BINOP(asinh_series)
    FLINTXX_DEFINE_MEMBER_BINOP(asin_series)
    FLINTXX_DEFINE_MEMBER_BINOP(atanh_series)
    FLINTXX_DEFINE_MEMBER_BINOP(atan_series)
    FLINTXX_DEFINE_MEMBER_BINOP(cosh_series)
    FLINTXX_DEFINE_MEMBER_BINOP(cos_series)
    FLINTXX_DEFINE_MEMBER_BINOP(divrem)
    FLINTXX_DEFINE_MEMBER_BINOP(exp_series)
    FLINTXX_DEFINE_MEMBER_BINOP(gcd)
    FLINTXX_DEFINE_MEMBER_BINOP(inv_series)
    FLINTXX_DEFINE_MEMBER_BINOP(inv_series_newton)
    FLINTXX_DEFINE_MEMBER_BINOP(invsqrt_series)
    FLINTXX_DEFINE_MEMBER_BINOP(lcm)
    FLINTXX_DEFINE_MEMBER_BINOP(log_series)
    FLINTXX_DEFINE_MEMBER_BINOP(pow)
    FLINTXX_DEFINE_MEMBER_BINOP_(rescale, rescale)
    FLINTXX_DEFINE_MEMBER_BINOP(resultant)
    FLINTXX_DEFINE_MEMBER_BINOP(reverse)
    FLINTXX_DEFINE_MEMBER_BINOP(revert_series)
    FLINTXX_DEFINE_MEMBER_BINOP(revert_series_lagrange)
    FLINTXX_DEFINE_MEMBER_BINOP(revert_series_lagrange_fast)
    FLINTXX_DEFINE_MEMBER_BINOP(revert_series_newton)
    FLINTXX_DEFINE_MEMBER_BINOP_(shift_left, shift_left)
    FLINTXX_DEFINE_MEMBER_BINOP_(shift_right, shift_right)
    FLINTXX_DEFINE_MEMBER_BINOP(sinh_series)
    FLINTXX_DEFINE_MEMBER_BINOP(sin_series)
    FLINTXX_DEFINE_MEMBER_BINOP(sqrt_series)
    FLINTXX_DEFINE_MEMBER_BINOP(tanh_series)
    FLINTXX_DEFINE_MEMBER_BINOP(tan_series)
    FLINTXX_DEFINE_MEMBER_BINOP(xgcd)
    FLINTXX_DEFINE_MEMBER_UNOP(derivative)
    FLINTXX_DEFINE_MEMBER_UNOP(integral)
    FLINTXX_DEFINE_MEMBER_UNOP(inv)
    FLINTXX_DEFINE_MEMBER_UNOP(make_monic)
    FLINTXX_DEFINE_MEMBER_UNOP(primitive_part)
    FLINTXX_DEFINE_MEMBER_UNOP_RTYPE(fmpqxx, content)
};

namespace detail {
struct fmpq_poly_data;
}

typedef fmpq_polyxx_expression<operations::immediate, detail::fmpq_poly_data>
           fmpq_polyxx;
typedef fmpq_polyxx_expression<operations::immediate,
            flint_classes::ref_data<fmpq_polyxx, fmpq_poly_struct> >
           fmpq_polyxx_ref;
typedef fmpq_polyxx_expression<operations::immediate,
            flint_classes::srcref_data<
                fmpq_polyxx, fmpq_polyxx_ref, fmpq_poly_struct> >
           fmpq_polyxx_srcref;

namespace detail {
template<>
struct fmpq_poly_traits<fmpq_polyxx_srcref>
{
    typedef fmpzxx_srcref coeff_ref_t;
    typedef fmpzxx_srcref coeff_srcref_t;
    template<class P> static coeff_srcref_t den(const P& p)
        {return coeff_srcref_t::make(fmpq_poly_denref(p._poly()));}
};
template<>
struct fmpq_poly_traits<fmpq_polyxx_ref>
{
    typedef fmpzxx_ref coeff_ref_t;
    typedef fmpzxx_ref coeff_srcref_t;
    template<class P>
    static coeff_ref_t den(P p)
        {return coeff_ref_t::make(fmpq_poly_denref(p._poly()));}
};
template<>
struct fmpq_poly_traits<fmpq_polyxx>
{
    typedef fmpzxx_ref coeff_ref_t;
    typedef fmpzxx_srcref coeff_srcref_t;
    template<class P>
    static coeff_ref_t den(P& p)
        {return coeff_ref_t::make(fmpq_poly_denref(p._poly()));}
    template<class P>
    static coeff_srcref_t den(const P& p)
        {return coeff_srcref_t::make(fmpq_poly_denref(p._poly()));}
};

struct fmpq_poly_data
{
    fmpq_poly_t inner;
    typedef fmpq_poly_t& data_ref_t;
    typedef const fmpq_poly_t& data_srcref_t;

    fmpq_poly_data() {fmpq_poly_init(inner);}
    ~fmpq_poly_data() {fmpq_poly_clear(inner);}

    fmpq_poly_data(const fmpq_poly_data& o)
    {
        fmpq_poly_init(inner);
        fmpq_poly_set(inner, o.inner);
    }

    fmpq_poly_data(fmpq_polyxx_srcref r)
    {
        fmpq_poly_init(inner);
        fmpq_poly_set(inner, r._poly());
    }

    fmpq_poly_data(slong alloc)
    {
        fmpq_poly_init2(inner, alloc);
    }

    fmpq_poly_data(const char* str)
    {
        fmpq_poly_init(inner);
        execution_check(!fmpq_poly_set_str(inner, str),
                "construct from string", "fmpq_polyxx");
    }
};
} // detail

namespace traits {
template<class T> struct is_fmpq_polyxx : mp::or_<
     traits::is_T_expr<T, fmpq_polyxx>,
     flint_classes::is_source<fmpq_polyxx, T> > { };
} // traits
namespace mp {
template<class T1, class T2 = void, class T3 = void, class T4 = void>
struct all_fmpq_polyxx : mp::and_<all_fmpq_polyxx<T1>, all_fmpq_polyxx<T2, T3, T4> > { };
template<class T>
struct all_fmpq_polyxx<T, void, void, void> : traits::is_fmpq_polyxx<T> { };

template<class Out, class T1, class T2 = void, class T3 = void, class T4 = void>
struct enable_all_fmpq_polyxx
    : mp::enable_if<all_fmpq_polyxx<T1, T2, T3, T4>, Out> { };
} // mp
} // flint

// here to deal with circular dependencies...
#include "fmpz_polyxx.h"

namespace flint {
namespace rules {
#define FMPQ_POLYXX_COND_S FLINTXX_COND_S(fmpq_polyxx)
#define FMPQ_POLYXX_COND_T FLINTXX_COND_T(fmpq_polyxx)

FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T, FMPQ_POLYXX_COND_S,
        fmpq_poly_set(to._poly(), from._poly()))
FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T,
        traits::is_signed_integer, fmpq_poly_set_si(to._poly(), from))
FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T,
        traits::is_unsigned_integer, fmpq_poly_set_ui(to._poly(), from))
FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T, FMPZXX_COND_S,
        fmpq_poly_set_fmpz(to._poly(), from._fmpz()))
FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T, FMPQXX_COND_S,
        fmpq_poly_set_fmpq(to._poly(), from._fmpq()))
FLINT_DEFINE_DOIT_COND2(assignment, FMPQ_POLYXX_COND_T, FMPZ_POLYXX_COND_S,
        fmpq_poly_set_fmpz_poly(to._poly(), from._poly()))

FLINTXX_DEFINE_ASSIGN_STR(fmpq_polyxx, execution_check(
            !fmpq_poly_set_str(to._poly(), from), "assign string", "fmpq_polyxx"))

FLINTXX_DEFINE_TO_STR(fmpq_polyxx, fmpq_poly_get_str(from._poly()))
FLINTXX_DEFINE_SWAP(fmpq_polyxx, fmpq_poly_swap(e1._poly(), e2._poly()))

FLINT_DEFINE_PRINT_COND(FMPQ_POLYXX_COND_S, fmpq_poly_fprint(to, from._poly()))
FLINT_DEFINE_PRINT_PRETTY_COND_2(FMPQ_POLYXX_COND_S, const char*,
        fmpq_poly_fprint_pretty(to, from._poly(), extra))
FLINT_DEFINE_READ_COND(FMPQ_POLYXX_COND_T, fmpq_poly_fread(from, to._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(reverse_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_reverse(to._poly(), e1._poly(), e2))

FLINTXX_DEFINE_CMP(fmpq_polyxx, fmpq_poly_cmp(e1._poly(), e2._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(negate, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_neg(to._poly(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(inv_op, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_inv(to._poly(), from._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpq_polyxx_get_coeff_op, fmpqxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_get_coeff_fmpq(to._fmpq(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(plus, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_add(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(minus, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_sub(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpq_poly_scalar_mul_fmpz(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQXX_COND_S,
        fmpq_poly_scalar_mul_fmpq(to._poly(), e1._poly(), e2._fmpq()))
FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::is_signed_integer,
        fmpq_poly_scalar_mul_si(to._poly(), e1._poly(), e2))
FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpq_poly_scalar_mul_ui(to._poly(), e1._poly(), e2))

FLINT_DEFINE_CBINARY_EXPR_COND2(divided_by, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQXX_COND_S,
        fmpq_poly_scalar_div_fmpq(to._poly(), e1._poly(), e2._fmpq()))
FLINT_DEFINE_CBINARY_EXPR_COND2(divided_by, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpq_poly_scalar_div_fmpz(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpq_poly_scalar_div_ui(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::is_signed_integer,
        fmpq_poly_scalar_div_si(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(times, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_mul(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(pow_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpq_poly_pow(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(shift_left_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_shift_left(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(shift_right_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_shift_right(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_div(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(modulo, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_rem(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(inv_series_newton_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_inv_series_newton(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(inv_series_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_inv_series(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(gcd_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_gcd(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(lcm_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_lcm(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(resultant_op, fmpqxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_resultant(to._fmpq(), e1._poly(), e2._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(derivative_op, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_derivative(to._poly(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(integral_op, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_integral(to._poly(), from._poly()))

#define FMPQ_POLYXX_DEFINE_SERIES(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_series_op, fmpq_polyxx, \
        FMPQ_POLYXX_COND_S, traits::fits_into_slong, \
        fmpq_poly_##name##_series(to._poly(), e1._poly(), e2))
FMPQ_POLYXX_DEFINE_SERIES(sqrt)
FMPQ_POLYXX_DEFINE_SERIES(invsqrt)
FMPQ_POLYXX_DEFINE_SERIES(exp)
FMPQ_POLYXX_DEFINE_SERIES(log)
FMPQ_POLYXX_DEFINE_SERIES(atan)
FMPQ_POLYXX_DEFINE_SERIES(atanh)
FMPQ_POLYXX_DEFINE_SERIES(asin)
FMPQ_POLYXX_DEFINE_SERIES(asinh)
FMPQ_POLYXX_DEFINE_SERIES(tan)
FMPQ_POLYXX_DEFINE_SERIES(sin)
FMPQ_POLYXX_DEFINE_SERIES(cos)
FMPQ_POLYXX_DEFINE_SERIES(sinh)
FMPQ_POLYXX_DEFINE_SERIES(cosh)
FMPQ_POLYXX_DEFINE_SERIES(tanh)

FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_op, fmpqxx,
        FMPQ_POLYXX_COND_S, FMPQXX_COND_S,
        fmpq_poly_evaluate_fmpq(to._fmpq(), e1._poly(), e2._fmpq()))
FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_op, fmpqxx,
        FMPQ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpq_poly_evaluate_fmpz(to._fmpq(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(compose_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
        fmpq_poly_compose(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpq_polyxx_interpolate_op, fmpq_polyxx,
        FMPZ_VECXX_COND_S, FMPZ_VECXX_COND_S,
        fmpq_poly_interpolate_fmpz_vec(to._poly(), e1._data().array,
            e2._data().array, e2.size()))

FLINT_DEFINE_BINARY_EXPR_COND2(rescale_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, FMPQXX_COND_S,
        fmpq_poly_rescale(to._poly(), e1._poly(), e2._fmpq()))

FLINT_DEFINE_BINARY_EXPR_COND2(revert_series_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_revert_series(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(revert_series_lagrange_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_revert_series_lagrange(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(revert_series_lagrange_fast_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_revert_series_lagrange_fast(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(revert_series_newton_op, fmpq_polyxx,
        FMPQ_POLYXX_COND_S, traits::fits_into_slong,
        fmpq_poly_revert_series_newton(to._poly(), e1._poly(), e2))

FLINT_DEFINE_UNARY_EXPR_COND(content_op, fmpqxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_content(to._fmpq(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(primitive_part_op, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_primitive_part(to._poly(), from._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(make_monic_op, fmpq_polyxx, FMPQ_POLYXX_COND_S,
        fmpq_poly_make_monic(to._poly(), from._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(fmpq_polyxx_den_op, fmpzxx, FMPQ_POLYXX_COND_S,
        fmpz_set(to._fmpz(), fmpq_poly_denref(from._poly())))

namespace rdetail {
typedef make_ltuple<mp::make_tuple<
    fmpq_polyxx, fmpq_polyxx, fmpq_polyxx>::type>::type fmpq_polyxx_triple;
typedef make_ltuple<mp::make_tuple<
    fmpq_polyxx, fmpq_polyxx>::type>::type fmpq_polyxx_pair;
} // rdetail

FLINT_DEFINE_BINARY_EXPR_COND2(xgcd_op, rdetail::fmpq_polyxx_triple,
    FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
    fmpq_poly_xgcd(to.template get<0>()._poly(), to.template get<1>()._poly(),
        to.template get<2>()._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(divrem_op, rdetail::fmpq_polyxx_pair,
    FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S,
    fmpq_poly_divrem(to.template get<0>()._poly(), to.template get<1>()._poly(),
        e1._poly(), e2._poly()))

#define FMPQ_POLYXX_DEFINE_SERIES_FUNC(name) \
FLINT_DEFINE_THREEARY_EXPR_COND3(name##_op, fmpq_polyxx, \
    FMPQ_POLYXX_COND_S, FMPQ_POLYXX_COND_S, traits::fits_into_slong, \
    fmpq_poly_##name(to._poly(), e1._poly(), e2._poly(), e3))
FMPQ_POLYXX_DEFINE_SERIES_FUNC(mullow)
FMPQ_POLYXX_DEFINE_SERIES_FUNC(div_series)
FMPQ_POLYXX_DEFINE_SERIES_FUNC(compose_series)
FMPQ_POLYXX_DEFINE_SERIES_FUNC(compose_series_brent_kung)
FMPQ_POLYXX_DEFINE_SERIES_FUNC(compose_series_horner)
} // rules

// NB: fmpq_poly addmul is just done by hand currently, no need to wrap that ..

} // flint

#endif
