/*
    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 <https://www.gnu.org/licenses/>.
*/

#ifndef FMPZ_POLYXX_H
#define FMPZ_POLYXX_H

#include <cstdlib>
#include <string>

#include "flint.h"
#include "fmpz_poly.h"

#include "fmpzxx.h"
#include "fmpz_vecxx.h"

#include "flintxx/expression.h"
#include "flintxx/flint_classes.h"
#include "flintxx/flint_exception.h"
#include "flintxx/frandxx.h"
#include "flintxx/ltuple.h"
#include "flintxx/traits.h"
#include "flintxx/traits_fwd.h"

// TODO exhibit this as a specialisation of a generic poly<fmpzxx>
// TODO newton basis?
// TODO power series class?
// TODO input
// TODO addmul
// TODO more hensel lifting?

namespace flint {
// function "declarations"
FLINT_DEFINE_UNOP(sqr_karatsuba)
FLINT_DEFINE_UNOP(sqrt_classical)

FLINT_DEFINE_BINOP(evaluate_divconquer)
FLINT_DEFINE_BINOP(evaluate_horner)
FLINT_DEFINE_BINOP(gcd_heuristic)
FLINT_DEFINE_BINOP(gcd_modular)
FLINT_DEFINE_BINOP(gcd_subresultant)
FLINT_DEFINE_BINOP(mul_karatsuba)
FLINT_DEFINE_BINOP(mulmid_classical)
FLINT_DEFINE_BINOP(mul_SS)
FLINT_DEFINE_BINOP(divides)
FLINT_DEFINE_BINOP(pow_addchains)
FLINT_DEFINE_BINOP(pow_binomial)
FLINT_DEFINE_BINOP(pow_multinomial)
FLINT_DEFINE_BINOP(pseudo_div)
FLINT_DEFINE_BINOP(pseudo_divrem)
FLINT_DEFINE_BINOP(pseudo_divrem_basecase)
FLINT_DEFINE_BINOP(pseudo_divrem_cohen)
FLINT_DEFINE_BINOP(pseudo_divrem_divconquer)
FLINT_DEFINE_BINOP(pseudo_rem)
FLINT_DEFINE_BINOP(pseudo_rem_cohen)
FLINT_DEFINE_BINOP(sqrlow)
FLINT_DEFINE_BINOP(sqrlow_classical)
FLINT_DEFINE_BINOP(sqrlow_karatsuba_n)
FLINT_DEFINE_BINOP(sqrlow_KS)
FLINT_DEFINE_BINOP(taylor_shift_divconquer)
FLINT_DEFINE_BINOP(xgcd_modular)

FLINT_DEFINE_THREEARY(mulhigh_karatsuba_n)
FLINT_DEFINE_THREEARY(mulhigh_n)
FLINT_DEFINE_THREEARY(mullow_karatsuba_n)
FLINT_DEFINE_THREEARY(mullow_SS)


FLINT_DEFINE_BINOP(fmpz_polyxx_interpolate)
FLINT_DEFINE_UNOP(fmpz_polyxx_product_roots)
FLINT_DEFINE_UNOP(fmpz_polyxx_lead)
FLINT_DEFINE_BINOP(fmpz_polyxx_get_coeff)
FLINT_DEFINE_BINOP(fmpz_polyxx_bit_unpack)
FLINT_DEFINE_BINOP(fmpz_polyxx_bit_unpack_unsigned)

FLINT_DEFINE_SEVENARY(hensel_lift)
FLINT_DEFINE_SEVENARY(hensel_lift_without_inverse)
FLINT_DEFINE_SIXARY(hensel_lift_only_inverse)


namespace detail {
template<class Poly>
struct fmpz_poly_traits
{
    typedef FLINT_UNOP_BUILD_RETTYPE(
                fmpz_polyxx_lead, fmpzxx, Poly) lead_ref_t;
    typedef lead_ref_t lead_srcref_t;
    static lead_ref_t lead(const Poly& p) {return fmpz_polyxx_lead(p);}

