/*
    Copyright (C) 2018 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 "nmod_poly.h"
#include "nmod_mpoly.h"
#include "fmpz.h"
#include "fq_nmod.h"
#include "fq_nmod_mat.h"
#include "fq_nmod_poly.h"
#include "n_poly.h"
#include "mpoly.h"
#include "fq_nmod_mpoly.h"
#include "fq_nmod_mpoly/impl.h"

int fq_nmod_next(fq_nmod_t alpha, const fq_nmod_ctx_t fqctx)
{
    slong i;
    slong deg = nmod_poly_degree(fqctx->modulus);

    for (i = 0; i < deg; i++)
    {
        ulong c = nmod_poly_get_coeff_ui(alpha, i);
        c += UWORD(1);
        if (c < fqctx->mod.n)
        {
            nmod_poly_set_coeff_ui(alpha, i, c);
            return 1;
        }
        nmod_poly_set_coeff_ui(alpha, i, 0);
    }

    return 0;
}

void fq_nmod_next_not_zero(fq_nmod_t alpha, const fq_nmod_ctx_t fqctx)
{
    slong i;
    slong deg = fqctx->modulus->length - 1;

    for (i = 0; i < deg; i++) {
        ulong c = nmod_poly_get_coeff_ui(alpha, i);
        c += UWORD(1);
        if (c < fqctx->mod.n) {
            nmod_poly_set_coeff_ui(alpha, i, c);
            return;
        }
        nmod_poly_set_coeff_ui(alpha, i, UWORD(0));
    }

    /* we hit zero, so skip to 1 */
    nmod_poly_set_coeff_ui(alpha, 0, UWORD(1));
}


/*
    Assuming that "A" depends only on the main variable,
    convert it to a poly "a".
*/
static void fq_nmod_mpolyu_cvtto_poly(
    fq_nmod_poly_t a,
    fq_nmod_mpolyu_t A,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong i;
    fq_nmod_t at;

    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(A, ctx));

    fq_nmod_init(at, ctx->fqctx);

    fq_nmod_poly_zero(a, ctx->fqctx);
    for (i = 0; i < A->length; i++)
    {
        FLINT_ASSERT((A->coeffs + i)->length == 1);
        FLINT_ASSERT(mpoly_monomial_is_zero((A->coeffs + i)->exps,
                      mpoly_words_per_exp((A->coeffs + i)->bits, ctx->minfo)));

        n_fq_get_fq_nmod(at, (A->coeffs + i)->coeffs + 0, ctx->fqctx);
        fq_nmod_poly_set_coeff(a, A->exps[i], at, ctx->fqctx);
    }

    fq_nmod_clear(at, ctx->fqctx);
}

static void fq_nmod_mpolyu_cvtfrom_poly(
    fq_nmod_mpolyu_t A,
    fq_nmod_poly_t a,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong i;
    slong k;
    slong N = mpoly_words_per_exp(A->bits, ctx->minfo);
    fq_nmod_t c;
    fq_nmod_init(c, ctx->fqctx);

    fq_nmod_mpolyu_zero(A, ctx);
    k = 0;
    for (i = fq_nmod_poly_length(a, ctx->fqctx) - 1; i >= 0; i--)
    {
        fq_nmod_poly_get_coeff(c, a, i, ctx->fqctx);
        if (!fq_nmod_is_zero(c, ctx->fqctx))
        {
            fq_nmod_mpolyu_fit_length(A, k + 1, ctx);
            A->exps[k] = i;
            fq_nmod_mpoly_fit_length_reset_bits(A->coeffs + k, 1, A->bits, ctx);
            n_fq_set_fq_nmod((A->coeffs + k)->coeffs + 0, c, ctx->fqctx);
            (A->coeffs + k)->length = 1;
            mpoly_monomial_zero((A->coeffs + k)->exps + N*0, N);
            k++;
        }
    }
    A->length = k;

    fq_nmod_clear(c, ctx->fqctx);
}



/* store in each coefficient the evaluation of the corresponding monomial */
static void fq_nmod_mpoly_evalsk(
    fq_nmod_mpoly_t A,
    fq_nmod_mpoly_t B,
    slong entries,
    slong * offs,
    ulong * masks,
    fq_nmod_struct * powers,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx->fqctx);
    slong i, j;
    slong N;

    FLINT_ASSERT(A->bits == B->bits);
    fq_nmod_mpoly_fit_length(A, B->length, ctx);
    N = mpoly_words_per_exp(B->bits, ctx->minfo);
    for (i = 0; i < B->length; i++)
    {
        _n_fq_one(A->coeffs + d*i, d);

        for (j = 0; j < entries; j++)
        {
            if ((B->exps + N*i)[offs[j]] & masks[j])
            {
                n_fq_mul_fq_nmod(A->coeffs + d*i, A->coeffs + d*i, powers + j, ctx->fqctx);
            }
        }

        mpoly_monomial_set(A->exps + N*i, B->exps + N*i, N);
    }
    A->length = B->length;
}

static void fq_nmod_mpolyu_evalsk(
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    slong entries,
    slong * offs,
    ulong * masks,
    fq_nmod_struct * powers,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong i;

    fq_nmod_mpolyu_fit_length(A, B->length, ctx);
    for (i = 0; i < B->length; i++)
    {
        A->exps[i] = B->exps[i];
        fq_nmod_mpoly_evalsk(A->coeffs + i, B->coeffs + i,
                                            entries, offs, masks, powers, ctx);
    }
    A->length = B->length;
}


/* multiply the coefficients of A pointwise by those of B */
static void fq_nmod_mpolyu_mulsk(
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx->fqctx);
    slong i, j;

    FLINT_ASSERT(A->length == B->length);
    for (i = 0; i < A->length; i++)
    {
        FLINT_ASSERT(A->exps[i] == B->exps[i]);

        FLINT_ASSERT((A->coeffs + i)->length == (B->coeffs + i)->length);
        for (j = 0; j < (A->coeffs + i)->length; j++)
        {
            n_fq_mul((A->coeffs + i)->coeffs + d*j,
                            (A->coeffs + i)->coeffs + d*j,
                            (B->coeffs + i)->coeffs + d*j, ctx->fqctx);
        }
    }
}

/*
    return 0 if the leading coeff of A vanishes
    else return 1
*/
static int fq_nmod_mpolyu_evalfromsk(
    fq_nmod_poly_t e,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t SK,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx->fqctx);
    slong i, j;
    int ret = 0;
    ulong * pp, * acc;
    fq_nmod_t acct;

    FLINT_ASSERT(A->length == SK->length);

    pp = FLINT_ARRAY_ALLOC(d, ulong);
    acc = FLINT_ARRAY_ALLOC(d, ulong);
    fq_nmod_init(acct, ctx->fqctx);

    fq_nmod_poly_zero(e, ctx->fqctx);
    for (i = 0; i < A->length; i++)
    {
        FLINT_ASSERT((A->coeffs + i)->length == (SK->coeffs + i)->length);

        _n_fq_zero(acc, d);

        for (j = 0; j < A->coeffs[i].length; j++)
        {
            n_fq_mul(pp, A->coeffs[i].coeffs + d*j, SK->coeffs[i].coeffs + d*j, ctx->fqctx);
            n_fq_add(acc, acc, pp, ctx->fqctx);
        }

        n_fq_get_fq_nmod(acct, acc, ctx->fqctx);
        fq_nmod_poly_set_coeff(e, A->exps[i], acct, ctx->fqctx);
        ret |= (i == 0 && !fq_nmod_is_zero(acct, ctx->fqctx));
    }

    flint_free(acc);
    flint_free(pp);
    fq_nmod_clear(acct, ctx->fqctx);

    return ret;
}


