/*
    Copyright (C) 2023, 2024 Fredrik Johansson

    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 3 of the License, or
    (at your option) any later version.  See <https://www.gnu.org/licenses/>.
*/

#include <string.h>
#include "fmpq.h"
#include "gr_vec.h"
#include "gr_poly.h"
#include "gr_generic.h"
#include "gr_series.h"
#include "gr_series/impl.h"

static const char * default_var = "x";

void gr_series_mod_ctx_clear(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_CTX(ctx)->var != default_var)
        flint_free(GR_SERIES_MOD_CTX(ctx)->var);
}

int gr_series_mod_ctx_write(gr_stream_t out, gr_ctx_t ctx)
{
    int status = GR_SUCCESS;
    status |= gr_stream_write(out, "Power series over ");
    status |= gr_ctx_write(out, GR_SERIES_MOD_ELEM_CTX(ctx));
    status |= gr_stream_write(out, " mod ");
    status |= gr_stream_write(out, GR_SERIES_MOD_CTX(ctx)->var);
    status |= gr_stream_write(out, "^");
    status |= gr_stream_write_si(out, GR_SERIES_MOD_N(ctx));
    return status;
}

truth_t
gr_series_mod_ctx_is_ring(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_ring(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_commutative_ring(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_commutative_ring(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_integral_domain(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) != 1)
        return T_FALSE;

    return gr_ctx_is_integral_domain(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_rational_vector_space(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_rational_vector_space(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_real_vector_space(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_real_vector_space(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_complex_vector_space(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_complex_vector_space(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_field(gr_ctx_t ctx)
{
    if (GR_SERIES_MOD_N(ctx) != 1)
        return T_FALSE;

    return gr_ctx_is_field(GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t
gr_series_mod_ctx_is_finite_characteristic(gr_ctx_t ctx)
{
    /* Zero ring */
    if (GR_SERIES_MOD_N(ctx) == 0)
        return T_TRUE;

    return gr_ctx_is_finite_characteristic(GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_ctx_set_gen_name(gr_ctx_t ctx, const char * s)
{
    slong len;
    len = strlen(s);

    if (GR_SERIES_MOD_CTX(ctx)->var == default_var)
        GR_SERIES_MOD_CTX(ctx)->var = NULL;

    GR_SERIES_MOD_CTX(ctx)->var = flint_realloc(GR_SERIES_MOD_CTX(ctx)->var, len + 1);
    memcpy(GR_SERIES_MOD_CTX(ctx)->var, s, len + 1);
    return GR_SUCCESS;
}

int gr_series_mod_ctx_set_gen_names(gr_ctx_t ctx, const char ** s)
{
    return gr_series_mod_ctx_set_gen_name(ctx, s[0]);
}


#define _gr_series_mod_ctx_gen_name _gr_series_ctx_gen_name

int
gr_series_mod_gens_recursive(gr_vec_t vec, gr_ctx_t ctx)
{
    int status;
    gr_vec_t vec1;
    slong i, n;

    /* Get generators of the element ring */
    gr_vec_init(vec1, 0, GR_SERIES_MOD_ELEM_CTX(ctx));
    status = gr_gens_recursive(vec1, GR_SERIES_MOD_ELEM_CTX(ctx));
    n = vec1->length;

    gr_vec_set_length(vec, n + 1, ctx);

    /* Promote to polynomials */
    for (i = 0; i < n; i++)
        status |= gr_poly_set_scalar(gr_vec_entry_ptr(vec, i, ctx),
                gr_vec_entry_srcptr(vec1, i, GR_SERIES_MOD_ELEM_CTX(ctx)),
                GR_SERIES_MOD_ELEM_CTX(ctx));

    status |= gr_poly_gen(gr_vec_entry_ptr(vec, n, ctx), GR_SERIES_MOD_ELEM_CTX(ctx));

    gr_vec_clear(vec1, GR_SERIES_MOD_ELEM_CTX(ctx));

    return status;
}

static int
gr_series_mod_big_o_base_fmpz(gr_poly_t res, const gr_poly_t base, const fmpz_t exp, gr_ctx_t ctx)
{
    gr_ctx_struct * cctx = GR_SERIES_MOD_ELEM_CTX(ctx);
    int status;

    /* base is a constant: build O(base^exp) in the coefficient ring */
    if (gr_poly_length(base, cctx) == 1)
    {
        gr_ptr t;
        GR_TMP_INIT(t, cctx);

        status = gr_big_o_base_fmpz(t, base->coeffs, exp, cctx);

        if (status == GR_SUCCESS)
            status = (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS
                                                 : gr_poly_set_scalar(res, t, cctx);

        GR_TMP_CLEAR(t, cctx);
        return status;
    }

    /* we don't support inexact elements with respect to the generator */
    return GR_UNABLE;
}

static gr_ptr _gr_series_mod_ctx_base(gr_ctx_t ctx) { return GR_SERIES_MOD_ELEM_CTX(ctx); }


void gr_series_mod_init(gr_poly_t res, gr_ctx_t ctx)
{
    gr_poly_init(res, GR_SERIES_MOD_ELEM_CTX(ctx));
}

void gr_series_mod_clear(gr_poly_t res, gr_ctx_t ctx)
{
    gr_poly_clear(res, GR_SERIES_MOD_ELEM_CTX(ctx));
}

void gr_series_mod_swap(gr_poly_t x, gr_poly_t y, gr_ctx_t ctx)
{
    gr_poly_swap(x, y, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_randtest(gr_poly_t res, flint_rand_t state, gr_ctx_t ctx)
{
    return gr_poly_randtest(res, state, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_write(gr_stream_t out, const gr_poly_t x, gr_ctx_t ctx)
{
    int status = GR_SUCCESS;
    status |= gr_poly_write(out, x, GR_SERIES_MOD_CTX(ctx)->var, GR_SERIES_MOD_ELEM_CTX(ctx));
    status |= gr_stream_write(out, " (mod ");
    status |= gr_stream_write(out, GR_SERIES_MOD_CTX(ctx)->var);
    status |= gr_stream_write(out, "^");
    status |= gr_stream_write_si(out, GR_SERIES_MOD_N(ctx));
    status |= gr_stream_write(out, ")");
    return status;
}

int gr_series_mod_zero(gr_poly_t res, gr_ctx_t ctx)
{
    return gr_poly_zero(res, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_one(gr_poly_t res, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_one(res, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_gen(gr_poly_t res, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) <= 1) ? gr_poly_zero(res, GR_SERIES_MOD_ELEM_CTX(ctx)) : gr_poly_gen(res, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_set(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx)
{
    return gr_poly_set(res, x, GR_SERIES_MOD_ELEM_CTX(ctx));
}

void gr_series_mod_set_shallow(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx)
{
    *res = *x;
}

int gr_series_mod_set_si(gr_poly_t res, slong c, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_set_si(res, c, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_set_ui(gr_poly_t res, ulong c, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_set_ui(res, c, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_set_fmpz(gr_poly_t res, const fmpz_t c, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_set_fmpz(res, c, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_set_fmpq(gr_poly_t res, const fmpq_t c, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_set_fmpq(res, c, GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t gr_series_mod_is_zero(const gr_poly_t x, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? T_TRUE : gr_poly_is_zero(x, GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t gr_series_mod_is_one(const gr_poly_t x, gr_ctx_t ctx)
{
    return (GR_SERIES_MOD_N(ctx) == 0) ? T_TRUE : gr_poly_is_one(x, GR_SERIES_MOD_ELEM_CTX(ctx));
}

truth_t gr_series_mod_equal(const gr_poly_t x, const gr_poly_t y, gr_ctx_t ctx)
{
    return gr_poly_equal(x, y, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_neg(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx)
{
    return gr_poly_neg(res, x, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_add(gr_poly_t res, const gr_poly_t x, const gr_poly_t y, gr_ctx_t ctx)
{
    return gr_poly_add(res, x, y, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_sub(gr_poly_t res, const gr_poly_t x, const gr_poly_t y, gr_ctx_t ctx)
{
    return gr_poly_sub(res, x, y, GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_mul(gr_poly_t res, const gr_poly_t x, const gr_poly_t y, gr_ctx_t ctx)
{
    return gr_poly_mullow(res, x, y, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_inv(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx)
{
    if (gr_ctx_is_approx_commutative_ring(ctx) != T_TRUE)
        return GR_UNABLE;

    return gr_poly_inv_series(res, x, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
}

int gr_series_mod_div(gr_poly_t res, const gr_poly_t x, const gr_poly_t y, gr_ctx_t ctx)
{
    if (gr_ctx_is_approx_commutative_ring(ctx) != T_TRUE)
        return GR_UNABLE;

    return gr_poly_div_series(res, x, y, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
}

#define UNARY_POLY_WRAPPER(func) \
int \
gr_series_mod_ ## func(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx) \
{ \
    return gr_poly_ ## func ## _series(res, x, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx)); \
} \

#define BINARY_UNARY_POLY_WRAPPER(func) \
int \
gr_series_mod_ ## func(gr_poly_t res1, gr_poly_t res2, const gr_poly_t x, gr_ctx_t ctx) \
{ \
    return gr_poly_ ## func ## _series(res1, res2, x, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx)); \
} \

UNARY_POLY_WRAPPER(exp)
UNARY_POLY_WRAPPER(log)
UNARY_POLY_WRAPPER(rsqrt)
UNARY_POLY_WRAPPER(sin)
UNARY_POLY_WRAPPER(cos)
UNARY_POLY_WRAPPER(sin_pi)
UNARY_POLY_WRAPPER(cos_pi)
UNARY_POLY_WRAPPER(tan)
UNARY_POLY_WRAPPER(tanh)
UNARY_POLY_WRAPPER(cot)
UNARY_POLY_WRAPPER(coth)
UNARY_POLY_WRAPPER(tan_pi)
UNARY_POLY_WRAPPER(cot_pi)
UNARY_POLY_WRAPPER(asin)
UNARY_POLY_WRAPPER(acos)
UNARY_POLY_WRAPPER(atan)
UNARY_POLY_WRAPPER(asinh)
UNARY_POLY_WRAPPER(acosh)
UNARY_POLY_WRAPPER(atanh)

BINARY_UNARY_POLY_WRAPPER(sin_cos)
BINARY_UNARY_POLY_WRAPPER(sin_cos_pi)


/* fixme: gr_poly_sqrt_series does not deal with leading zeros */
int gr_series_mod_sqrt(gr_poly_t res, const gr_poly_t x, gr_ctx_t ctx)
{
    slong n = GR_SERIES_MOD_N(ctx);

    if (n == 0)
        return GR_SUCCESS;

    if (x->length == 0)
        return gr_poly_zero(res, GR_SERIES_MOD_ELEM_CTX(ctx));

    if (gr_is_zero(x->coeffs, GR_SERIES_MOD_ELEM_CTX(ctx)) != T_FALSE)
        return GR_UNABLE;

    return gr_poly_sqrt_series(res, x, n, GR_SERIES_MOD_ELEM_CTX(ctx));
}


static int
_set_truncate_poly(gr_poly_t res, const gr_poly_t x, gr_ctx_t x_elem_ctx, slong n, gr_ctx_t elem_ctx)
{
    if (x_elem_ctx == elem_ctx)
    {
        return gr_poly_truncate(res, x, n, elem_ctx);
    }
    else
    {
        if (gr_poly_length(x, x_elem_ctx) <= n)
        {
            return gr_poly_set_gr_poly_other(res, x, x_elem_ctx, elem_ctx);
        }
        else
        {
            int status = GR_SUCCESS;
            gr_poly_t t;
            /* hack: t may not be normalised, but that's fine because
               gr_poly_set_gr_poly_other currently always normalises */
            t->coeffs = x->coeffs;
            t->length = n;
            status |= gr_poly_set_gr_poly_other(res, t, x_elem_ctx, elem_ctx);
            return status;
        }
    }
}

int
gr_series_mod_set_other(gr_poly_t res, gr_srcptr x, gr_ctx_t x_ctx, gr_ctx_t ctx)
{
    if (x_ctx == ctx)
    {
        return gr_series_mod_set(res, x, ctx);
    }
    else if (x_ctx == GR_SERIES_MOD_ELEM_CTX(ctx))
    {
        return (GR_SERIES_MOD_N(ctx) == 0) ? GR_SUCCESS : gr_poly_set_scalar(res, x, GR_SERIES_MOD_ELEM_CTX(ctx));
    }
    else if (x_ctx->which_ring == GR_CTX_SERIES_MOD_GR_POLY && !strcmp(GR_SERIES_MOD_CTX(x_ctx)->var, GR_SERIES_MOD_CTX(ctx)->var))
    {
        if (GR_SERIES_MOD_N(ctx) <= GR_SERIES_MOD_N(x_ctx))   /* set() does modular reduction */
            return _set_truncate_poly(res, x, GR_SERIES_MOD_ELEM_CTX(x_ctx), GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
        else
            return GR_DOMAIN;   /* set() doesn't do lift */
    }
    else if (x_ctx->which_ring == GR_CTX_GR_POLY && !strcmp(POLYNOMIAL_CTX(x_ctx)->var, GR_SERIES_MOD_CTX(ctx)->var))
    {
        return _set_truncate_poly(res, x, POLYNOMIAL_ELEM_CTX(x_ctx), GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
    }
    else if (x_ctx->which_ring == GR_CTX_GR_SERIES && !strcmp(GR_SERIES_CTX(x_ctx)->var, GR_SERIES_MOD_CTX(ctx)->var))
    {
        /* all coefficients below x^n must be known */
        if (((const gr_series_struct *) x)->error < GR_SERIES_MOD_N(ctx))
            return GR_UNABLE;
        else
            return _set_truncate_poly(res, &((const gr_series_struct *) x)->poly, GR_SERIES_ELEM_CTX(x_ctx), GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
    }
    else
    {
        int status = GR_SUCCESS;

        gr_poly_fit_length(res, 1, GR_SERIES_MOD_ELEM_CTX(ctx));
        status = gr_set_other(res->coeffs, x, x_ctx, GR_SERIES_MOD_ELEM_CTX(ctx));

        if (status == GR_SUCCESS)
        {
            _gr_poly_set_length(res, 1, GR_SERIES_MOD_ELEM_CTX(ctx));
            _gr_poly_normalise(res, GR_SERIES_MOD_ELEM_CTX(ctx));
        }
        else
            _gr_poly_set_length(res, 0, GR_SERIES_MOD_ELEM_CTX(ctx));

        status |= gr_poly_truncate(res, res, GR_SERIES_MOD_N(ctx), GR_SERIES_MOD_ELEM_CTX(ctx));
        return status;
    }

    return GR_UNABLE;
}



int _gr_series_mod_methods_initialized = 0;

gr_static_method_table _gr_series_mod_methods;

gr_method_tab_input _gr_series_mod_methods_input[] =
{
    {GR_METHOD_CTX_CLEAR,   (gr_funcptr) gr_series_mod_ctx_clear},
    {GR_METHOD_CTX_WRITE,   (gr_funcptr) gr_series_mod_ctx_write},
    {GR_METHOD_CTX_SET_GEN_NAME, (gr_funcptr) gr_series_mod_ctx_set_gen_name},
    {GR_METHOD_CTX_SET_GEN_NAMES, (gr_funcptr) gr_series_mod_ctx_set_gen_names},
    {GR_METHOD_CTX_NGENS,       (gr_funcptr) gr_generic_ctx_ngens_1},
    {GR_METHOD_CTX_GEN_NAME,    (gr_funcptr) _gr_series_mod_ctx_gen_name},
    {GR_METHOD_CTX_IS_RING, (gr_funcptr) gr_series_mod_ctx_is_ring},
    {GR_METHOD_CTX_IS_COMMUTATIVE_RING, (gr_funcptr) gr_series_mod_ctx_is_commutative_ring},
    {GR_METHOD_CTX_IS_INTEGRAL_DOMAIN, (gr_funcptr) gr_series_mod_ctx_is_integral_domain},
    {GR_METHOD_CTX_IS_FIELD, (gr_funcptr) gr_series_mod_ctx_is_field},
    {GR_METHOD_CTX_IS_RATIONAL_VECTOR_SPACE, (gr_funcptr) gr_series_mod_ctx_is_rational_vector_space},
    {GR_METHOD_CTX_IS_REAL_VECTOR_SPACE, (gr_funcptr) gr_series_mod_ctx_is_real_vector_space},
    {GR_METHOD_CTX_IS_COMPLEX_VECTOR_SPACE, (gr_funcptr) gr_series_mod_ctx_is_complex_vector_space},
    {GR_METHOD_CTX_IS_FINITE_CHARACTERISTIC, (gr_funcptr) gr_series_mod_ctx_is_finite_characteristic},
    {GR_METHOD_CTX_BASE,    (gr_funcptr) _gr_series_mod_ctx_base},
    {GR_METHOD_INIT,        (gr_funcptr) gr_series_mod_init},
    {GR_METHOD_CLEAR,       (gr_funcptr) gr_series_mod_clear},
    {GR_METHOD_SWAP,        (gr_funcptr) gr_series_mod_swap},
    {GR_METHOD_SET_SHALLOW, (gr_funcptr) gr_series_mod_set_shallow},
    {GR_METHOD_RANDTEST,    (gr_funcptr) gr_series_mod_randtest},
    {GR_METHOD_WRITE,       (gr_funcptr) gr_series_mod_write},
    {GR_METHOD_ZERO,        (gr_funcptr) gr_series_mod_zero},
    {GR_METHOD_ONE,         (gr_funcptr) gr_series_mod_one},
    {GR_METHOD_IS_ZERO,     (gr_funcptr) gr_series_mod_is_zero},
    {GR_METHOD_IS_ONE,      (gr_funcptr) gr_series_mod_is_one},
    {GR_METHOD_EQUAL,       (gr_funcptr) gr_series_mod_equal},
    {GR_METHOD_GEN,         (gr_funcptr) gr_series_mod_gen},
    {GR_METHOD_GENS,        (gr_funcptr) gr_generic_gens_single},
    {GR_METHOD_GENS_RECURSIVE,  (gr_funcptr) gr_series_mod_gens_recursive},
    {GR_METHOD_BIG_O_BASE_FMPZ, (gr_funcptr) gr_series_mod_big_o_base_fmpz},
    {GR_METHOD_SET,         (gr_funcptr) gr_series_mod_set},
    {GR_METHOD_SET_UI,      (gr_funcptr) gr_series_mod_set_ui},
    {GR_METHOD_SET_SI,      (gr_funcptr) gr_series_mod_set_si},
    {GR_METHOD_SET_FMPZ,    (gr_funcptr) gr_series_mod_set_fmpz},
    {GR_METHOD_SET_FMPQ,    (gr_funcptr) gr_series_mod_set_fmpq},
    {GR_METHOD_SET_OTHER,   (gr_funcptr) gr_series_mod_set_other},
    {GR_METHOD_SET_STR,     (gr_funcptr) gr_generic_set_str_balance_additions},
    {GR_METHOD_NEG,         (gr_funcptr) gr_series_mod_neg},
    {GR_METHOD_ADD,         (gr_funcptr) gr_series_mod_add},
    {GR_METHOD_SUB,         (gr_funcptr) gr_series_mod_sub},
    {GR_METHOD_MUL,         (gr_funcptr) gr_series_mod_mul},
    {GR_METHOD_INV,         (gr_funcptr) gr_series_mod_inv},
    {GR_METHOD_DIV,         (gr_funcptr) gr_series_mod_div},
    {GR_METHOD_SQRT,        (gr_funcptr) gr_series_mod_sqrt},
    {GR_METHOD_RSQRT,       (gr_funcptr) gr_series_mod_rsqrt},
    {GR_METHOD_EXP,         (gr_funcptr) gr_series_mod_exp},
    {GR_METHOD_LOG,         (gr_funcptr) gr_series_mod_log},
    {GR_METHOD_SIN,         (gr_funcptr) gr_series_mod_sin},
    {GR_METHOD_COS,         (gr_funcptr) gr_series_mod_cos},
    {GR_METHOD_SIN_PI,      (gr_funcptr) gr_series_mod_sin_pi},
    {GR_METHOD_COS_PI,      (gr_funcptr) gr_series_mod_cos_pi},
    {GR_METHOD_SIN_COS,     (gr_funcptr) gr_series_mod_sin_cos},
    {GR_METHOD_SIN_COS_PI,  (gr_funcptr) gr_series_mod_sin_cos_pi},
    {GR_METHOD_TAN,         (gr_funcptr) gr_series_mod_tan},
    {GR_METHOD_TANH,        (gr_funcptr) gr_series_mod_tanh},
    {GR_METHOD_COT,         (gr_funcptr) gr_series_mod_cot},
    {GR_METHOD_COTH,        (gr_funcptr) gr_series_mod_coth},
    {GR_METHOD_TAN_PI,      (gr_funcptr) gr_series_mod_tan_pi},
    {GR_METHOD_COT_PI,      (gr_funcptr) gr_series_mod_cot_pi},
    {GR_METHOD_ASIN,        (gr_funcptr) gr_series_mod_asin},
    {GR_METHOD_ACOS,        (gr_funcptr) gr_series_mod_acos},
    {GR_METHOD_ATAN,        (gr_funcptr) gr_series_mod_atan},
    {GR_METHOD_ASINH,       (gr_funcptr) gr_series_mod_asinh},
    {GR_METHOD_ACOSH,       (gr_funcptr) gr_series_mod_acosh},
    {GR_METHOD_ATANH,       (gr_funcptr) gr_series_mod_atanh},
/*
    {GR_METHOD_GAMMA,       (gr_funcptr) gr_series_mod_gamma},
    {GR_METHOD_RGAMMA,      (gr_funcptr) gr_series_mod_rgamma},
    {GR_METHOD_LGAMMA,      (gr_funcptr) gr_series_mod_lgamma},
    {GR_METHOD_DIGAMMA,     (gr_funcptr) gr_series_mod_digamma},
    {GR_METHOD_ERF,         (gr_funcptr) gr_series_mod_erf},
    {GR_METHOD_ERFC,        (gr_funcptr) gr_series_mod_erfc},
    {GR_METHOD_ERFI,        (gr_funcptr) gr_series_mod_erfi},
    {GR_METHOD_FRESNEL,     (gr_funcptr) gr_series_mod_fresnel},
    {GR_METHOD_FRESNEL_S,   (gr_funcptr) gr_series_mod_fresnel_s},
    {GR_METHOD_FRESNEL_C,   (gr_funcptr) gr_series_mod_fresnel_c},
    {GR_METHOD_AIRY,        (gr_funcptr) gr_series_mod_airy},
    {GR_METHOD_AIRY_AI,        (gr_funcptr) gr_series_mod_airy_ai},
    {GR_METHOD_AIRY_AI_PRIME,  (gr_funcptr) gr_series_mod_airy_ai_prime},
    {GR_METHOD_AIRY_BI,        (gr_funcptr) gr_series_mod_airy_bi},
    {GR_METHOD_AIRY_BI_PRIME,  (gr_funcptr) gr_series_mod_airy_bi_prime},
    {GR_METHOD_EXP_INTEGRAL_EI,       (gr_funcptr) gr_series_mod_exp_integral_ei},
    {GR_METHOD_COS_INTEGRAL,          (gr_funcptr) gr_series_mod_cos_integral},
    {GR_METHOD_COSH_INTEGRAL,         (gr_funcptr) gr_series_mod_cosh_integral},
    {GR_METHOD_SIN_INTEGRAL,          (gr_funcptr) gr_series_mod_sin_integral},
    {GR_METHOD_SINH_INTEGRAL,         (gr_funcptr) gr_series_mod_sinh_integral},
    {GR_METHOD_LOG_INTEGRAL,          (gr_funcptr) gr_series_mod_log_integral},
    {GR_METHOD_GAMMA_UPPER,           (gr_funcptr) gr_series_mod_gamma_upper},
    {GR_METHOD_GAMMA_LOWER,           (gr_funcptr) gr_series_mod_gamma_lower},
    {GR_METHOD_BETA_LOWER,            (gr_funcptr) gr_series_mod_beta_lower},
    {GR_METHOD_HYPGEOM_PFQ,           (gr_funcptr) gr_series_mod_hypgeom_pfq},
    {GR_METHOD_HURWITZ_ZETA,          (gr_funcptr) gr_series_mod_hurwitz_zeta},
    {GR_METHOD_POLYLOG,               (gr_funcptr) gr_series_mod_polylog},
    {GR_METHOD_DIRICHLET_L,           (gr_funcptr) gr_series_mod_dirichlet_l},
    {GR_METHOD_DIRICHLET_HARDY_Z,     (gr_funcptr) gr_series_mod_dirichlet_hardy_z},
    {GR_METHOD_DIRICHLET_HARDY_THETA, (gr_funcptr) gr_series_mod_dirichlet_hardy_theta},
    {GR_METHOD_JACOBI_THETA,          (gr_funcptr) gr_series_mod_jacobi_theta},
    {GR_METHOD_JACOBI_THETA_1,          (gr_funcptr) gr_series_mod_jacobi_theta_1},
    {GR_METHOD_JACOBI_THETA_2,          (gr_funcptr) gr_series_mod_jacobi_theta_2},
    {GR_METHOD_JACOBI_THETA_3,          (gr_funcptr) gr_series_mod_jacobi_theta_3},
    {GR_METHOD_JACOBI_THETA_4,          (gr_funcptr) gr_series_mod_jacobi_theta_4},
    {GR_METHOD_AGM1,                   (gr_funcptr) gr_series_mod_agm1},
    {GR_METHOD_ELLIPTIC_K,             (gr_funcptr) gr_series_mod_elliptic_k},
    {GR_METHOD_WEIERSTRASS_P,          (gr_funcptr) gr_series_mod_weierstrass_p},
*/
    {0,                     (gr_funcptr) NULL},
};

void
gr_series_mod_ctx_init(gr_ctx_t ctx, gr_ctx_t base_ring, slong n)
{
    ctx->which_ring = GR_CTX_SERIES_MOD_GR_POLY;
    ctx->sizeof_elem = sizeof(gr_poly_struct);
    ctx->size_limit = WORD_MAX;

    GR_SERIES_MOD_CTX(ctx)->base_ring = (gr_ctx_struct *) base_ring;
    GR_SERIES_MOD_CTX(ctx)->var = (char *) default_var;
    GR_SERIES_MOD_CTX(ctx)->n = FLINT_MAX(0, n);

    ctx->methods = _gr_series_mod_methods;

    if (!_gr_series_mod_methods_initialized)
    {
        gr_method_tab_init(_gr_series_mod_methods, _gr_series_mod_methods_input);
        _gr_series_mod_methods_initialized = 1;
    }
}

/* compatibility */
void
gr_ctx_init_series_mod_gr_poly(gr_ctx_t ctx, gr_ctx_t base_ring, slong n)
{
    gr_series_mod_ctx_init(ctx, base_ring, n);
}