    template<class T>
    struct coeff
    {
        typedef FLINT_BINOP_ENABLE_RETTYPE(fmpz_polyxx_get_coeff, Poly, T) ref_t;
        typedef ref_t srcref_t;
        static ref_t get(const Poly& p, const T& t)
            {return fmpz_polyxx_get_coeff(p, t);}
    };
};
}

template<class Operation, class Data>
class fmpz_polyxx_expression
    : public expression<derived_wrapper<fmpz_polyxx_expression>,
                            Operation, Data>
{
public:
    typedef expression<derived_wrapper< ::flint::fmpz_polyxx_expression>,
              Operation, Data> base_t;
    typedef detail::fmpz_poly_traits<fmpz_polyxx_expression> poly_traits_t;

    FLINTXX_DEFINE_BASICS(fmpz_polyxx_expression)
    FLINTXX_DEFINE_CTORS(fmpz_polyxx_expression)
    FLINTXX_DEFINE_C_REF(fmpz_polyxx_expression, fmpz_poly_struct, _poly)

    // static methods which only make sense with fmpz_polyxx
    static fmpz_polyxx_expression randtest(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpz_polyxx_expression res;
        fmpz_poly_randtest(res._poly(), state._data(), len, bits);
        return res;
    }
    static fmpz_polyxx_expression randtest_unsigned(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpz_polyxx_expression res;
        fmpz_poly_randtest_unsigned(res._poly(), state._data(), len, bits);
        return res;
    }
    static fmpz_polyxx_expression randtest_not_zero(frandxx& state, slong len,
            flint_bitcnt_t bits)
    {
        fmpz_polyxx_expression res;
        fmpz_poly_randtest_not_zero(res._poly(), state._data(), len, bits);
        return res;
    }

    template<class Fmpz_vec1, class Fmpz_vec2>
    static FLINT_BINOP_ENABLE_RETTYPE(fmpz_polyxx_interpolate,
        Fmpz_vec1, Fmpz_vec2)
    interpolate(const Fmpz_vec1& xs, const Fmpz_vec2& ys)
    {
        return fmpz_polyxx_interpolate(xs, ys);
    }

    template<class Fmpz_vec>
    static FLINT_UNOP_ENABLE_RETTYPE(fmpz_polyxx_product_roots, Fmpz_vec)
    product_roots(const Fmpz_vec& xs)
    {
        return fmpz_polyxx_product_roots(xs);
    }

    template<class Arg1, class Arg2>
    static FLINT_BINOP_ENABLE_RETTYPE(fmpz_polyxx_bit_unpack,
        Arg1, Arg2)
    bit_unpack(const Arg1& a1, const Arg2& a2)
    {
        return fmpz_polyxx_bit_unpack(a1, a2);
    }
    template<class Arg1, class Arg2>
    static FLINT_BINOP_ENABLE_RETTYPE(fmpz_polyxx_bit_unpack_unsigned,
        Arg1, Arg2)
    bit_unpack_unsigned(const Arg1& a1, const Arg2& a2)
    {
        return fmpz_polyxx_bit_unpack_unsigned(a1, a2);
    }

    template<class T>
    static fmpz_polyxx_expression from_ground(const T& t)
    {
        fmpz_polyxx_expression res;
        fmpzxx u; u = t;
        res.set_coeff(0, u);
        return res;
    }

    template<class Nmod_poly>
    static fmpz_polyxx_expression _lift(const Nmod_poly& poly)
    {
        fmpz_polyxx_expression res(poly.length());
        fmpz_poly_set_nmod_poly(res._poly(), poly._poly());
        return res;
    }
    template<class Nmod_poly>
    static fmpz_polyxx_expression lift(const Nmod_poly& poly,
            typename mp::enable_if<traits::is_nmod_polyxx<Nmod_poly> >::type* = 0)
    {
        return _lift(poly.evaluate());
    }
    template<class Nmod_poly>
    static fmpz_polyxx_expression _lift_unsigned(const Nmod_poly& poly)
    {
        fmpz_polyxx_expression res(poly.length());
        fmpz_poly_set_nmod_poly_unsigned(res._poly(), poly.evaluate()._poly());
        return res;
    }
    template<class Nmod_poly>
    static fmpz_polyxx_expression lift_unsigned(const Nmod_poly& poly,
            typename mp::enable_if<traits::is_nmod_polyxx<Nmod_poly> >::type* = 0)
    {
        return _lift_unsigned(poly.evaluate());
    }

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

    // These only make sense with immediates
    void realloc(slong alloc) {fmpz_poly_realloc(_poly(), alloc);}
    void fit_length(slong len) {fmpz_poly_fit_length(_poly(), len);}
    void _normalise() {_fmpz_poly_normalise(_poly());}
    void _set_length(slong len) {_fmpz_poly_set_length(_poly(), len);}
    void zero_coeffs(slong i, slong j) {fmpz_poly_zero_coeffs(_poly(), i, j);}
    void set_zero() {fmpz_poly_zero(_poly());}
    void set_one() {fmpz_poly_one(_poly());}

    // The result of these are undefined if n is >= length
    // You also may have to call _normalise().
    template<class T>
    typename poly_traits_t::template coeff<T>::ref_t coeff(const T& n)
    {
        return poly_traits_t::template coeff<T>::get(*this, n);
    }
    template<class T>
    typename poly_traits_t::template coeff<T>::srcref_t
        coeff(const T& n) const
    {
        return poly_traits_t::template coeff<T>::get(*this, n);
    }
    typename poly_traits_t::lead_ref_t lead()
    {
        return poly_traits_t::lead(*this);
    }
    typename poly_traits_t::lead_srcref_t lead() const
    {
        return poly_traits_t::lead(*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)
    {
        fmpz_poly_set_coeff_fmpz(_poly(), n, x.evaluate()._fmpz());
    }
    template<class T>
    typename mp::enable_if<traits::is_signed_integer<T> >::type
    set_coeff(slong n, T x)
    {
        fmpz_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)
    {
        fmpz_poly_set_coeff_ui(_poly(), n, x);
    }

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

    // These cause evaluation
    slong length() const {return fmpz_poly_length(this->evaluate()._poly());}
    slong degree() const {return fmpz_poly_degree(this->evaluate()._poly());}
    bool is_one() const {return fmpz_poly_is_one(this->evaluate()._poly());}
    bool is_zero() const {return fmpz_poly_is_zero(this->evaluate()._poly());}
    bool is_unit() const {return fmpz_poly_is_unit(this->evaluate()._poly());}
    ulong max_limbs() const {return fmpz_poly_max_limbs(this->evaluate()._poly());}
    slong max_bits() const {return fmpz_poly_max_bits(this->evaluate()._poly());}

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

    void signature(slong& r1, slong& r2) const
    {
        fmpz_poly_signature(&r1, &r2, this->evaluate()._poly());
    }

    int read_pretty(char** x, FILE* f = stdin)
    {
        return fmpz_poly_fread_pretty(f, _poly(), x);
    }

    // lazy member forwarding
    FLINTXX_DEFINE_MEMBER_BINOP_(operator(), compeval)
    FLINTXX_DEFINE_MEMBER_BINOP_(get_coeff, fmpz_polyxx_get_coeff)

    FLINTXX_DEFINE_MEMBER_BINOP(bit_pack)
    FLINTXX_DEFINE_MEMBER_BINOP(compose_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(compose_horner)
    FLINTXX_DEFINE_MEMBER_BINOP(div_basecase)
    FLINTXX_DEFINE_MEMBER_BINOP(div_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(divexact)
    FLINTXX_DEFINE_MEMBER_BINOP(divides)
    FLINTXX_DEFINE_MEMBER_BINOP(divrem)
    FLINTXX_DEFINE_MEMBER_BINOP(divrem_basecase)
    FLINTXX_DEFINE_MEMBER_BINOP(divrem_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(div_root)
    FLINTXX_DEFINE_MEMBER_BINOP(evaluate_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(evaluate_horner)
    FLINTXX_DEFINE_MEMBER_BINOP(fdiv_2exp)
    FLINTXX_DEFINE_MEMBER_BINOP(gcd)
    FLINTXX_DEFINE_MEMBER_BINOP(gcd_heuristic)
    FLINTXX_DEFINE_MEMBER_BINOP(gcd_modular)
    FLINTXX_DEFINE_MEMBER_BINOP(gcd_subresultant)
    FLINTXX_DEFINE_MEMBER_BINOP(inv_series)
    FLINTXX_DEFINE_MEMBER_BINOP(inv_series_newton)
    FLINTXX_DEFINE_MEMBER_BINOP(lcm)
    FLINTXX_DEFINE_MEMBER_BINOP(mul_2exp)
    FLINTXX_DEFINE_MEMBER_BINOP(mul_classical)
    FLINTXX_DEFINE_MEMBER_BINOP(mul_karatsuba)
    FLINTXX_DEFINE_MEMBER_BINOP(mul_KS)
    FLINTXX_DEFINE_MEMBER_BINOP(mulmid_classical)
    FLINTXX_DEFINE_MEMBER_BINOP(mul_SS)
    FLINTXX_DEFINE_MEMBER_BINOP(shift_left)
    FLINTXX_DEFINE_MEMBER_BINOP(shift_right)
    FLINTXX_DEFINE_MEMBER_BINOP(pow)
    FLINTXX_DEFINE_MEMBER_BINOP(pow_addchains)
    FLINTXX_DEFINE_MEMBER_BINOP(pow_binexp)
    FLINTXX_DEFINE_MEMBER_BINOP(pow_binomial)
    FLINTXX_DEFINE_MEMBER_BINOP(pow_multinomial)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_div)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_divrem)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_divrem_basecase)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_divrem_cohen)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_divrem_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_rem)
    FLINTXX_DEFINE_MEMBER_BINOP(pseudo_rem_cohen)
    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(smod)
    FLINTXX_DEFINE_MEMBER_BINOP(sqrlow)
    FLINTXX_DEFINE_MEMBER_BINOP(sqrlow_classical)
    FLINTXX_DEFINE_MEMBER_BINOP(sqrlow_karatsuba_n)
    FLINTXX_DEFINE_MEMBER_BINOP(sqrlow_KS)
    FLINTXX_DEFINE_MEMBER_BINOP(taylor_shift)
    FLINTXX_DEFINE_MEMBER_BINOP(taylor_shift_divconquer)
    FLINTXX_DEFINE_MEMBER_BINOP(taylor_shift_horner)
    FLINTXX_DEFINE_MEMBER_BINOP(tdiv)
    FLINTXX_DEFINE_MEMBER_BINOP(tdiv_2exp)
    FLINTXX_DEFINE_MEMBER_BINOP(xgcd)
    FLINTXX_DEFINE_MEMBER_BINOP(xgcd_modular)

    FLINTXX_DEFINE_MEMBER_UNOP(derivative)
    FLINTXX_DEFINE_MEMBER_UNOP(primitive_part)
    FLINTXX_DEFINE_MEMBER_UNOP(sqr)
    FLINTXX_DEFINE_MEMBER_UNOP(sqr_classical)
    FLINTXX_DEFINE_MEMBER_UNOP(sqr_karatsuba)
    FLINTXX_DEFINE_MEMBER_UNOP(sqr_KS)
    FLINTXX_DEFINE_MEMBER_UNOP(sqrt)
    FLINTXX_DEFINE_MEMBER_UNOP(sqrt_classical)

    FLINTXX_DEFINE_MEMBER_UNOP_RTYPE(fmpzxx, bound_roots)
    FLINTXX_DEFINE_MEMBER_UNOP_RTYPE(fmpzxx, twonorm)

    FLINTXX_DEFINE_MEMBER_UNOP_RTYPE(fmpzxx, content)
    FLINTXX_DEFINE_MEMBER_UNOP_RTYPE(fmpzxx, height)

    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(mulhigh_classical)
    FLINTXX_DEFINE_MEMBER_3OP(mulhigh_karatsuba_n)
    FLINTXX_DEFINE_MEMBER_3OP(mulhigh_n)
    FLINTXX_DEFINE_MEMBER_3OP(mullow)
    FLINTXX_DEFINE_MEMBER_3OP(mullow_classical)
    FLINTXX_DEFINE_MEMBER_3OP(mullow_karatsuba_n)
    FLINTXX_DEFINE_MEMBER_3OP(mullow_KS)
    FLINTXX_DEFINE_MEMBER_3OP(mullow_SS)
    FLINTXX_DEFINE_MEMBER_3OP(pow_trunc)

    FLINTXX_DEFINE_MEMBER_4OP(CRT)
};

namespace detail {
struct fmpz_poly_data;
}

typedef fmpz_polyxx_expression<operations::immediate, detail::fmpz_poly_data>
           fmpz_polyxx;
typedef fmpz_polyxx_expression<operations::immediate,
            flint_classes::ref_data<fmpz_polyxx, fmpz_poly_struct> >
           fmpz_polyxx_ref;
typedef fmpz_polyxx_expression<operations::immediate,
            flint_classes::srcref_data<
                fmpz_polyxx, fmpz_polyxx_ref, fmpz_poly_struct> >
           fmpz_polyxx_srcref;

namespace detail {
template<>
struct fmpz_poly_traits<fmpz_polyxx_srcref>
{
    typedef fmpzxx_srcref lead_srcref_t;
    typedef fmpzxx_srcref lead_ref_t;