void fq_nmod_poly_product_roots(fq_nmod_poly_t P, fq_nmod_struct * r,
                                            slong n, const fq_nmod_ctx_t fqctx)
{
    slong i;
    fq_nmod_poly_t B;
    fq_nmod_t a;
    fq_nmod_init(a, fqctx);
    fq_nmod_poly_init(B, fqctx);
    fq_nmod_poly_one(P, fqctx);
    fq_nmod_poly_gen(B, fqctx);
    for (i = 0; i < n; i++)
    {
        fq_nmod_neg(a, r + i, fqctx);
        fq_nmod_poly_set_coeff(B, 0, a, fqctx);
        fq_nmod_poly_mul(P, P, B, fqctx);
    }
    fq_nmod_clear(a, fqctx);
    fq_nmod_poly_clear(B, fqctx);
}

/*
    solve

    [ a[0]    a[1]    ... a[n-1]   ]   [ x[0]   ]    [ b[0]   ]
    [ a[0]^2  a[1]^2  ... a[n-1]^2 ]   [ x[1]   ]    [ b[1]   ]
    [  ...                 ...     ] . [ ...    ] =  [ ...    ]
    [ a[0]^n  a[1]^n  ... a[n-1]^n ]   [ x[n-1] ]    [ b[n-1] ]

    for x
*/
static int fq_nmod_vandsolve(ulong * X, ulong * A, fq_nmod_struct * b,
                                            slong n, const fq_nmod_ctx_t fqctx)
{
    slong d = fq_nmod_ctx_degree(fqctx);
    int success = 0;
    slong i, j;
    fq_nmod_t t, s;
    fq_nmod_t Dinv;
    fq_nmod_poly_t Q, P, R, u;
    fq_nmod_struct * x, * a;

    x = FLINT_ARRAY_ALLOC(n, fq_nmod_struct);
    a = FLINT_ARRAY_ALLOC(n, fq_nmod_struct);

    fq_nmod_init(t, fqctx);
    fq_nmod_init(s, fqctx);
    fq_nmod_init(Dinv, fqctx);

    for (i = 0; i < n; i++)
    {
        fq_nmod_init(a + i, fqctx);
        fq_nmod_init(x + i, fqctx);
        n_fq_get_fq_nmod(a + i, A + d*i, fqctx);
        fq_nmod_zero(x + i, fqctx);
    }

    fq_nmod_poly_init(Q, fqctx);
    fq_nmod_poly_init(P, fqctx);
    fq_nmod_poly_init(R, fqctx);
    fq_nmod_poly_init(u, fqctx);
    fq_nmod_poly_gen(u, fqctx);
    fq_nmod_poly_product_roots(P, a, n, fqctx);
    for (i = 0; i < n; i++)
    {
        if (fq_nmod_is_zero(a + i, fqctx))
            goto cleanup;

        fq_nmod_neg(t, a + i, fqctx);
        fq_nmod_poly_set_coeff(u, 0, t, fqctx);
        fq_nmod_poly_divrem(Q, R, P, u, fqctx);
        fq_nmod_poly_evaluate_fq_nmod(t, Q, a + i, fqctx);
        fq_nmod_mul(t, a + i, t, fqctx);
        if (fq_nmod_is_zero(t, fqctx))
            goto cleanup;

        fq_nmod_inv(Dinv, t, fqctx);
        for (j = 0; j < n; j++)
        {
            fq_nmod_mul(t, b + j, Dinv, fqctx);
            fq_nmod_poly_get_coeff(s, Q, j, fqctx);
            fq_nmod_mul(t, t, s, fqctx);
            fq_nmod_add(x + i, x + i, t, fqctx);
        }
    }
    success = 1;

cleanup:
    fq_nmod_poly_clear(Q, fqctx);
    fq_nmod_poly_clear(P, fqctx);
    fq_nmod_poly_clear(R, fqctx);
    fq_nmod_poly_clear(u, fqctx);

    fq_nmod_clear(t, fqctx);
    fq_nmod_clear(s, fqctx);
    fq_nmod_clear(Dinv, fqctx);

    for (i = 0; i < n; i++)
    {
        n_fq_set_fq_nmod(X + d*i, x + i, fqctx);
        fq_nmod_clear(a + i, fqctx);
        fq_nmod_clear(x + i, fqctx);
    }

    flint_free(a);
    flint_free(x);

    return success;
}

