/*
    Copyright (C) 2020 Daniel Schultz

    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 "fq_nmod.h"
#include "n_poly.h"

static void _n_fq_poly_taylor_shift_horner_n_fq(
    ulong * poly,
    const ulong * c,
    slong n,
    const fq_nmod_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx);
    slong i, j;
    ulong * p = FLINT_ARRAY_ALLOC(d, ulong);

    for (i = n - 2; i >= 0; i--)
    {
        for (j = i; j < n - 1; j++)
        {
            n_fq_mul(p, poly + d*(j + 1), c, ctx);
            n_fq_add(poly + d*j, poly + d*j, p, ctx);
        }
    }

    flint_free(p);
}


void n_fq_bpoly_taylor_shift_gen1_fq_nmod(
    n_bpoly_t A,
    const n_bpoly_t B,
    const fq_nmod_t c_,
    const fq_nmod_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx);
    slong i;
    ulong * c = FLINT_ARRAY_ALLOC(d, ulong);

    n_fq_set_fq_nmod(c, c_, ctx);
    n_fq_bpoly_set(A, B, ctx);
    for (i = A->length - 1; i >= 0; i--)
        _n_fq_poly_taylor_shift_horner_n_fq(A->coeffs[i].coeffs, c, A->coeffs[i].length, ctx);

    flint_free(c);
}

void n_fq_bpoly_taylor_shift_gen0_fq_nmod(
    n_bpoly_t A,
    const fq_nmod_t alpha,
    const fq_nmod_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx);
    slong n, i, j;
    ulong * c;
    n_poly_t t;

    if (fq_nmod_is_zero(alpha, ctx))
        return;

    c = FLINT_ARRAY_ALLOC(d, ulong);
    n_fq_set_fq_nmod(c, alpha, ctx);

    n_poly_init(t);
    n = A->length;

    for (i = n - 2; i >= 0; i--)
    {
        for (j = i; j < n - 1; j++)
        {
            n_fq_poly_scalar_mul_n_fq(t, A->coeffs + j + 1, c, ctx);
            n_fq_poly_add(A->coeffs + j, A->coeffs + j, t, ctx);
        }
    }

    n_poly_clear(t);

    flint_free(c);
}



void n_fq_bpoly_taylor_shift_gen0_n_fq(
    n_fq_bpoly_t A,
    const ulong * alpha,
    const fq_nmod_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx);
    slong i, j, n = A->length;
    ulong * tmp, * c, * alphainv;
    TMP_INIT;

    if (_n_fq_is_zero(alpha, d))
        return;

    TMP_START;

    tmp = (ulong *) TMP_ALLOC(d*N_FQ_MUL_INV_ITCH*sizeof(ulong));
    c = TMP_ALLOC(d*sizeof(ulong));
    alphainv = TMP_ALLOC(d*sizeof(ulong));

    _n_fq_one(c, d);
    for (i = 1; i < n; i++)
    {
        _n_fq_mul(c, c, alpha, ctx, tmp);
        if (!_n_fq_is_one(c, d))
        {
            ulong * Aic = A->coeffs[i].coeffs;
            for (j = 0; j < A->coeffs[i].length; j++)
                _n_fq_mul(Aic + d*j, Aic + d*j, c, ctx, tmp);
        }
    }

    for (i = n - 2; i >= 0; i--)
    {
        for (j = i; j < n - 1; j++)
        {
            n_fq_poly_add(A->coeffs + j, A->coeffs + j, A->coeffs + j + 1, ctx);
        }
    }

    _n_fq_inv(alphainv, alpha, ctx, tmp);
    _n_fq_one(c, d);
    for (i = 1; i < n; i++)
    {
        _n_fq_mul(c, c, alphainv, ctx, tmp);
        if (!_n_fq_is_one(c, d))
        {
            ulong * Aic = A->coeffs[i].coeffs;
            for (j = 0; j < A->coeffs[i].length; j++)
                _n_fq_mul(Aic + d*j, Aic + d*j, c, ctx, tmp);
        }
    }

    TMP_END;

    return;
}