    template<class P>
    static lead_srcref_t lead(const P& p)
        {return lead_srcref_t::make(fmpz_poly_lead(p._poly()));}

    template<class T>
    struct coeff
    {
        typedef typename mp::enable_if<
            traits::is_integer<T>, fmpzxx_srcref>::type ref_t;
        typedef ref_t srcref_t;

        template<class P>
        static srcref_t get(const P& p, const T& n)
            {return srcref_t::make(fmpz_poly_coeff_ptr(p._poly(), n));}
    };
};
template<>
struct fmpz_poly_traits<fmpz_polyxx_ref>
{
    typedef fmpzxx_ref lead_ref_t;
    typedef fmpzxx_ref lead_srcref_t;

    template<class P>
    static lead_ref_t lead(P p)
        {return lead_ref_t::make(fmpz_poly_lead(p._poly()));}

    template<class T>
    struct coeff
    {
        typedef fmpzxx_ref ref_t;
        typedef fmpzxx_ref srcref_t;

        template<class P>
        static ref_t get(P p, const T& n)
            {return ref_t::make(fmpz_poly_get_coeff_ptr(p._poly(), n));}
    };
};
template<>
struct fmpz_poly_traits<fmpz_polyxx>
{
    typedef fmpzxx_ref lead_ref_t;
    typedef fmpzxx_srcref lead_srcref_t;