/*
    Try to set G to the gcd of A and B given the form f of G.
    return codes as enumerated in nmod_mpoly.h:

    nmod_mpoly_gcds_success,
    nmod_mpoly_gcds_form_wrong,
    nmod_mpoly_gcds_no_solution,
    nmod_mpoly_gcds_scales_not_found,
    nmod_mpoly_gcds_eval_point_not_found,
    nmod_mpoly_gcds_eval_gcd_deg_too_high
*/
nmod_gcds_ret_t fq_nmod_mpolyu_gcds_zippel(
    fq_nmod_mpolyu_t G,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    fq_nmod_mpolyu_t f,
    slong var,
    const fq_nmod_mpoly_ctx_t ctx,
    flint_rand_t randstate,
    slong * degbound)
{
    slong d = fq_nmod_ctx_degree(ctx->fqctx);
    int eval_points_tried;
    nmod_gcds_ret_t success;
    fq_nmod_mpolyu_t Aevalsk1, Bevalsk1, fevalsk1, Aevalski, Bevalski, fevalski;
    fq_nmod_poly_t Aeval, Beval, Geval;
    fq_nmod_struct * alpha, * b;
    fq_nmod_mat_struct * M, * ML;
    fq_nmod_mat_t MF, Msol;
    int lc_ok;
    int underdeterminedcount = 0;
    int exceededcount = 0;
    int * ML_is_initialized;
    slong i, j, k, s, S, nullity;
    slong * tlen;
    slong l;
    fq_nmod_struct * W;
    slong entries;
    slong * offs;
    ulong * masks;
    fq_nmod_struct * powers;
    fq_nmod_t ck;
    TMP_INIT;

    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(B->length > 0);
    FLINT_ASSERT(f->length > 0);

    FLINT_ASSERT(A->bits == B->bits);
    FLINT_ASSERT(A->bits == G->bits);
    FLINT_ASSERT(A->bits == f->bits);
    FLINT_ASSERT(var >= 0);

    FLINT_ASSERT(*degbound == f->exps[0]);

    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(A, ctx));
    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(B, ctx));

    FLINT_ASSERT(var > 0);

    if (f->length == 1)
    {
        if ((f->coeffs + 0)->length > 1)
        {
            /* impossible to find scale factors in this case */
            return nmod_gcds_scales_not_found;
        }
        else
        {
            /* otherwise set the coeff of the monomial to one */
            nmod_gcds_ret_t ret;
            FLINT_ASSERT((f->coeffs + 0)->length == 1);
            fq_nmod_mpolyu_set(G, f, ctx);
            _n_fq_one((G->coeffs + 0)->coeffs + d*0, d);
            fq_nmod_mpolyu_init(Aevalsk1, f->bits, ctx);
            ret = nmod_gcds_form_wrong;
            if (   fq_nmod_mpolyuu_divides(Aevalsk1, A, G, 1, ctx)
                && fq_nmod_mpolyuu_divides(Aevalsk1, B, G, 1, ctx))
            {
                ret = nmod_gcds_success;
            }
            fq_nmod_mpolyu_clear(Aevalsk1, ctx);
            return ret;
        }
    }

    TMP_START;

    fq_nmod_init(ck, ctx->fqctx);

    fq_nmod_mpolyu_init(Aevalsk1, f->bits, ctx);
    fq_nmod_mpolyu_init(Bevalsk1, f->bits, ctx);
    fq_nmod_mpolyu_init(fevalsk1, f->bits, ctx);
    fq_nmod_mpolyu_init(Aevalski, f->bits, ctx);
    fq_nmod_mpolyu_init(Bevalski, f->bits, ctx);
    fq_nmod_mpolyu_init(fevalski, f->bits, ctx);
    fq_nmod_poly_init(Aeval, ctx->fqctx);
    fq_nmod_poly_init(Beval, ctx->fqctx);
    fq_nmod_poly_init(Geval, ctx->fqctx);

    tlen = (slong *) TMP_ALLOC(f->length*sizeof(slong));
    for (i = 0; i < f->length; i++)
    {
        tlen[i] = i;
    }

    /*
        make tlen sort the coeffs so that
        (f->coeffs + tlen[j-1])->length <= (f->coeffs + tlen[j-0])->length
        for all j
    */
    for (i = 1; i<f->length; i++)
    {
        for (j=i; j > 0 && (f->coeffs + tlen[j-1])->length
                         > (f->coeffs + tlen[j-0])->length; j--)
        {
            slong temp = tlen[j-1];
            tlen[j-1] = tlen[j-0];
            tlen[j-0] = temp;
        }
    }

    /* l is the number of images we will try to construct */
    l = f->length - 3;
    for (i = 0; i < f->length; i++)
    {
        l += (f->coeffs + i)->length;
    }
    l = l / (f->length - 1);
    l = FLINT_MAX(l, (f->coeffs + tlen[f->length - 1])->length);
    /* one extra test image */
    l += 1;

    alpha = (fq_nmod_struct *) TMP_ALLOC(var*sizeof(fq_nmod_struct));
    ML = (fq_nmod_mat_struct *) TMP_ALLOC(f->length*sizeof(fq_nmod_mat_struct));
    b = (fq_nmod_struct *) TMP_ALLOC((f->coeffs + tlen[f->length - 1])->length
                                                      *sizeof(fq_nmod_struct));

    for (i = 0; i < var; i++)
        fq_nmod_init(alpha + i, ctx->fqctx);
    for (i = 0; i < (f->coeffs + tlen[f->length - 1])->length; i++)
        fq_nmod_init(b + i, ctx->fqctx);

    fq_nmod_mat_init(MF, 0, l, ctx->fqctx);

    M = (fq_nmod_mat_struct *) TMP_ALLOC(f->length*sizeof(fq_nmod_mat_struct));
    ML_is_initialized = (int *) TMP_ALLOC(f->length*sizeof(int));
    for (i = 0; i < f->length; i++)
    {
        fq_nmod_mat_init(M + i, l, (f->coeffs + i)->length, ctx->fqctx);
        ML_is_initialized[i] = 0;
    }

    W = (fq_nmod_struct *) flint_malloc(l*f->length*sizeof(fq_nmod_struct));
    for (i = 0; i < l*f->length; i++)
        fq_nmod_init(W + i, ctx->fqctx);


    fq_nmod_mat_init(Msol, l, 1, ctx->fqctx);

    /* compute how many masks are needed */
    entries = f->bits * var;
    offs = (slong *) TMP_ALLOC(entries*sizeof(slong));
    masks = (ulong *) TMP_ALLOC(entries*sizeof(slong));
    powers = (fq_nmod_struct *) TMP_ALLOC(entries*sizeof(fq_nmod_struct));
    for (i = 0; i < entries; i++)
        fq_nmod_init(powers + i, ctx->fqctx);


    /***** evaluation loop head *******/
    eval_points_tried = 0;
pick_evaluation_point:

    if (++eval_points_tried > 10)
    {
        success = nmod_gcds_eval_point_not_found;
        goto finished;
    }

    /* avoid 0 for the evaluation points */
    for (i = 0; i < var; i++)
        fq_nmod_randtest_not_zero(alpha + i, randstate, ctx->fqctx);

    /* store bit masks for each power of two of the non-main variables */
    for (i = 0; i < var; i++)
    {
        slong shift, off;
        mpoly_gen_offset_shift_sp(&off, &shift, i, f->bits, ctx->minfo);
        for (j = 0; (ulong) j < f->bits; j++)
        {
            offs[f->bits*i + j] = off;
            masks[f->bits*i + j] = UWORD(1) << (j + shift);
            if (j == 0)
                fq_nmod_set(powers + f->bits*i + j, alpha + i, ctx->fqctx);
            else
                fq_nmod_mul(powers + f->bits*i + j, powers + f->bits*i + j - 1,
                                                    powers + f->bits*i + j - 1, ctx->fqctx);
        }
    }

    fq_nmod_mpolyu_evalsk(Aevalsk1, A, entries, offs, masks, powers, ctx);
    fq_nmod_mpolyu_evalsk(Bevalsk1, B, entries, offs, masks, powers, ctx);
    fq_nmod_mpolyu_evalsk(fevalsk1, f, entries, offs, masks, powers, ctx);

    for (i = 0; i < l*f->length; i++)
    {
        fq_nmod_zero(W + i, ctx->fqctx);
    }


    for (i = 0; i < l; i++)
    {
        if (i == 0)
        {
            fq_nmod_mpolyu_set(Aevalski, Aevalsk1, ctx);
            fq_nmod_mpolyu_set(Bevalski, Bevalsk1, ctx);
            fq_nmod_mpolyu_set(fevalski, fevalsk1, ctx);
        } else
        {
            fq_nmod_mpolyu_mulsk(Aevalski, Aevalsk1, ctx);
            fq_nmod_mpolyu_mulsk(Bevalski, Bevalsk1, ctx);
            fq_nmod_mpolyu_mulsk(fevalski, fevalsk1, ctx);
        }

        for (j = 0; j < f->length; j++)
        {
            for (k = 0; k < (f->coeffs + j)->length; k++)
            {
                n_fq_get_fq_nmod(fq_nmod_mat_entry(M + j, i, k),
                               (fevalski->coeffs + j)->coeffs + d*k, ctx->fqctx);
            }
        }

        lc_ok = 1;
        lc_ok = lc_ok && fq_nmod_mpolyu_evalfromsk(Aeval, A, Aevalski, ctx);
        lc_ok = lc_ok && fq_nmod_mpolyu_evalfromsk(Beval, B, Bevalski, ctx);
        if (!lc_ok)
        {
            /* lc of A or B vanished */
            goto pick_evaluation_point;
        }

        fq_nmod_poly_gcd(Geval, Aeval, Beval, ctx->fqctx);

        if (f->exps[0] < (ulong) fq_nmod_poly_degree(Geval, ctx->fqctx))
        {
            ++exceededcount;
            if (exceededcount < 2)
                goto pick_evaluation_point;

            success = nmod_gcds_eval_gcd_deg_too_high;
            goto finished;
        }

        if (f->exps[0] > (ulong) fq_nmod_poly_degree(Geval, ctx->fqctx))
        {
            success = nmod_gcds_form_main_degree_too_high;
            *degbound = fq_nmod_poly_degree(Geval, ctx->fqctx);
            goto finished;
        }

        k = fq_nmod_poly_length(Geval, ctx->fqctx);
        j = WORD(0);
        while ((--k) >= 0)
        {
            fq_nmod_poly_get_coeff(ck, Geval, k, ctx->fqctx);
            if (!fq_nmod_is_zero(ck, ctx->fqctx))
            {
                while (j < f->length && f->exps[j] > (ulong) k)
                {
                    j++;
                }
                if (j >= f->length || f->exps[j] != (ulong) k)
                {
                    success = nmod_gcds_form_wrong;
                    goto finished;
                }
                fq_nmod_set(W + l*j + i, ck, ctx->fqctx);
            }
        }
    }

    nullity = -1;
    fq_nmod_mat_clear(MF, ctx->fqctx);
    fq_nmod_mat_init(MF, 0, l, ctx->fqctx);

    for (S = 0; S < f->length; S++)
    {
        s = tlen[S];

        if (!ML_is_initialized[s])
        {
            fq_nmod_mat_init(ML + s, l, (f->coeffs + s)->length + l, ctx->fqctx);
            ML_is_initialized[s] = 1;
            for (i = 0; i < l; i++)
            {
                for (j = 0; j < (f->coeffs + s)->length; j++)
                {
                    fq_nmod_set(fq_nmod_mat_entry(ML + s, i, j),
                                 fq_nmod_mat_entry(M + s, i, j), ctx->fqctx);
                }
                fq_nmod_set(fq_nmod_mat_entry(ML + s, i, (f->coeffs + s)->length + i),
                                                      W + l*s + i, ctx->fqctx);
            }
        } else {
            for (i = 0; i < l; i++)
            {
                for (j = 0; j < (f->coeffs + s)->length; j++)
                {
                    fq_nmod_set(fq_nmod_mat_entry(ML + s, i, j),
                                             fq_nmod_mat_entry(M + s, i, j), ctx->fqctx);
                }
                for (j = 0; j < l; j++) {
                    if (j == i)
                    {
                        fq_nmod_set(fq_nmod_mat_entry(ML + s, i, (f->coeffs + s)->length + j),
                                                      W + l*s + i, ctx->fqctx);
                    } else {
                        fq_nmod_zero(fq_nmod_mat_entry(ML + s, i, (f->coeffs + s)->length + j),
                                                                   ctx->fqctx);
                    }
                }
            }

        }
        fq_nmod_mat_rref(ML + s, ML + s, ctx->fqctx);

        for (i = 0; i < (f->coeffs + s)->length; i++)
        {
            if (!fq_nmod_is_one(fq_nmod_mat_entry(ML + s, i, i), ctx->fqctx))
            {
                /* evaluation points produced a singular vandermonde matrix */
                goto pick_evaluation_point;
            }
        }

        {
            /* appends rows to MF */
            fq_nmod_mat_t MFtemp;
            fq_nmod_mat_t Mwindow;

            fq_nmod_mat_window_init(Mwindow, ML + s,
                    (f->coeffs + s)->length, (f->coeffs + s)->length,
                     l, (f->coeffs + s)->length + l, ctx->fqctx);
            fq_nmod_mat_init(MFtemp,
                             fq_nmod_mat_nrows(MF, ctx->fqctx)
                                     + l - (f->coeffs + s)->length,
                             l, ctx->fqctx);
            fq_nmod_mat_concat_vertical(MFtemp, MF, Mwindow, ctx->fqctx);
            fq_nmod_mat_swap(MFtemp, MF, ctx->fqctx);
            fq_nmod_mat_clear(MFtemp, ctx->fqctx);
            fq_nmod_mat_window_clear(Mwindow, ctx->fqctx);
        }

        nullity = l - fq_nmod_mat_rref(MF, MF, ctx->fqctx);

        if (nullity == 0)
        {
            /* There is no solution for scale factors. Form f must be wrong */
            success = nmod_gcds_form_wrong;
            goto finished;
        }
        if (nullity == 1)
        {
            /*
                There is one solution for scale factors based on equations
                considered thus far. Accept this as a solution and perform
                checks of the remaining equations at the end.
            */
            break;
        }
    }

    if (nullity != 1)
    {
        ++underdeterminedcount;
        if (underdeterminedcount < 2)
            goto pick_evaluation_point;

        success = nmod_gcds_scales_not_found;
        goto finished;
    }

    nullity = fq_nmod_mat_nullspace(Msol, MF, ctx->fqctx);
    FLINT_ASSERT(nullity == 1);

    fq_nmod_mpolyu_setform(G, f, ctx);

    for (i = 0; i < f->length; i++)
    {
        for (j = 0; j < (f->coeffs + i)->length; j++)
        {
            FLINT_ASSERT((f->coeffs + i)->length <= l);
            FLINT_ASSERT(j < (f->coeffs + tlen[f->length - 1])->length);
            fq_nmod_mul(b + j, W + l*i + j, fq_nmod_mat_entry(Msol, j, 0), ctx->fqctx);
        }
        success = fq_nmod_vandsolve((G->coeffs + i)->coeffs,
                                 (fevalsk1->coeffs + i)->coeffs, b,
                                    (f->coeffs + i)->length, ctx->fqctx);
        if (!success)
        {
            /* evaluation points produced a singular vandermonde matrix */
            goto pick_evaluation_point;
        }
    }

    /* check solution */
    for (s = 0; s < f->length; s++)
    {
        fq_nmod_t acc, pp, u;
        fq_nmod_init(acc, ctx->fqctx);
        fq_nmod_init(pp, ctx->fqctx);
        fq_nmod_init(u, ctx->fqctx);

        for (i = 0; i < l; i++)
        {
            fq_nmod_zero(acc, ctx->fqctx);
            for (j = 0; j < (f->coeffs + s)->length; j++)
            {
                n_fq_get_fq_nmod(u, (G->coeffs + s)->coeffs + d*j, ctx->fqctx);
                fq_nmod_mul(pp, fq_nmod_mat_entry(M + s, i, j), u, ctx->fqctx);
                fq_nmod_add(acc, acc, pp, ctx->fqctx);
            }

            fq_nmod_mul(u, W + l*s + i, fq_nmod_mat_entry(Msol, i, 0), ctx->fqctx);
            if (!fq_nmod_equal(acc, u, ctx->fqctx))
            {
                fq_nmod_clear(acc, ctx->fqctx);
                fq_nmod_clear(pp, ctx->fqctx);
                fq_nmod_clear(u, ctx->fqctx);
                success = nmod_gcds_no_solution;
                goto finished;
            }
        }

        fq_nmod_clear(acc, ctx->fqctx);
        fq_nmod_clear(pp, ctx->fqctx);
        fq_nmod_clear(u, ctx->fqctx);
    }

    success = nmod_gcds_success;

finished:

    for (i = 0; i < entries; i++)
        fq_nmod_clear(powers + i, ctx->fqctx);

    for (i = 0; i < var; i++)
        fq_nmod_clear(alpha + i, ctx->fqctx);
    for (i = 0; i < (f->coeffs + tlen[f->length - 1])->length; i++)
        fq_nmod_clear(b + i, ctx->fqctx);
    for (i = 0; i < l*f->length; i++)
        fq_nmod_clear(W + i, ctx->fqctx);

    flint_free(W);
    fq_nmod_mat_clear(MF, ctx->fqctx);
    fq_nmod_mat_clear(Msol, ctx->fqctx);
    for (i = 0; i < f->length; i++)
    {
        fq_nmod_mat_clear(M + i, ctx->fqctx);
        if (ML_is_initialized[i])
        {
            fq_nmod_mat_clear(ML + i, ctx->fqctx);
        }
    }

    fq_nmod_clear(ck, ctx->fqctx);

    fq_nmod_mpolyu_clear(Aevalsk1, ctx);
    fq_nmod_mpolyu_clear(Bevalsk1, ctx);
    fq_nmod_mpolyu_clear(fevalsk1, ctx);
    fq_nmod_mpolyu_clear(Aevalski, ctx);
    fq_nmod_mpolyu_clear(Bevalski, ctx);
    fq_nmod_mpolyu_clear(fevalski, ctx);
    fq_nmod_poly_clear(Aeval, ctx->fqctx);
    fq_nmod_poly_clear(Beval, ctx->fqctx);
    fq_nmod_poly_clear(Geval, ctx->fqctx);

    TMP_END;
    return success;
}