    template<class P>
    static lead_ref_t lead(P& p)
        {return lead_ref_t::make(fmpz_poly_lead(p._poly()));}
    template<class P>
    static lead_srcref_t lead(const P& p)
        {return lead_srcref_t::make(fmpz_poly_lead(p._poly()));}

    template<class T>
    struct coeff
    {
        typedef fmpzxx_ref ref_t;
        typedef fmpzxx_srcref srcref_t;

        template<class P>
        static ref_t get(P& p, const T& n)
            {return ref_t::make(fmpz_poly_get_coeff_ptr(p._poly(), n));}
        template<class P>
        static srcref_t get(const P& p, const T& n)
            {return srcref_t::make(fmpz_poly_get_coeff_ptr(p._poly(), n));}
    };
};

struct fmpz_poly_data
{
    fmpz_poly_t inner;
    typedef fmpz_poly_t& data_ref_t;
    typedef const fmpz_poly_t& data_srcref_t;

    fmpz_poly_data() {fmpz_poly_init(inner);}
    ~fmpz_poly_data() {fmpz_poly_clear(inner);}

    fmpz_poly_data(const fmpz_poly_data& o)
    {
        fmpz_poly_init(inner);
        fmpz_poly_set(inner, o.inner);
    }

    fmpz_poly_data(fmpz_polyxx_srcref r)
    {
        fmpz_poly_init(inner);
        fmpz_poly_set(inner, r._poly());
    }

    fmpz_poly_data(slong alloc)
    {
        fmpz_poly_init2(inner, alloc);
    }