/* setform copies the exponents and zeros the coefficients */
static void fq_nmod_mpoly_setform_mpolyn(
    fq_nmod_mpoly_t A,
    fq_nmod_mpolyn_t B,
    const fq_nmod_mpoly_ctx_t ctx)
{
    slong d = fq_nmod_ctx_degree(ctx->fqctx);
    slong i;
    slong N;

    FLINT_ASSERT(A->bits == B->bits);

    fq_nmod_mpoly_fit_length(A, B->length, ctx);
    N = mpoly_words_per_exp(B->bits, ctx->minfo);
    for (i = 0; i < B->length; i++)
    {
        _n_fq_zero(A->coeffs + d*i, d);
        mpoly_monomial_set(A->exps + N*i, B->exps + N*i, N);
    }
    A->length = B->length;
}

static void fq_nmod_mpolyu_setform_mpolyun(fq_nmod_mpolyu_t A, fq_nmod_mpolyun_t B,
                                                 const fq_nmod_mpoly_ctx_t ctx)
{
    slong i;

    fq_nmod_mpolyu_fit_length(A, B->length, ctx);
    for (i = 0; i < B->length; i++)
    {
        FLINT_ASSERT((B->coeffs + i)->bits == B->bits);
        fq_nmod_mpoly_setform_mpolyn(A->coeffs + i, B->coeffs + i, ctx);
        A->exps[i] = B->exps[i];
    }
    A->length = B->length;
}


int fq_nmod_mpolyu_gcdp_zippel_univar(
    fq_nmod_mpolyu_t G,
    fq_nmod_mpolyu_t Abar,
    fq_nmod_mpolyu_t Bbar,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    const fq_nmod_mpoly_ctx_t ctx)
{
    fq_nmod_poly_t a, b, g, t, r;
    FLINT_ASSERT(A->bits == B->bits);
    fq_nmod_poly_init(a, ctx->fqctx);
    fq_nmod_poly_init(b, ctx->fqctx);
    fq_nmod_poly_init(g, ctx->fqctx);
    fq_nmod_poly_init(t, ctx->fqctx);
    fq_nmod_poly_init(r, ctx->fqctx);
    fq_nmod_mpolyu_cvtto_poly(a, A, ctx);
    fq_nmod_mpolyu_cvtto_poly(b, B, ctx);
    fq_nmod_poly_gcd(g, a, b, ctx->fqctx);
    fq_nmod_mpolyu_cvtfrom_poly(G, g, ctx);
    fq_nmod_poly_divrem(t, r, a, g, ctx->fqctx);
    fq_nmod_mpolyu_cvtfrom_poly(Abar, t, ctx);
    fq_nmod_poly_divrem(t, r, b, g, ctx->fqctx);
    fq_nmod_mpolyu_cvtfrom_poly(Bbar, t, ctx);
    fq_nmod_poly_clear(a, ctx->fqctx);
    fq_nmod_poly_clear(b, ctx->fqctx);
    fq_nmod_poly_clear(g, ctx->fqctx);
    fq_nmod_poly_clear(t, ctx->fqctx);
    fq_nmod_poly_clear(r, ctx->fqctx);
    return 1;
}

int fq_nmod_mpolyu_gcdp_zippel_univar_no_cofactors(
    fq_nmod_mpolyu_t G,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    const fq_nmod_mpoly_ctx_t ctx)
{
    fq_nmod_poly_t a, b, g;
    FLINT_ASSERT(A->bits == B->bits);

    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(A,ctx));
    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(B,ctx));

    fq_nmod_poly_init(a, ctx->fqctx);
    fq_nmod_poly_init(b, ctx->fqctx);
    fq_nmod_poly_init(g, ctx->fqctx);
    fq_nmod_mpolyu_cvtto_poly(a, A, ctx);
    fq_nmod_mpolyu_cvtto_poly(b, B, ctx);

    fq_nmod_poly_gcd(g, a, b, ctx->fqctx);

    fq_nmod_mpolyu_cvtfrom_poly(G, g, ctx);
    fq_nmod_poly_clear(a, ctx->fqctx);
    fq_nmod_poly_clear(b, ctx->fqctx);
    fq_nmod_poly_clear(g, ctx->fqctx);
    return 1;
}


static int fq_nmod_mpolyu_gcdp_zippel_bivar(
    fq_nmod_mpolyu_t G,
    fq_nmod_mpolyu_t Abar,
    fq_nmod_mpolyu_t Bbar,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    const fq_nmod_mpoly_ctx_t ctx,
    flint_rand_t randstate)
{
    slong var = 0;
    slong Alastdeg;
    slong Blastdeg;
    ulong Ashift, Bshift, Gshift;
    slong lastdeg;
    slong bound;
    int success = 0, changed, have_enough;
    fq_nmod_poly_t a, b, c, g, modulus, tempmod, tmp1, tmp2;
    fq_nmod_mpolyu_t Aeval, Beval, Geval;
    fq_nmod_mpolyun_t An, Bn, H, Ht;
    fq_nmod_t geval, temp, alpha, alphastart;
    fmpz_t minusone;

    FLINT_ASSERT(ctx->minfo->ord == ORD_LEX);
    FLINT_ASSERT(var >= -WORD(1));

    FLINT_ASSERT(G->bits == A->bits);
    FLINT_ASSERT(G->bits == B->bits);

    fmpz_init(minusone);
    fmpz_set_si(minusone, -WORD(1));

    fq_nmod_mpolyun_init(An, A->bits, ctx);
    fq_nmod_mpolyun_init(Bn, A->bits, ctx);
    fq_nmod_mpolyu_cvtto_mpolyun(An, A, var, ctx);
    fq_nmod_mpolyu_cvtto_mpolyun(Bn, B, var, ctx);

    FLINT_ASSERT(An->length > 0);
    FLINT_ASSERT(Bn->length > 0);

    Ashift = A->exps[A->length - 1];
    Bshift = B->exps[B->length - 1];
    Gshift = FLINT_MIN(Ashift, Bshift);
    fq_nmod_mpolyun_shift_right(An, Ashift);
    fq_nmod_mpolyun_shift_right(Bn, Bshift);

    fq_nmod_poly_init(a, ctx->fqctx);
    fq_nmod_poly_init(b, ctx->fqctx);
    fq_nmod_poly_init(c, ctx->fqctx);
    fq_nmod_poly_init(g, ctx->fqctx);
    fq_nmod_poly_init(tmp1, ctx->fqctx);
    fq_nmod_poly_init(tmp2, ctx->fqctx);

    /* if the gcd has content wrt last variable, we are going to fail */
    fq_nmod_mpolyun_content_poly(a, An, ctx);
    fq_nmod_mpolyun_content_poly(b, Bn, ctx);
    fq_nmod_mpolyun_divexact_poly(An, An, a, ctx);
    fq_nmod_mpolyun_divexact_poly(Bn, Bn, b, ctx);
    fq_nmod_poly_gcd(c, a, b, ctx->fqctx);

    n_fq_poly_get_fq_nmod_poly(tmp1, fq_nmod_mpolyun_leadcoeff_poly(An, ctx), ctx->fqctx);
    n_fq_poly_get_fq_nmod_poly(tmp2, fq_nmod_mpolyun_leadcoeff_poly(Bn, ctx), ctx->fqctx);
    fq_nmod_poly_gcd(g, tmp1, tmp2, ctx->fqctx);

    Alastdeg = fq_nmod_mpolyun_lastdeg(An, ctx);
    Blastdeg = fq_nmod_mpolyun_lastdeg(Bn, ctx);

    /* bound of the number of images required */
    bound = 1 + FLINT_MIN(Alastdeg, Blastdeg)
              + fq_nmod_poly_degree(g, ctx->fqctx);

    fq_nmod_poly_init(modulus, ctx->fqctx);
    fq_nmod_poly_init(tempmod, ctx->fqctx);
    fq_nmod_poly_set_coeff_fmpz(tempmod, 1, minusone, ctx->fqctx);
    fq_nmod_mpolyu_init(Aeval, A->bits, ctx);
    fq_nmod_mpolyu_init(Beval, A->bits, ctx);
    fq_nmod_mpolyu_init(Geval, A->bits, ctx);
    fq_nmod_mpolyun_init(H, A->bits, ctx);
    fq_nmod_mpolyun_init(Ht, A->bits, ctx);

    fq_nmod_init(temp, ctx->fqctx);
    fq_nmod_init(geval, ctx->fqctx);
    fq_nmod_init(alpha, ctx->fqctx);
    fq_nmod_init(alphastart, ctx->fqctx);

    /* fail if the gcd has content wrt last variable */
    if (fq_nmod_poly_degree(c, ctx->fqctx) > 0)
    {
        success = 0;
        goto finished;
    }

    fq_nmod_poly_one(modulus, ctx->fqctx);
    fq_nmod_mpolyun_zero(H, ctx);

    fq_nmod_randtest_not_zero(alphastart, randstate, ctx->fqctx);
    fq_nmod_set(alpha, alphastart, ctx->fqctx);

    while (1)
    {
        /* get new evaluation point */
        fq_nmod_next_not_zero(alpha, ctx->fqctx);

        if (fq_nmod_equal(alpha, alphastart, ctx->fqctx))
        {
            success = 0;
            goto finished;
        }

        /* make sure evaluation point does not kill both lc(A) and lc(B) */
        fq_nmod_poly_evaluate_fq_nmod(geval, g, alpha, ctx->fqctx);

        if (fq_nmod_is_zero(geval, ctx->fqctx))
            goto outer_continue;

        /* make sure evaluation point does not kill either A or B */
        fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Aeval, An, alpha, ctx);
        fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Beval, Bn, alpha, ctx);

        FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(Aeval, ctx));
        FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(Beval, ctx));

        if (Aeval->length == 0 || Beval->length == 0)
            goto outer_continue;

        fq_nmod_mpolyu_gcdp_zippel_univar_no_cofactors(Geval, Aeval, Beval, ctx);

        if (fq_nmod_mpolyu_is_one(Geval, ctx))
        {
            fq_nmod_mpolyu_one(G, ctx);
            fq_nmod_mpolyu_swap(Abar, A, ctx);
            fq_nmod_mpolyu_swap(Bbar, B, ctx);
            fq_nmod_mpolyu_shift_left(G, Gshift);
            fq_nmod_mpolyu_shift_left(Abar, Ashift - Gshift);
            fq_nmod_mpolyu_shift_left(Bbar, Bshift - Gshift);
            success = 1;
            goto finished;
        }

        FLINT_ASSERT(Geval->length > 0);

        if (fq_nmod_poly_degree(modulus, ctx->fqctx) > 0)
        {
            if (Geval->exps[0] > H->exps[0])
            {
                goto outer_continue;
            }
            else if (Geval->exps[0] < H->exps[0])
            {
                fq_nmod_poly_one(modulus, ctx->fqctx);
            }
        }

        /* update interpolant H */
        n_fq_get_fq_nmod(temp, fq_nmod_mpolyu_leadcoeff(Geval, ctx), ctx->fqctx);
        fq_nmod_inv(temp, temp, ctx->fqctx);
        fq_nmod_mul(temp, geval, temp, ctx->fqctx);
        fq_nmod_mpolyu_scalar_mul_fq_nmod(Geval, temp, ctx);

        if (fq_nmod_poly_degree(modulus, ctx->fqctx) > 0)
        {
            fq_nmod_poly_evaluate_fq_nmod(temp, modulus, alpha, ctx->fqctx);
            fq_nmod_inv(temp, temp, ctx->fqctx);
            fq_nmod_poly_scalar_mul_fq_nmod(modulus, modulus, temp, ctx->fqctx);
            changed = fq_nmod_mpolyun_interp_crt_sm_mpolyu(&lastdeg, H, Ht, Geval,
                                                          modulus, alpha, ctx);
            fq_nmod_poly_set_coeff(tempmod, 0, alpha, ctx->fqctx);
            fq_nmod_poly_mul(modulus, modulus, tempmod, ctx->fqctx);

            have_enough = fq_nmod_poly_degree(modulus, ctx->fqctx) >= bound;

            if (changed && !have_enough)
            {
                goto outer_continue;
            }

            if (!changed || have_enough)
            {
                fq_nmod_mpolyun_content_poly(a, H, ctx);
                fq_nmod_mpolyun_divexact_poly(Ht, H, a, ctx);
                fq_nmod_mpolyun_shift_left(Ht, Gshift);
                fq_nmod_mpolyu_cvtfrom_mpolyun(G, Ht, var, ctx);

                if (    fq_nmod_mpolyuu_divides(Abar, A, G, 1, ctx)
                     && fq_nmod_mpolyuu_divides(Bbar, B, G, 1, ctx))
                {
                    success = 1;
                    goto finished;
                }
            }

            if (have_enough)
            {
                fq_nmod_poly_one(modulus, ctx->fqctx);
                goto outer_continue;
            }
        }
        else
        {
            fq_nmod_mpolyun_interp_lift_sm_mpolyu(H, Geval, ctx);
            lastdeg = WORD(0);
            fq_nmod_poly_set_coeff(tempmod, 0, alpha, ctx->fqctx);
            fq_nmod_poly_mul(modulus, modulus, tempmod, ctx->fqctx);
        }

outer_continue:;
    }

    success = 0;

finished:

    fmpz_clear(minusone);
    fq_nmod_clear(temp, ctx->fqctx);
    fq_nmod_clear(geval, ctx->fqctx);
    fq_nmod_clear(alpha, ctx->fqctx);
    fq_nmod_clear(alphastart, ctx->fqctx);
    fq_nmod_poly_clear(a, ctx->fqctx);
    fq_nmod_poly_clear(b, ctx->fqctx);
    fq_nmod_poly_clear(c, ctx->fqctx);
    fq_nmod_poly_clear(g, ctx->fqctx);
    fq_nmod_poly_clear(tmp1, ctx->fqctx);
    fq_nmod_poly_clear(tmp2, ctx->fqctx);
    fq_nmod_poly_clear(modulus, ctx->fqctx);
    fq_nmod_poly_clear(tempmod, ctx->fqctx);
    fq_nmod_mpolyu_clear(Aeval, ctx);
    fq_nmod_mpolyu_clear(Beval, ctx);
    fq_nmod_mpolyu_clear(Geval, ctx);
    fq_nmod_mpolyun_clear(An, ctx);
    fq_nmod_mpolyun_clear(Bn, ctx);
    fq_nmod_mpolyun_clear(H, ctx);
    fq_nmod_mpolyun_clear(Ht, ctx);

    return success;
}