    fmpz_poly_data(const char* str)
    {
        fmpz_poly_init(inner);
        execution_check(!fmpz_poly_set_str(inner, str),
                "construct from string", "fmpz_polyxx");
    }
};
} // detail

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

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

namespace rules {
#define FMPZ_POLYXX_COND_S FLINTXX_COND_S(fmpz_polyxx)
#define FMPZ_POLYXX_COND_T FLINTXX_COND_T(fmpz_polyxx)

FLINTXX_DEFINE_EQUALS(fmpz_polyxx, fmpz_poly_equal(e1._poly(), e2._poly()))

FLINT_DEFINE_DOIT_COND2(assignment, FMPZ_POLYXX_COND_T, FMPZ_POLYXX_COND_S,
        fmpz_poly_set(to._poly(), from._poly()))
FLINT_DEFINE_DOIT_COND2(assignment, FMPZ_POLYXX_COND_T,
        traits::is_signed_integer,
        fmpz_poly_set_si(to._poly(), from))
FLINT_DEFINE_DOIT_COND2(assignment, FMPZ_POLYXX_COND_T,
        traits::is_unsigned_integer,
        fmpz_poly_set_ui(to._poly(), from))
FLINT_DEFINE_DOIT_COND2(assignment, FMPZ_POLYXX_COND_T, FMPZXX_COND_S,
        fmpz_poly_set_fmpz(to._poly(), from._fmpz()))
FLINTXX_DEFINE_ASSIGN_STR(fmpz_polyxx, execution_check(
            !fmpz_poly_set_str(to._poly(), from), "assign string", "fmpz_polyxx"))

FLINT_DEFINE_PRINT_COND(FMPZ_POLYXX_COND_S, fmpz_poly_fprint(to, from._poly()))
FLINT_DEFINE_PRINT_PRETTY_COND_2(FMPZ_POLYXX_COND_S, const char*,
        fmpz_poly_fprint_pretty(to, from._poly(), extra))
FLINT_DEFINE_READ_COND(FMPZ_POLYXX_COND_T, fmpz_poly_fread(from, to._poly()))

FLINTXX_DEFINE_TO_STR(fmpz_polyxx, fmpz_poly_get_str(from._poly()))
FLINTXX_DEFINE_SWAP(fmpz_polyxx, fmpz_poly_swap(e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(reverse_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_slong,
        fmpz_poly_reverse(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(plus, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_add(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(minus, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_sub(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(negate, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_neg(to._poly(), from._poly()))

FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_scalar_mul_fmpz(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_signed_integer,
        fmpz_poly_scalar_mul_si(to._poly(), e1._poly(), e2))
FLINT_DEFINE_CBINARY_EXPR_COND2(times, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpz_poly_scalar_mul_ui(to._poly(), e1._poly(), e2))

FLINT_DEFINE_CBINARY_EXPR_COND2(mul_2exp_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpz_poly_scalar_mul_2exp(to._poly(), e1._poly(), e2))

FLINT_DEFINE_CBINARY_EXPR_COND2(divided_by, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_scalar_fdiv_fmpz(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpz_poly_scalar_fdiv_ui(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_signed_integer,
        fmpz_poly_scalar_fdiv_si(to._poly(), e1._poly(), e2))

FLINT_DEFINE_CBINARY_EXPR_COND2(fdiv_2exp_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpz_poly_scalar_fdiv_2exp(to._poly(), e1._poly(), e2))

#define FMPZ_POLYXX_DEFINE_SCALAR_DIVFUNCS(name) \
FLINT_DEFINE_CBINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S, \
        fmpz_poly_scalar_##name##_fmpz(to._poly(), e1._poly(), e2._fmpz())) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer, \
        fmpz_poly_scalar_##name##_ui(to._poly(), e1._poly(), e2)) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, traits::is_signed_integer, \
        fmpz_poly_scalar_##name##_si(to._poly(), e1._poly(), e2))
FMPZ_POLYXX_DEFINE_SCALAR_DIVFUNCS(tdiv)
FMPZ_POLYXX_DEFINE_SCALAR_DIVFUNCS(divexact)

FLINT_DEFINE_CBINARY_EXPR_COND2(tdiv_2exp_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer,
        fmpz_poly_scalar_tdiv_2exp(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(modulo, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_scalar_mod_fmpz(to._poly(), e1._poly(), e2._fmpz()))

FLINT_DEFINE_BINARY_EXPR_COND2(smod_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_scalar_smod_fmpz(to._poly(), e1._poly(), e2._fmpz()))

FLINT_DEFINE_BINARY_EXPR_COND2(bit_pack_op, fmpzxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_flint_bitcnt_t,
        fmpz_poly_bit_pack(to._fmpz(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpz_polyxx_bit_unpack_op, fmpz_polyxx,
        FMPZXX_COND_S, traits::fits_into_flint_bitcnt_t,
        fmpz_poly_bit_unpack(to._poly(), e1._fmpz(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpz_polyxx_bit_unpack_unsigned_op, fmpz_polyxx,
        FMPZXX_COND_S, traits::fits_into_flint_bitcnt_t,
        fmpz_poly_bit_unpack_unsigned(to._poly(), e1._fmpz(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(times, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_mul(to._poly(), e1._poly(), e2._poly()))

#define FMPZ_POLYXX_DEFINE_MUL(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, \
        fmpz_poly_##name(to._poly(), e1._poly(), e2._poly()))
FMPZ_POLYXX_DEFINE_MUL(mul_classical)
FMPZ_POLYXX_DEFINE_MUL(mulmid_classical)
FMPZ_POLYXX_DEFINE_MUL(mul_karatsuba)
FMPZ_POLYXX_DEFINE_MUL(mul_SS)
FMPZ_POLYXX_DEFINE_MUL(mul_KS)

FLINT_DEFINE_UNARY_EXPR_COND(sqr_KS_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_sqr_KS(to._poly(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(sqr_karatsuba_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_sqr_karatsuba(to._poly(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(sqr_classical_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_sqr_classical(to._poly(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(sqr_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_sqr(to._poly(), from._poly()))

#define FMPZ_POLYXX_DEFINE_SQRLOW(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, traits::fits_into_slong, \
        fmpz_poly_##name(to._poly(), e1._poly(), e2))
FMPZ_POLYXX_DEFINE_SQRLOW(sqrlow_KS)
FMPZ_POLYXX_DEFINE_SQRLOW(sqrlow_karatsuba_n)
FMPZ_POLYXX_DEFINE_SQRLOW(sqrlow_classical)
FMPZ_POLYXX_DEFINE_SQRLOW(sqrlow)

#define FMPZ_POLYXX_DEFINE_POW(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, fmpz_polyxx, \
        FMPZ_POLYXX_COND_S, traits::is_unsigned_integer, \
        fmpz_poly_##name(to._poly(), e1._poly(), e2))
FMPZ_POLYXX_DEFINE_POW(pow_multinomial)
FMPZ_POLYXX_DEFINE_POW(pow_binomial)
FMPZ_POLYXX_DEFINE_POW(pow_addchains)
FMPZ_POLYXX_DEFINE_POW(pow_binexp)
FMPZ_POLYXX_DEFINE_POW(pow)

FLINT_DEFINE_BINARY_EXPR_COND2(shift_left_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_slong,
        fmpz_poly_shift_left(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(shift_right_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_slong,
        fmpz_poly_shift_right(to._poly(), e1._poly(), e2))

FLINT_DEFINE_UNARY_EXPR_COND(height_op, fmpzxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_height(to._fmpz(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(twonorm_op, fmpzxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_2norm(to._fmpz(), from._poly()))

FMPZ_POLYXX_DEFINE_MUL(gcd)
FMPZ_POLYXX_DEFINE_MUL(gcd_subresultant)
FMPZ_POLYXX_DEFINE_MUL(gcd_heuristic)
FMPZ_POLYXX_DEFINE_MUL(gcd_modular)
FMPZ_POLYXX_DEFINE_MUL(lcm)

FLINT_DEFINE_BINARY_EXPR_COND2(resultant_op, fmpzxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_resultant(to._fmpz(), e1._poly(), e2._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(content_op, fmpzxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_content(to._fmpz(), from._poly()))
FLINT_DEFINE_UNARY_EXPR_COND(primitive_part_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_primitive_part(to._poly(), from._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(div_basecase_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_div_basecase(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(div_divconquer_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_div_divconquer(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(divided_by, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_div(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(rem_basecase_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_rem_basecase(to._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(modulo, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_rem(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(div_root_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_div_root(to._poly(), e1._poly(), e2._fmpz()))

FLINT_DEFINE_BINARY_EXPR_COND2(inv_series_newton_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_slong,
        fmpz_poly_inv_series_newton(to._poly(), e1._poly(), e2))
FLINT_DEFINE_BINARY_EXPR_COND2(inv_series_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, traits::fits_into_slong,
        fmpz_poly_inv_series(to._poly(), e1._poly(), e2))

FLINT_DEFINE_BINARY_EXPR_COND2(pseudo_rem_cohen_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
        fmpz_poly_pseudo_rem_cohen(to._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_UNARY_EXPR_COND(derivative_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_derivative(to._poly(), from._poly()))

FMPZ_POLYXX_DEFINE_MUL(compose)
FMPZ_POLYXX_DEFINE_MUL(compose_horner)
FMPZ_POLYXX_DEFINE_MUL(compose_divconquer)

FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_op, fmpzxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_evaluate_fmpz(to._fmpz(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_divconquer_op, fmpzxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_evaluate_divconquer_fmpz(to._fmpz(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_horner_op, fmpzxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_evaluate_horner_fmpz(to._fmpz(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(evaluate_op, fmpz_vecxx,
        FMPZ_POLYXX_COND_S, FMPZ_VECXX_COND_S,
        fmpz_poly_evaluate_fmpz_vec(to._data().array, e1._poly(),
            e2._data().array, e2.size()))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpz_polyxx_interpolate_op, fmpz_polyxx,
        FMPZ_VECXX_COND_S, FMPZ_VECXX_COND_S,
        fmpz_poly_interpolate_fmpz_vec(to._poly(), e1._data().array,
            e2._data().array, e2.size()))

FLINT_DEFINE_BINARY_EXPR_COND2(taylor_shift_horner_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_taylor_shift_horner(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(taylor_shift_divconquer_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_taylor_shift_divconquer(to._poly(), e1._poly(), e2._fmpz()))
FLINT_DEFINE_BINARY_EXPR_COND2(taylor_shift_op, fmpz_polyxx,
        FMPZ_POLYXX_COND_S, FMPZXX_COND_S,
        fmpz_poly_taylor_shift(to._poly(), e1._poly(), e2._fmpz()))

FMPZ_POLYXX_DEFINE_SQRLOW(revert_series)
FMPZ_POLYXX_DEFINE_SQRLOW(revert_series_newton)
FMPZ_POLYXX_DEFINE_SQRLOW(revert_series_lagrange_fast)
FMPZ_POLYXX_DEFINE_SQRLOW(revert_series_lagrange)

FLINT_DEFINE_UNARY_EXPR_COND(sqrt_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        execution_check(fmpz_poly_sqrt(to._poly(), from._poly()),
            "sqrt", "fmpz_polyxx"))
FLINT_DEFINE_UNARY_EXPR_COND(sqrt_classical_op, fmpz_polyxx, FMPZ_POLYXX_COND_S,
        execution_check(fmpz_poly_sqrt_classical(to._poly(), from._poly()),
            "sqrt_classical", "fmpz_polyxx"))

FLINT_DEFINE_UNARY_EXPR_COND(fmpz_polyxx_product_roots_op, fmpz_polyxx,
        FMPZ_VECXX_COND_S,
        fmpz_poly_product_roots_fmpz_vec(to._poly(),
            from._data().array, from.size()))

FLINT_DEFINE_BINARY_EXPR_COND2(fmpz_polyxx_get_coeff_op, fmpzxx,
        FMPZ_POLYXX_COND_S, traits::is_integer,
        fmpz_poly_get_coeff_fmpz(to._fmpz(), e1._poly(), e2))
FLINT_DEFINE_UNARY_EXPR_COND(fmpz_polyxx_lead_op, fmpzxx,
        FMPZ_POLYXX_COND_S,
        fmpz_set(to._fmpz(), fmpz_poly_lead(from._poly())))

FLINT_DEFINE_UNARY_EXPR_COND(bound_roots_op, fmpzxx, FMPZ_POLYXX_COND_S,
        fmpz_poly_bound_roots(to._fmpz(), from._poly()))

namespace rdetail {
typedef make_ltuple<mp::make_tuple<fmpz_polyxx, fmpz_polyxx>::type>::type
    fmpz_polyxx_pair;
typedef make_ltuple<mp::make_tuple<fmpzxx, fmpz_polyxx, fmpz_polyxx>::type>::type
    fmpzxx_fmpz_polyxx_pair;
} // rdetail
#define FMPZ_POLYXX_DEFINE_DIVREM(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, rdetail::fmpz_polyxx_pair, \
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, \
    fmpz_poly_##name(to.template get<0>()._poly(), to.template get<1>()._poly(), \
        e1._poly(), e2._poly()))
FMPZ_POLYXX_DEFINE_DIVREM(divrem_basecase)
FMPZ_POLYXX_DEFINE_DIVREM(divrem_divconquer)
FMPZ_POLYXX_DEFINE_DIVREM(divrem)
FMPZ_POLYXX_DEFINE_DIVREM(pseudo_divrem_cohen)

#define FMPZ_POLYXX_DEFINE_MULFUNC(name) \
FLINT_DEFINE_THREEARY_EXPR_COND3(name##_op, fmpz_polyxx, \
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, traits::fits_into_slong, \
    fmpz_poly_##name(to._poly(), e1._poly(), e2._poly(), e3))

FMPZ_POLYXX_DEFINE_MULFUNC(mullow_classical)
FMPZ_POLYXX_DEFINE_MULFUNC(mulhigh_classical)
FMPZ_POLYXX_DEFINE_MULFUNC(mullow_karatsuba_n)
FMPZ_POLYXX_DEFINE_MULFUNC(mulhigh_karatsuba_n)
FMPZ_POLYXX_DEFINE_MULFUNC(mullow_KS)
FMPZ_POLYXX_DEFINE_MULFUNC(mullow_SS)
FMPZ_POLYXX_DEFINE_MULFUNC(mullow)
FMPZ_POLYXX_DEFINE_MULFUNC(mulhigh_n)

FLINT_DEFINE_BINARY_EXPR_COND2(xgcd_op, rdetail::fmpzxx_fmpz_polyxx_pair,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    fmpz_poly_xgcd(to.template get<0>()._fmpz(), to.template get<1>()._poly(),
        to.template get<2>()._poly(), e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(xgcd_modular_op, rdetail::fmpzxx_fmpz_polyxx_pair,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    fmpz_poly_xgcd_modular(to.template get<0>()._fmpz(),
        to.template get<1>()._poly(),
        to.template get<2>()._poly(), e1._poly(), e2._poly()))

FLINT_DEFINE_THREEARY_EXPR_COND3(pow_trunc_op, fmpz_polyxx,
    FMPZ_POLYXX_COND_S, traits::is_unsigned_integer, traits::fits_into_slong,
    fmpz_poly_pow_trunc(to._poly(), e1._poly(), e2, e3))

#define FMPZ_POLYXX_DEFINE_SERIES(name) \
FLINT_DEFINE_THREEARY_EXPR_COND3(name##_op, fmpz_polyxx, \
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, traits::fits_into_slong, \
    fmpz_poly_##name(to._poly(), e1._poly(), e2._poly(), e3))
FMPZ_POLYXX_DEFINE_SERIES(div_series)
FMPZ_POLYXX_DEFINE_SERIES(compose_series_brent_kung)
FMPZ_POLYXX_DEFINE_SERIES(compose_series_horner)
FMPZ_POLYXX_DEFINE_SERIES(compose_series)

namespace rdetail {
typedef make_ltuple<mp::make_tuple<fmpz_polyxx, fmpz_polyxx, ulong>::type>::type
    fmpz_polyxx_pair_ulong;
typedef make_ltuple<mp::make_tuple<fmpz_polyxx, ulong>::type>::type
    fmpz_polyxx_ulong;
typedef make_ltuple<mp::make_tuple<bool, fmpz_polyxx>::type>::type
    bool_fmpz_polyxx;
}
#define FMPZ_POLYXX_DEFINE_PSEUDO_DIVREM(name) \
FLINT_DEFINE_BINARY_EXPR_COND2(name##_op, rdetail::fmpz_polyxx_pair_ulong, \
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, \
    fmpz_poly_##name(to.template get<0>()._poly(), to.template get<1>()._poly(), \
        &to.template get<2>(), e1._poly(), e2._poly()))
FMPZ_POLYXX_DEFINE_PSEUDO_DIVREM(pseudo_divrem_basecase)
FMPZ_POLYXX_DEFINE_PSEUDO_DIVREM(pseudo_divrem_divconquer)
FMPZ_POLYXX_DEFINE_PSEUDO_DIVREM(pseudo_divrem)

FLINT_DEFINE_BINARY_EXPR_COND2(pseudo_div_op, rdetail::fmpz_polyxx_ulong,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    fmpz_poly_pseudo_div(to.template get<0>()._poly(), &to.template get<1>(),
        e1._poly(), e2._poly()))
FLINT_DEFINE_BINARY_EXPR_COND2(pseudo_rem_op, rdetail::fmpz_polyxx_ulong,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    fmpz_poly_pseudo_rem(to.template get<0>()._poly(), &to.template get<1>(),
        e1._poly(), e2._poly()))

FLINT_DEFINE_BINARY_EXPR_COND2(divides_op, rdetail::bool_fmpz_polyxx,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    to.template get<0> () =
        fmpz_poly_divides(to.template get<1>()._poly(), e1._poly(), e2._poly()))

namespace rdetail {
typedef make_ltuple<mp::make_tuple<
        fmpz_polyxx, fmpz_polyxx, fmpz_polyxx, fmpz_polyxx>::type>::type
    fmpz_polyxx_quadruple;
}
FLINT_DEFINE_SEVENARY_EXPR_COND7(hensel_lift_op, rdetail::fmpz_polyxx_quadruple,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    FMPZXX_COND_S, FMPZXX_COND_S,
    fmpz_poly_hensel_lift(to.template get<0>()._poly(),
        to.template get<1>()._poly(), to.template get<2>()._poly(),
        to.template get<3>()._poly(),
        e1._poly(), e2._poly(), e3._poly(), e4._poly(), e5._poly(),
        e6._fmpz(), e7._fmpz()))
FLINT_DEFINE_SEVENARY_EXPR_COND7(hensel_lift_without_inverse_op,
    rdetail::fmpz_polyxx_pair,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    FMPZXX_COND_S, FMPZXX_COND_S,
    fmpz_poly_hensel_lift_without_inverse(to.template get<0>()._poly(),
        to.template get<1>()._poly(),
        e1._poly(), e2._poly(), e3._poly(), e4._poly(), e5._poly(),
        e6._fmpz(), e7._fmpz()))
FLINT_DEFINE_SIXARY_EXPR_COND6(hensel_lift_only_inverse_op,
    rdetail::fmpz_polyxx_pair,
    FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S, FMPZ_POLYXX_COND_S,
    FMPZ_POLYXX_COND_S,
    FMPZXX_COND_S, FMPZXX_COND_S,
    fmpz_poly_hensel_lift_only_inverse(to.template get<0>()._poly(),
        to.template get<1>()._poly(),
        e1._poly(), e2._poly(), e3._poly(), e4._poly(),
        e5._fmpz(), e6._fmpz()))
} // rules

// immediate functions
// TODO make lazy when we have nmod class
template<class Poly>
inline typename mp::enable_all_fmpz_polyxx<mp_limb_t, Poly>::type
evaluate_mod(const Poly& p, mp_limb_t x, mp_limb_t n)
{
    return fmpz_poly_evaluate_mod(p.evaluate()._poly(), x, n);
}

template<class Fmpz_poly>
int read_pretty(Fmpz_poly& f, char** x,
        typename mp::enable_if<FMPZ_POLYXX_COND_T<Fmpz_poly> >::type* = 0)
{
    return f.read_pretty(x);
}
template<class Fmpz_poly>
int read_pretty(FILE* fi, Fmpz_poly& f, char** x,
        typename mp::enable_if<FMPZ_POLYXX_COND_T<Fmpz_poly> >::type* = 0)
{
    return f.read_pretty(x, fi);
}
} // flint

#include "nmod_polyxx.h" // modular reconstruction code

#include "fmpz_poly_factorxx.h"

#endif