int fq_nmod_mpolyu_gcdp_zippel(
    fq_nmod_mpolyu_t G,
    fq_nmod_mpolyu_t Abar,
    fq_nmod_mpolyu_t Bbar,
    fq_nmod_mpolyu_t A,
    fq_nmod_mpolyu_t B,
    slong var,
    const fq_nmod_mpoly_ctx_t ctx,
    flint_rand_t randstate)
{
    slong lastdeg;
    slong Alastdeg;
    slong Blastdeg;
    ulong Ashift, Bshift, Gshift;
    slong degbound;
    slong bound;
    int success = 0, changed, have_enough;
    fq_nmod_mpolyun_t An, Bn;
    fq_nmod_poly_t a, b, c, g, tmp1, tmp2;
    fq_nmod_poly_t modulus, tempmod;
    fq_nmod_mpolyu_t Aeval, Beval, Geval, Abareval, Bbareval, Gform;
    fq_nmod_mpolyun_t H, Ht;
    fq_nmod_t geval, temp;
    fq_nmod_t alpha, alphastart;
    fmpz_t minusone;

    FLINT_ASSERT(ctx->minfo->ord == ORD_LEX);
    FLINT_ASSERT(var >= -WORD(1));

    FLINT_ASSERT(G->bits == A->bits);
    FLINT_ASSERT(G->bits == B->bits);
    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(B->length > 0);

    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(A,ctx));
    FLINT_ASSERT(fq_nmod_mpolyu_is_canonical(B,ctx));

    if (var == -WORD(1))
    {
        /* no more variables left to interpolate */
        return fq_nmod_mpolyu_gcdp_zippel_univar(G, Abar, Bbar, A, B, ctx);
    }

    if (var == WORD(0))
    {
        /* bivariate is more comfortable separated */
        return fq_nmod_mpolyu_gcdp_zippel_bivar(G, Abar, Bbar, A, B, ctx, randstate);
    }

    fq_nmod_mpolyun_init(An, A->bits, ctx);
    fq_nmod_mpolyun_init(Bn, A->bits, ctx);
    fq_nmod_mpolyu_cvtto_mpolyun(An, A, var, ctx);
    fq_nmod_mpolyu_cvtto_mpolyun(Bn, B, var, ctx);

    Ashift = A->exps[A->length - 1];
    Bshift = B->exps[B->length - 1];
    Gshift = FLINT_MIN(Ashift, Bshift);
    fq_nmod_mpolyun_shift_right(An, Ashift);
    fq_nmod_mpolyun_shift_right(Bn, Bshift);

    fq_nmod_poly_init(a, ctx->fqctx);
    fq_nmod_poly_init(b, ctx->fqctx);
    fq_nmod_poly_init(c, ctx->fqctx);
    fq_nmod_poly_init(g, ctx->fqctx);
    fq_nmod_poly_init(tmp1, ctx->fqctx);
    fq_nmod_poly_init(tmp2, ctx->fqctx);

    /* if the gcd has content wrt last variable, we are going to fail */
    fq_nmod_mpolyun_content_poly(a, An, ctx);
    fq_nmod_mpolyun_content_poly(b, Bn, ctx);
    fq_nmod_mpolyun_divexact_poly(An, An, a, ctx);
    fq_nmod_mpolyun_divexact_poly(Bn, Bn, b, ctx);
    fq_nmod_poly_gcd(c, a, b, ctx->fqctx);

    n_fq_poly_get_fq_nmod_poly(tmp1, fq_nmod_mpolyun_leadcoeff_poly(An, ctx), ctx->fqctx);
    n_fq_poly_get_fq_nmod_poly(tmp2, fq_nmod_mpolyun_leadcoeff_poly(Bn, ctx), ctx->fqctx);
    fq_nmod_poly_gcd(g, tmp1, tmp2, ctx->fqctx);

    Alastdeg = fq_nmod_mpolyun_lastdeg(An, ctx);
    Blastdeg = fq_nmod_mpolyun_lastdeg(Bn, ctx);

    /* bound of the number of images required */
    bound = 1 + FLINT_MIN(Alastdeg, Blastdeg)
              + fq_nmod_poly_degree(g, ctx->fqctx);

    /* degree bound on the gcd */
    degbound = FLINT_MIN(A->exps[0], B->exps[0]);

    fq_nmod_poly_init(modulus, ctx->fqctx);
    fq_nmod_poly_init(tempmod, ctx->fqctx);
    fmpz_init(minusone);
    fmpz_set_si(minusone, WORD(-1));
    fq_nmod_poly_set_coeff_fmpz(tempmod, 1, minusone, ctx->fqctx);
    fq_nmod_mpolyu_init(Aeval, A->bits, ctx);
    fq_nmod_mpolyu_init(Beval, A->bits, ctx);
    fq_nmod_mpolyu_init(Geval, A->bits, ctx);
    fq_nmod_mpolyu_init(Abareval, A->bits, ctx);
    fq_nmod_mpolyu_init(Bbareval, A->bits, ctx);
    fq_nmod_mpolyu_init(Gform, A->bits, ctx);
    fq_nmod_mpolyun_init(H, A->bits, ctx);
    fq_nmod_mpolyun_init(Ht, A->bits, ctx);

    fq_nmod_init(geval, ctx->fqctx);
    fq_nmod_init(temp, ctx->fqctx);
    fq_nmod_init(alpha, ctx->fqctx);
    fq_nmod_init(alphastart, ctx->fqctx);

    /* fail if the gcd has content wrt last variable */
    if (fq_nmod_poly_degree(c, ctx->fqctx) > 0)
    {
        success = 0;
        goto finished;
    }

    /* we don't expect this function to work over F_p */
    if (nmod_poly_degree(ctx->fqctx->modulus) < WORD(2))
    {
        success = 0;
        goto finished;
    }

    fq_nmod_poly_one(modulus, ctx->fqctx);
    fq_nmod_mpolyun_zero(H, ctx);

    fq_nmod_randtest_not_zero(alphastart, randstate, ctx->fqctx);
    fq_nmod_set(alpha, alphastart, ctx->fqctx);

    while (1)
    {
        /* get new evaluation point */
        fq_nmod_next_not_zero(alpha, ctx->fqctx);
        if (fq_nmod_equal(alpha, alphastart, ctx->fqctx))
        {
            success = 0;
            goto finished;
        }

        /* make sure evaluation point does not kill both lc(A) and lc(B) */
        fq_nmod_poly_evaluate_fq_nmod(geval, g, alpha, ctx->fqctx);
        if (fq_nmod_is_zero(geval, ctx->fqctx))
        {
            goto outer_continue;
        }

        /* make sure evaluation point does not kill either A or B */
        fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Aeval, An, alpha, ctx);
        fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Beval, Bn, alpha, ctx);
        if (Aeval->length == 0 || Beval->length == 0)
        {
            goto outer_continue;
        }

        success = fq_nmod_mpolyu_gcdp_zippel(Geval, Abareval, Bbareval,
                                        Aeval, Beval, var - 1, ctx, randstate);
        if (!success || Geval->exps[0] > (ulong) degbound)
        {
            success = 0;
            goto finished;
        }

        degbound = Geval->exps[0];

        if (fq_nmod_mpolyu_is_one(Geval, ctx))
        {
            fq_nmod_mpolyu_one(G, ctx);
            fq_nmod_mpolyu_swap(Abar, A, ctx);
            fq_nmod_mpolyu_swap(Bbar, B, ctx);
            fq_nmod_mpolyu_shift_left(G, Gshift);
            fq_nmod_mpolyu_shift_left(Abar, Ashift - Gshift);
            fq_nmod_mpolyu_shift_left(Bbar, Bshift - Gshift);
            success = 1;
            goto finished;
        }

        if (fq_nmod_poly_degree(modulus, ctx->fqctx) > 0)
        {
            if (Geval->exps[0] > H->exps[0])
            {
                goto outer_continue;
            }
            else if (Geval->exps[0] < H->exps[0])
            {
                fq_nmod_poly_one(modulus, ctx->fqctx);
            }
        }

        /* update interpolant H */
        n_fq_get_fq_nmod(temp, fq_nmod_mpolyu_leadcoeff(Geval, ctx), ctx->fqctx);
        fq_nmod_inv(temp, temp, ctx->fqctx);
        fq_nmod_mul(temp, geval, temp, ctx->fqctx);
        fq_nmod_mpolyu_scalar_mul_fq_nmod(Geval, temp, ctx);
        if (fq_nmod_poly_degree(modulus, ctx->fqctx) > 0)
        {
            fq_nmod_poly_evaluate_fq_nmod(temp, modulus, alpha, ctx->fqctx);
            fq_nmod_inv(temp, temp, ctx->fqctx);
            fq_nmod_poly_scalar_mul_fq_nmod(modulus, modulus, temp, ctx->fqctx);
            changed = fq_nmod_mpolyun_interp_crt_sm_mpolyu(&lastdeg, H, Ht, Geval,
                                                          modulus, alpha, ctx);
            if (!changed)
            {
                fq_nmod_mpolyun_content_poly(a, H, ctx);
                fq_nmod_mpolyun_divexact_poly(Ht, H, a, ctx);
                fq_nmod_mpolyun_shift_left(Ht, Gshift);
                fq_nmod_mpolyu_cvtfrom_mpolyun(G, Ht, var, ctx);
                if (   !fq_nmod_mpolyuu_divides(Abar, A, G, 1, ctx)
                    || !fq_nmod_mpolyuu_divides(Bbar, B, G, 1, ctx))
                {
                    goto outer_continue;
                }
                success = 1;
                goto finished;
            }
        }
        else
        {
            fq_nmod_mpolyun_interp_lift_sm_mpolyu(H, Geval, ctx);
        }
        fq_nmod_poly_set_coeff(tempmod, 0, alpha, ctx->fqctx);
        fq_nmod_poly_mul(modulus, modulus, tempmod, ctx->fqctx);

        fq_nmod_mpolyu_setform_mpolyun(Gform, H, ctx);

        while (1)
        {
            /* get new evaluation point */
            fq_nmod_next_not_zero(alpha, ctx->fqctx);
            if (fq_nmod_equal(alpha, alphastart, ctx->fqctx))
            {
                success = 0;
                goto finished;
            }

            /* make sure evaluation point does not kill both lc(A) and lc(B) */
            fq_nmod_poly_evaluate_fq_nmod(geval, g, alpha, ctx->fqctx);
            if (fq_nmod_is_zero(geval, ctx->fqctx))
            {
                goto inner_continue;
            }

            /* make sure evaluation point does not kill either A or B */
            fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Aeval, An, alpha, ctx);
            fq_nmod_mpolyun_interp_reduce_sm_mpolyu(Beval, Bn, alpha, ctx);
            if (Aeval->length == 0 || Beval->length == 0)
            {
                goto inner_continue;
            }

            switch (fq_nmod_mpolyu_gcds_zippel(Geval, Aeval, Beval, Gform, var,
                                                    ctx, randstate, &degbound))
            {
                default:
                    FLINT_ASSERT(0);
                case nmod_gcds_form_main_degree_too_high:
                    /* fq_nmod_mpolyu_gcds_zippel has updated degbound */
                    fq_nmod_poly_one(modulus, ctx->fqctx);
                    goto outer_continue;
                case nmod_gcds_form_wrong:
                case nmod_gcds_no_solution:
                    success = 0;
                    goto finished;
                case nmod_gcds_scales_not_found:
                case nmod_gcds_eval_point_not_found:
                case nmod_gcds_eval_gcd_deg_too_high:
                    goto inner_continue;
                case nmod_gcds_success:
                    (void)(NULL);
            }

            n_fq_get_fq_nmod(temp, fq_nmod_mpolyu_leadcoeff(Geval, ctx), ctx->fqctx);
            if (fq_nmod_is_zero(temp, ctx->fqctx))
            {
                goto inner_continue;
            }

            /* update interpolant H */
            FLINT_ASSERT(fq_nmod_poly_degree(modulus, ctx->fqctx) > 0);

            fq_nmod_inv(temp, temp, ctx->fqctx);
            fq_nmod_mul(temp, temp, geval, ctx->fqctx);
            fq_nmod_mpolyu_scalar_mul_fq_nmod(Geval, temp, ctx);

            fq_nmod_poly_evaluate_fq_nmod(temp, modulus, alpha, ctx->fqctx);
            fq_nmod_inv(temp, temp, ctx->fqctx);
            fq_nmod_poly_scalar_mul_fq_nmod(modulus, modulus, temp, ctx->fqctx);
            changed = fq_nmod_mpolyun_interp_crt_sm_mpolyu(&lastdeg, H, Ht, Geval, modulus,
                                                                   alpha, ctx);

            fq_nmod_poly_set_coeff(tempmod, 0, alpha, ctx->fqctx);
            fq_nmod_poly_mul(modulus, modulus, tempmod, ctx->fqctx);

            have_enough = fq_nmod_poly_degree(modulus, ctx->fqctx) >= bound;

            if (changed && !have_enough)
            {
                goto inner_continue;
            }

            if (!changed || have_enough)
            {
                fq_nmod_mpolyun_content_poly(a, H, ctx);
                fq_nmod_mpolyun_divexact_poly(Ht, H, a, ctx);
                fq_nmod_mpolyun_shift_left(Ht, Gshift);
                fq_nmod_mpolyu_cvtfrom_mpolyun(G, Ht, var, ctx);
                if (    fq_nmod_mpolyuu_divides(Abar, A, G, 1, ctx)
                     && fq_nmod_mpolyuu_divides(Bbar, B, G, 1, ctx))
                {
                    success = 1;
                    goto finished;
                }
            }

            if (have_enough)
            {
                fq_nmod_poly_one(modulus, ctx->fqctx);
                goto outer_continue;
            }

inner_continue:;
        }
        FLINT_ASSERT(0 && "not reachable");

outer_continue:;
    }
    FLINT_ASSERT(0 && "not reachable");


finished:

    fmpz_clear(minusone);
    fq_nmod_clear(geval, ctx->fqctx);
    fq_nmod_clear(temp, ctx->fqctx);
    fq_nmod_clear(alpha, ctx->fqctx);
    fq_nmod_clear(alphastart, ctx->fqctx);
    fq_nmod_poly_clear(a, ctx->fqctx);
    fq_nmod_poly_clear(b, ctx->fqctx);
    fq_nmod_poly_clear(c, ctx->fqctx);
    fq_nmod_poly_clear(g, ctx->fqctx);
    fq_nmod_poly_clear(tmp1, ctx->fqctx);
    fq_nmod_poly_clear(tmp2, ctx->fqctx);
    fq_nmod_poly_clear(modulus, ctx->fqctx);
    fq_nmod_poly_clear(tempmod, ctx->fqctx);
    fq_nmod_mpolyu_clear(Aeval, ctx);
    fq_nmod_mpolyu_clear(Beval, ctx);
    fq_nmod_mpolyu_clear(Geval, ctx);
    fq_nmod_mpolyu_clear(Abareval, ctx);
    fq_nmod_mpolyu_clear(Bbareval, ctx);
    fq_nmod_mpolyu_clear(Gform, ctx);
    fq_nmod_mpolyun_clear(An, ctx);
    fq_nmod_mpolyun_clear(Bn, ctx);
    fq_nmod_mpolyun_clear(H, ctx);
    fq_nmod_mpolyun_clear(Ht, ctx);

    return success;
}
