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

#include "fmpz_mod_vec.h"
#include "fmpz_mod_mpoly_factor.h"

/* set up mock to point to the coefficients of A, which are not owned by mock */
static void nmod_mpoly_mock_eval_coeff(
    n_polyun_t mock,
    const nmod_mpoly_t A,
    const n_polyun_t Aeh_inc,
    const nmod_mpoly_ctx_t ctx)
{
    slong i, k;

    if (mock->alloc < Aeh_inc->length)
    {
        mock->alloc = FLINT_MAX(Aeh_inc->length, mock->alloc + mock->alloc/2);
        mock->coeffs = FLINT_ARRAY_REALLOC(mock->coeffs, mock->alloc,
                                                                n_poly_struct);        
    }

    mock->length = Aeh_inc->length;

    k = 0;
    for (i = 0; i < Aeh_inc->length; i++)
    {
        slong l = Aeh_inc->coeffs[i].length;
        mock->coeffs[i].coeffs = A->coeffs + k;
        mock->coeffs[i].alloc = l;
        mock->coeffs[i].length = l;
        k += l;
    }

    FLINT_ASSERT(k == A->length);
}

static void nmod_mpoly_monomial_evals1(
    n_polyun_t E,
    const nmod_mpoly_t A, const ulong * Amarks, slong Amarkslen,
    n_poly_struct * betas,
    slong m,
    const nmod_mpoly_ctx_t ctx)
{
    mpoly1_monomial_evals_nmod(E, A->exps, A->bits, Amarks,
                                Amarkslen, betas, m, ctx->minfo, ctx->mod);
}

static void n_polyu1n_mod_zip_eval_cur_inc_coeff(
    n_poly_t E,
    n_polyun_t Acur,
    const n_polyun_t Ainc,
    const n_polyun_t Acoeff,
    const nmod_t ctx)
{
    slong i;
    mp_limb_t c;

    FLINT_ASSERT(Acur->length > 0);
    FLINT_ASSERT(Acur->length == Ainc->length);
    FLINT_ASSERT(Acur->length == Acoeff->length);

    n_poly_zero(E);

    for (i = 0; i < Acur->length; i++)
    {
        slong this_len = Acur->coeffs[i].length;
        FLINT_ASSERT(this_len == Ainc->coeffs[i].length);
        FLINT_ASSERT(this_len == Acoeff->coeffs[i].length);
        c = _nmod_zip_eval_step(Acur->coeffs[i].coeffs, Ainc->coeffs[i].coeffs,
                                Acoeff->coeffs[i].coeffs, this_len, ctx);
        n_poly_set_coeff(E, Acur->exps[i], c);
    }
}

static int n_poly_add_zip_must_match(
    n_polyun_t Z,
    n_poly_t A,
    slong cur_length)
{
    slong i, ai;
    slong Alen = A->length;
    ulong * Zexps = Z->exps;
    n_poly_struct * Zcoeffs = Z->coeffs;
    mp_limb_t * Acoeffs = A->coeffs;

    ai = Alen - 1;

    for (i = 0; i < Z->length; i++)
    {
        if (ai >= 0 && Zexps[i] == ai)
        {
            /* Z present, A present */
            Zcoeffs[i].coeffs[cur_length] = Acoeffs[ai];
            Zcoeffs[i].length = cur_length + 1;
            do {
                ai--;
            } while (ai >= 0 && Acoeffs[ai] == 0);
        }
        else if (ai < 0 || Zexps[i] > ai)
        {
            /* Z present, A missing */
            Zcoeffs[i].coeffs[cur_length] = 0;
            Zcoeffs[i].length = cur_length + 1;
        }
        else
        {
            /* Z missing, A present */
            return 0;
        }
    }

    return ai < 0;
}


static mp_limb_t * nmod_mat_row_ref(nmod_mat_t M, slong i)
{
    return M->rows[i];
}

static void _nmod_vec_mul(mp_limb_t * a, mp_limb_t * b, mp_limb_t * c,
                                                           slong n, nmod_t ctx)
{
    for (n--; n >= 0; n--)
        a[n] = nmod_mul(b[n], c[n], ctx);
}


/*
    Try to set G to the gcd of A and B given the form f of G.

    f = sum_i X^i sum_j c_ij x^e_j

    images are formed from evaluation at x = alpha^(k+1), 0 <= k < l

    Assume all image gcds have the same degree and are

        sum_i X^i g_ik, 0 <= k < l

    Then, for each 0 <= k < l there is a scalar factor s_k such that

        sum_j c_ij alpha^((k+1)e_j) = s_k g_ik  for 0 <= i <= degree(gcd)

*/
int nmod_mpolyl_gcds_zippel(
    nmod_mpoly_t G, const ulong * Gmarks, slong Gmarkslen,
    nmod_mpoly_t A,
    nmod_mpoly_t B,
    slong *perm,
    slong l,
    slong var,
    const nmod_mpoly_ctx_t ctx,
    flint_rand_t state,
    slong * Gdegbound,
    n_poly_t Amarks,    /* temps */
    n_poly_t Bmarks)
{
    int success;
    int betas_tries_left, underdetermined_left, exceeded_left;
    slong i, s, S, n, cur_zip_image;
    slong Adeg, Bdeg, Gdeg;
    flint_bitcnt_t bits = A->bits;
    n_poly_t Aev, Bev, Gev;
    n_polyun_t Aeh_inc, Aeh_cur, Aeh_coeff_mock;
    n_polyun_t Beh_inc, Beh_cur, Beh_coeff_mock;
    n_polyun_t HG, MG, ZG;
    mp_limb_t * betas;
    n_poly_struct * beta_caches;
    nmod_mat_struct * ML;
    nmod_mat_t MF, Msol, MFtemp, Mwindow;

    FLINT_ASSERT(ctx->minfo->ord == ORD_LEX);
    FLINT_ASSERT(1 < var && var <= ctx->minfo->nvars);

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

    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(B->length > 0);
    FLINT_ASSERT(G->length > 0);
    FLINT_ASSERT(Gmarkslen > 0);
    FLINT_ASSERT(*Gdegbound == mpoly_degree_si(G->exps, G->length,
                                                         bits, 0, ctx->minfo));
    if (Gmarkslen < 2)
    {
        G->coeffs[0] = 1;
        return Gmarks[1] - Gmarks[0] == 1;
    }

    betas = FLINT_ARRAY_ALLOC(var, mp_limb_t);
    beta_caches = FLINT_ARRAY_ALLOC(3*var, n_poly_struct);
    for (i = 0; i < var; i++)
    {
        n_poly_init(beta_caches + 3*i + 0);
        n_poly_init(beta_caches + 3*i + 1);
        n_poly_init(beta_caches + 3*i + 2);
    }

    ML = FLINT_ARRAY_ALLOC(Gmarkslen, nmod_mat_struct);
    for (i = 0; i < Gmarkslen; i++)
        nmod_mat_init(ML + i, 0, 0, nmod_mpoly_ctx_modulus(ctx));

    nmod_mat_init(MF, 0, l, nmod_mpoly_ctx_modulus(ctx));
    nmod_mat_init(Msol, l, 1, nmod_mpoly_ctx_modulus(ctx));

    n_poly_init(Aev);
    n_poly_init(Bev);
    n_poly_init(Gev);

    n_polyun_init(Aeh_inc);
    n_polyun_init(Aeh_cur);
    n_polyun_init(Beh_inc);
    n_polyun_init(Beh_cur);
    Aeh_coeff_mock->exps = NULL;
    Aeh_coeff_mock->coeffs = NULL;
    Aeh_coeff_mock->length = 0;
    Aeh_coeff_mock->alloc = 0;
    Beh_coeff_mock->exps = NULL;
    Beh_coeff_mock->coeffs = NULL;
    Beh_coeff_mock->length = 0;
    Beh_coeff_mock->alloc = 0;

    n_polyun_init(HG);
    n_polyun_init(MG);
    n_polyun_init(ZG);

    Adeg = nmod_mpoly_degree_si(A, 0, ctx);
    Bdeg = nmod_mpoly_degree_si(B, 0, ctx);

    mpoly1_fill_marks(&Amarks->coeffs, &Amarks->length, &Amarks->alloc,
                                         A->exps, A->length, bits, ctx->minfo);

    mpoly1_fill_marks(&Bmarks->coeffs, &Bmarks->length, &Bmarks->alloc,
                                         B->exps, B->length, bits, ctx->minfo);

    betas_tries_left = 10;
    underdetermined_left = 3;
    exceeded_left = 3;

next_betas:

    if (--betas_tries_left < 0)
    {
        success = -1;
        goto cleanup;
    }

    for (i = 1; i < var; i++)
    {
        betas[i] = 1 + n_randint(state, nmod_mpoly_ctx_modulus(ctx) - 1);
        nmod_pow_cache_start(betas[i], beta_caches + 3*i + 0,
                                       beta_caches + 3*i + 1,
                                       beta_caches + 3*i + 2);
    }

    nmod_mpoly_monomial_evals1(Aeh_inc, A, Amarks->coeffs, Amarks->length,
                                                  beta_caches + 3*1, var, ctx);
    nmod_mpoly_monomial_evals1(Beh_inc, B, Bmarks->coeffs, Bmarks->length,
                                                  beta_caches + 3*1, var, ctx);

    n_polyun_set(Aeh_cur, Aeh_inc);
    n_polyun_set(Beh_cur, Beh_inc);
    nmod_mpoly_mock_eval_coeff(Aeh_coeff_mock, A, Aeh_inc, ctx);
    nmod_mpoly_mock_eval_coeff(Beh_coeff_mock, B, Beh_inc, ctx);

    nmod_mpoly_monomial_evals1(HG, G, Gmarks, Gmarkslen,
                                                  beta_caches + 3*1, var, ctx);
    n_polyun_zip_start(ZG, HG, l);
    n = n_polyun_product_roots(MG, HG, ctx->mod);
    FLINT_ASSERT(n <= l);

    for (cur_zip_image = 0; cur_zip_image < l; cur_zip_image++)
    {
        n_polyu1n_mod_zip_eval_cur_inc_coeff(Aev, Aeh_cur, Aeh_inc,
                                                     Aeh_coeff_mock, ctx->mod);
        n_polyu1n_mod_zip_eval_cur_inc_coeff(Bev, Beh_cur, Beh_inc,
                                                     Beh_coeff_mock, ctx->mod);

        if (n_poly_degree(Aev) != Adeg)
            goto next_betas;
        if (n_poly_degree(Bev) != Bdeg)
            goto next_betas;

        n_poly_mod_gcd(Gev, Aev, Bev, ctx->mod);
        Gdeg = n_poly_degree(Gev);

        if (Gdeg > *Gdegbound)
        {
            if (--exceeded_left >= 0)
                goto next_betas;

            success = -1;
            goto cleanup;
        }

        if (Gdeg < *Gdegbound)
        {
            *Gdegbound = Gdeg;
            success = 0;
            goto cleanup;
        }

        if (!n_poly_add_zip_must_match(ZG, Gev, cur_zip_image))
            goto next_betas;
    }

    nmod_mat_clear(Msol);
    nmod_mat_init(Msol, 1, l, nmod_mpoly_ctx_modulus(ctx));

    s = perm[0];
    if (Gmarks[s + 1] - Gmarks[s] == 1)
    {
        /* monic case */
        mp_limb_t temp = 1;

        for (i = 0; i < l; i++)
        {
            temp = nmod_mul(temp, HG->coeffs[s].coeffs[0], ctx->mod);

            if (ZG->coeffs[s].coeffs[i] == 0)
                goto general_case;

            nmod_mat_entry(Msol, 0, i) = nmod_div(temp,
                                            ZG->coeffs[s].coeffs[i], ctx->mod);
        }

        goto try_it;
    }

general_case:

    nmod_mat_clear(Msol);
    nmod_mat_init(Msol, 0, l, nmod_mpoly_ctx_modulus(ctx));

    nmod_mat_clear(MF);
    nmod_mat_init(MF, 0, l, nmod_mpoly_ctx_modulus(ctx));

    for (S = 0; S < Gmarkslen; S++)
    {
        s = perm[S];
        n = Gmarks[s + 1] - Gmarks[s];

        FLINT_ASSERT(n <= l);

        if (nmod_mat_nrows(ML + s) != l ||
            nmod_mat_ncols(ML + s) != l + n)
        {
            nmod_mat_clear(ML + s);
            nmod_mat_init(ML + s, l, l + n, nmod_mpoly_ctx_modulus(ctx));
        }

        _nmod_vec_set(nmod_mat_row_ref(ML + s, 0), HG->coeffs[s].coeffs, n);
        for (i = 1; i < l; i++)
        {
            _nmod_vec_mul(nmod_mat_row_ref(ML + s, i),
                          nmod_mat_row_ref(ML + s, i - 1), 
                                      HG->coeffs[s].coeffs, n, ctx->mod);
        }

        for (i = 0; i < l; i++)
        {
            _nmod_vec_zero(nmod_mat_row_ref(ML + s, i) + n, l);
            nmod_mat_entry(ML + s, i, n + i) = ZG->coeffs[s].coeffs[i];
        }

        /*
            l x (n + l) matrix ML[s]

                     n                    l
            | monomial evals 1 | image coeffs      0 |
          l |     ...          |         on the      |
            | monomial evals l | 0          diagonal |

        */

        nmod_mat_rref(ML + s);

        for (i = 0; i < n; i++)
            if (nmod_mat_entry(ML + s, i, i) != 1)
                goto next_betas;

        /* delete zero rows from MF, matrix interface makes this fun */
        i = nmod_mat_nrows(MF);
        while (i > 1 && _nmod_vec_is_zero(nmod_mat_row_ref(MF, i - 1), l))
            i--;

        if (i < nmod_mat_nrows(MF))
        {
            nmod_mat_window_init(Mwindow, MF, 0, 0, i, l);
            nmod_mat_init(MFtemp, i, l, nmod_mpoly_ctx_modulus(ctx));
            nmod_mat_set(MFtemp, Mwindow);
            nmod_mat_swap(MF, MFtemp);
            nmod_mat_clear(MFtemp);
            nmod_mat_window_clear(Mwindow);
        }

        /* appends rows to MF */
        nmod_mat_window_init(Mwindow, ML + s, n, n, l, n + l);
        nmod_mat_init(MFtemp, i + l - n, l, nmod_mpoly_ctx_modulus(ctx));
        nmod_mat_concat_vertical(MFtemp, MF, Mwindow);
        nmod_mat_swap(MF, MFtemp);
        nmod_mat_clear(MFtemp);
        nmod_mat_window_clear(Mwindow);

        nmod_mat_clear(Msol);
        nmod_mat_init_nullspace_tr(Msol, MF);

        if (nmod_mat_nrows(Msol) < 1)
        {
            success = 0;
            goto cleanup;
        }

        if (nmod_mat_nrows(Msol) == 1)
            break;
    }

    if (nmod_mat_nrows(Msol) != 1)
    {
        if (--underdetermined_left >= 0)
            goto next_betas;

        success = -1;
        goto cleanup;
    }

try_it:

    /* the first row of Msol has the scales */

    for (s = 0; s < Gmarkslen; s++)
    {
        _nmod_vec_mul(ZG->coeffs[s].coeffs, ZG->coeffs[s].coeffs,
                                       nmod_mat_row_ref(Msol, 0), l, ctx->mod);
    }

    success = n_polyun_zip_solve(G, ZG, HG, MG, ctx);

    if (success < 0)
        goto next_betas;

cleanup:

    n_poly_clear(Aev);
    n_poly_clear(Bev);
    n_poly_clear(Gev);

    for (i = 0; i < var; i++)
    {
        n_poly_clear(beta_caches + 3*i + 0);
        n_poly_clear(beta_caches + 3*i + 1);
        n_poly_clear(beta_caches + 3*i + 2);
    }
    flint_free(beta_caches);
    flint_free(betas);

    for (i = 0; i < Gmarkslen; i++)
        nmod_mat_clear(ML + i);
    flint_free(ML);

    nmod_mat_clear(MF);
    nmod_mat_clear(Msol);

    n_polyun_clear(Aeh_inc);
    n_polyun_clear(Aeh_cur);
    n_polyun_clear(Beh_inc);
    n_polyun_clear(Beh_cur);
    flint_free(Aeh_coeff_mock->coeffs);
    flint_free(Beh_coeff_mock->coeffs);

    n_polyun_clear(HG);
    n_polyun_clear(MG);
    n_polyun_clear(ZG);

    return success;
}


static int _do_bivar_or_univar(
    nmod_mpoly_t G,
    nmod_mpoly_t Abar,
    nmod_mpoly_t Bbar,
    nmod_mpoly_t A,
    nmod_mpoly_t B,
    slong var,
    const nmod_mpoly_ctx_t ctx,
    flint_rand_t state)
{
    if (var == 1)
    {
        int success;
        n_poly_t c;
        n_polyun_t Aev, Bev, Gev, Abarev, Bbarev;
        n_poly_polyun_stack_t St;

        n_poly_stack_init(St->poly_stack);
        n_polyun_stack_init(St->polyun_stack);
        n_polyun_init(Aev);
        n_polyun_init(Bev);
        n_polyun_init(Gev);
        n_polyun_init(Abarev);
        n_polyun_init(Bbarev);
        n_poly_init(c);

        nmod_mpoly_get_polyu1n(Aev, A, 0, 1, ctx);
        nmod_mpoly_get_polyu1n(Bev, B, 0, 1, ctx);

        success = n_polyu1n_mod_gcd_brown_smprime(Gev, Abarev, Bbarev,
                                                       Aev, Bev, ctx->mod, St);
        if (success)
        {
            _n_poly_vec_mod_content(c, Gev->coeffs, Gev->length, ctx->mod);
            success = n_poly_is_one(c);
            nmod_mpoly_set_polyu1n(G, Gev, 0, 1, ctx);
        }

        n_poly_clear(c);
        n_polyun_clear(Aev);
        n_polyun_clear(Bev);
        n_polyun_clear(Gev);
        n_polyun_clear(Abarev);
        n_polyun_clear(Bbarev);
        n_poly_stack_clear(St->poly_stack);
        n_polyun_stack_clear(St->polyun_stack);

        return success;
    }
    else
    {
        n_poly_t a, b, c;
        n_poly_init(a);
        n_poly_init(b);
        n_poly_init(c);
        nmod_mpoly_get_n_poly(a, A, 0, ctx);
        nmod_mpoly_get_n_poly(b, B, 0, ctx);
        n_poly_mod_gcd(c, a, b, ctx->mod);
        _nmod_mpoly_set_nmod_poly(G, G->bits, c->coeffs, c->length, 0, ctx);
        n_poly_clear(a);
        n_poly_clear(b);
        n_poly_clear(c);
        return 1;
    }
}


/*
    A and B depend only on x_0, ..., x_var.
    Assume content_x0(G) = 1, and fail otherwise

    when var = 1, this is checked explicitly.

    when var = 2,

        The possibility of f(x2) | G(x0,x1,x2) for nonconstant f(x2) is ruled
        out by the explicit check.

        suppose f(x1,x2) | G(x0,x1,x2) where f is not in Fp[x2]

        the evaluation point x2 = a2 is chosen so that lc_{x0,x1}(G(x0,x1,x2))
        does not vanish at x2 = a2. This means that lc_x1(f(x1, x2)) does
        not vanish at x2 = a2, and so f(x1, a2) is a nontrivial divisor of
        G(x0,x1,a2) which would be caught by the recursive call.

    ...
*/
int nmod_mpolyl_gcdp_zippel_smprime(
    nmod_mpoly_t G,
    nmod_mpoly_t Abar,
    nmod_mpoly_t Bbar,
    nmod_mpoly_t A,
    nmod_mpoly_t B,
    slong var,
    const nmod_mpoly_ctx_t ctx,
    flint_rand_t state)
{
    flint_bitcnt_t bits = A->bits;
    slong i, j, N = mpoly_words_per_exp_sp(bits, ctx->minfo);
    slong Adeg, Bdeg, Alastdeg, Blastdeg, Gdeg;
    slong bound, Gdegbound, lastdeg, req_zip_images;
    int success, changed, have_enough;
    mp_limb_t alpha, start_alpha, gammaeval, temp;
    n_poly_t a, b, c, gamma, modulus, alphapow;
    nmod_mpoly_t Ac, Bc, Aeval, Beval, Geval, Abareval, Bbareval;
    nmod_mpolyn_t H, T;
    n_poly_t Amarks, Bmarks, Gmarks;
    slong * perm = NULL;

    FLINT_ASSERT(ctx->minfo->ord == ORD_LEX);
    FLINT_ASSERT(0 <= var && var < ctx->minfo->nvars);

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

    if (var < 2)
        return _do_bivar_or_univar(G, Abar, Bbar, A, B, var, ctx, state);

    nmod_mpoly_init3(Ac, 0, bits, ctx);
    nmod_mpoly_init3(Bc, 0, bits, ctx);
    nmod_mpoly_init3(Aeval, 0, bits, ctx);
    nmod_mpoly_init3(Beval, 0, bits, ctx);
    nmod_mpoly_init3(Geval, 0, bits, ctx);
    nmod_mpoly_init3(Abareval, 0, bits, ctx);
    nmod_mpoly_init3(Bbareval, 0, bits, ctx);
    n_poly_init(a);
    n_poly_init(b);
    n_poly_init(c);
    n_poly_init(gamma);
    n_poly_init(modulus);
    n_poly_init2(alphapow, 3);
    nmod_mpolyn_init(H, bits, ctx);
    nmod_mpolyn_init(T, bits, ctx);
    n_poly_init(Amarks);
    n_poly_init(Bmarks);
    n_poly_init(Gmarks);

    nmod_mpolyl_content(Ac, A, var, ctx);
    if (!nmod_mpoly_is_one(Ac, ctx))
    {
        success = nmod_mpoly_divides(A, A, Ac, ctx);
        FLINT_ASSERT(success);
    }

    nmod_mpolyl_content(Bc, B, var, ctx);
    if (!nmod_mpoly_is_one(Bc, ctx))
    {
        success = nmod_mpoly_divides(B, B, Bc, ctx);
        FLINT_ASSERT(success);
    }

    nmod_mpoly_get_n_poly(a, Ac, var, ctx);
    nmod_mpoly_get_n_poly(b, Bc, var, ctx);
    n_poly_mod_gcd(c, a, b, ctx->mod);
    success = n_poly_is_one(c);
    if (!success)
        goto cleanup;

    nmod_mpolyl_lead_coeff(Ac, A, var, ctx);
    nmod_mpolyl_lead_coeff(Bc, B, var, ctx);
    nmod_mpoly_get_n_poly(a, Ac, var, ctx);
    nmod_mpoly_get_n_poly(b, Bc, var, ctx);
    n_poly_mod_gcd(gamma, a, b, ctx->mod);

    /* degree bound on the gcd */
    Adeg = nmod_mpoly_degree_si(A, 0, ctx);
    Bdeg = nmod_mpoly_degree_si(B, 0, ctx);
    Gdegbound = FLINT_MIN(Adeg, Bdeg);

    /* bound of the number of images required */
    Alastdeg = nmod_mpoly_degree_si(A, var, ctx);
    Blastdeg = nmod_mpoly_degree_si(B, var, ctx);
    bound = 1 + FLINT_MIN(Alastdeg, Blastdeg) + n_poly_degree(gamma);

    n_poly_one(modulus);

    start_alpha = 1 + n_randint(state, ctx->mod.n - 1);
    alpha = start_alpha;

outer_loop:

    /* get new evaluation point */
    if (alpha < 2)
        alpha = ctx->mod.n;
    alpha -= 1;
    success = (alpha != start_alpha);
    if (!success)
        goto cleanup;

    FLINT_ASSERT(alphapow->alloc >= 2);
    alphapow->coeffs[0] = 1;
    alphapow->coeffs[1] = alpha;
    alphapow->length = 2;

    /* make sure evaluation point does not kill both lc(A) and lc(B) */
    gammaeval = n_poly_mod_eval_pow(gamma, alphapow, ctx->mod);
    if (gammaeval == 0)
        goto outer_loop;

    /* make sure evaluation point does not kill either A or B */
    nmod_mpoly_evaluate_one_ui(Aeval, A, var, alpha, ctx);
    nmod_mpoly_evaluate_one_ui(Beval, B, var, alpha, ctx);
    if (Aeval->length == 0 || Beval->length == 0)
        goto outer_loop;

    nmod_mpoly_repack_bits_inplace(Aeval, bits, ctx);
    nmod_mpoly_repack_bits_inplace(Beval, bits, ctx);

    success = nmod_mpolyl_gcdp_zippel_smprime(Geval, Abareval, Bbareval,
                                            Aeval, Beval, var - 1, ctx, state);
    if (!success)
        goto cleanup;

    FLINT_ASSERT(Geval->length > 0);
    FLINT_ASSERT(Geval->coeffs[0] == 1);

    if (nmod_mpoly_is_one(Geval, ctx))
    {
        nmod_mpoly_one(G, ctx);
        nmod_mpoly_set(Abar, A, ctx);
        nmod_mpoly_set(Bbar, B, ctx);
        success = 1;
        goto cleanup;
    }

    Gdeg = nmod_mpoly_degree_si(Geval, 0, ctx);

    if (Gdeg > Gdegbound)        
        goto outer_loop;

    if (Gdeg < Gdegbound)
        n_poly_one(modulus);

    Gdegbound = Gdeg;

    /* update interpolant H */

    nmod_mpoly_scalar_mul_nmod_invertible(Geval, Geval, gammaeval, ctx);

    if (n_poly_degree(modulus) > 0)
    {
        temp = n_poly_mod_eval_pow(modulus, alphapow, ctx->mod);
        temp = nmod_inv(temp, ctx->mod);
        _n_poly_mod_scalar_mul_nmod_inplace(modulus, temp, ctx->mod);
        changed = nmod_mpolyn_interp_crt_sm_mpoly(&lastdeg, H, T, Geval,
                                                       modulus, alphapow->coeffs[1], ctx);
        if (!changed)
        {
            _n_poly_vec_mod_remove_content(c, H->coeffs, H->length, ctx->mod);
            nmod_mpoly_cvtfrom_mpolyn(G, H, var, ctx);
            nmod_mpoly_make_monic(G, G, ctx);
            success = nmod_mpoly_divides(Abar, A, G, ctx) &&
                      nmod_mpoly_divides(Bbar, B, G, ctx);
            if (success)
                goto cleanup;
            /* restore H */
            _n_poly_vec_mod_mul_poly(H->coeffs, H->length, c, ctx->mod);
            goto outer_loop;
        }
    }
    else
    {
        nmod_mpolyn_interp_lift_sm_mpoly(H, Geval, ctx);
    }

    temp = nmod_neg(alpha, ctx->mod);
    n_poly_mod_shift_left_scalar_addmul(modulus, 1, temp, ctx->mod);


    nmod_mpoly_fit_length(G, H->length, ctx);
    mpoly_copy_monomials(G->exps, H->exps, H->length, N);
    G->length = H->length;

    mpoly1_fill_marks(&Gmarks->coeffs, &Gmarks->length, &Gmarks->alloc,
                                         G->exps, G->length, bits, ctx->minfo);

    perm = FLINT_ARRAY_REALLOC(perm, Gmarks->length, slong);
    for (i = 0; i < Gmarks->length; i++)
        perm[i] = i;

#define length(k) Gmarks->coeffs[(k)+1] - Gmarks->coeffs[k]

    for (i = 1; i < Gmarks->length; i++)
        for (j = i; j > 0 && length(perm[j-1]) > length(perm[j]); j--)
            SLONG_SWAP(perm[j-1], perm[j]);

    req_zip_images = Gmarks->length - 2;
    j = 0;
    for (i = 0; i < Gmarks->length; i++)
    {
        req_zip_images += length(i);
        j = FLINT_MAX(j, length(i));
    }

    if (Gmarks->length > 1)
        req_zip_images = req_zip_images / (Gmarks->length - 1);

    req_zip_images = FLINT_MAX(req_zip_images, j);
    req_zip_images += 1;

inner_loop:

    /* get new evaluation point */
    if (alpha < 2)
        alpha = ctx->mod.n;
    alpha -= 1;
    success = (alpha != start_alpha);
    if (!success)
        goto cleanup;

    FLINT_ASSERT(alphapow->alloc >= 2);
    alphapow->coeffs[0] = 1;
    alphapow->coeffs[1] = alpha;
    alphapow->length = 2;

    /* make sure evaluation does not kill both lc(A) and lc(B) */
    gammaeval = n_poly_mod_eval_pow(gamma, alphapow, ctx->mod);
    if (gammaeval == 0)
        goto inner_loop;

    /* make sure evaluation does not kill either A or B */
    nmod_mpoly_evaluate_one_ui(Aeval, A, var, alpha, ctx);
    nmod_mpoly_evaluate_one_ui(Beval, B, var, alpha, ctx);
    if (Aeval->length == 0 || Beval->length == 0)
        goto outer_loop;

    nmod_mpoly_repack_bits_inplace(Aeval, bits, ctx);
    nmod_mpoly_repack_bits_inplace(Beval, bits, ctx);

    success = nmod_mpolyl_gcds_zippel(Geval, Gmarks->coeffs, Gmarks->length,
                                Aeval, Beval, perm, req_zip_images, var, ctx,
                                            state, &Gdegbound, Amarks, Bmarks);
    if (success == 0)
    {
        n_poly_one(modulus);
        goto outer_loop;
    }

    if (success < 0 || Geval->coeffs[0] == 0)
        goto inner_loop;

    /* update interpolant H */
    temp = nmod_div(gammaeval, Geval->coeffs[0], ctx->mod);
    nmod_mpoly_scalar_mul_nmod_invertible(Geval, Geval, temp, ctx);

    FLINT_ASSERT(n_poly_degree(modulus) > 0);

    temp = n_poly_mod_eval_pow(modulus, alphapow, ctx->mod);
    temp = nmod_inv(temp, ctx->mod);
    _n_poly_mod_scalar_mul_nmod_inplace(modulus, temp, ctx->mod);

    changed = nmod_mpolyn_interp_mcrt_sm_mpoly(&lastdeg, H, Geval,
                                                       modulus, alphapow, ctx);
    temp = nmod_neg(alpha, ctx->mod);
    n_poly_mod_shift_left_scalar_addmul(modulus, 1, temp, ctx->mod);

    have_enough = n_poly_degree(modulus) >= bound;

    if (changed && !have_enough)
        goto inner_loop;

    if (!changed || have_enough)
    {
        /*
            since H started from a correct modular image and was scaled
            wrt to the univariate gamma, if H is the correct gcd modulo
            content, the only content is univariate in the last variable
        */
        _n_poly_vec_mod_remove_content(c, H->coeffs, H->length, ctx->mod);

        nmod_mpoly_cvtfrom_mpolyn(G, H, var, ctx);
        nmod_mpoly_make_monic(G, G, ctx);

        success = nmod_mpoly_divides(Abar, A, G, ctx) &&
                  nmod_mpoly_divides(Bbar, B, G, ctx);
        if (success)
            goto cleanup;

        /* restore H */
        _n_poly_vec_mod_mul_poly(H->coeffs, H->length, c, ctx->mod);
    }

    if (have_enough)
    {
        n_poly_one(modulus);
        goto outer_loop;
    }

    goto inner_loop;

cleanup:

    flint_free(perm);

    nmod_mpoly_clear(Ac, ctx);
    nmod_mpoly_clear(Bc, ctx);
    nmod_mpoly_clear(Aeval, ctx);
    nmod_mpoly_clear(Beval, ctx);
    nmod_mpoly_clear(Geval, ctx);
    nmod_mpoly_clear(Abareval, ctx);
    nmod_mpoly_clear(Bbareval, ctx);
    n_poly_clear(a);
    n_poly_clear(b);
    n_poly_clear(c);
    n_poly_clear(gamma);
    n_poly_clear(modulus);
    n_poly_clear(alphapow);
    nmod_mpolyn_clear(H, ctx);
    nmod_mpolyn_clear(T, ctx);
    n_poly_clear(Amarks);
    n_poly_clear(Bmarks);
    n_poly_clear(Gmarks);

    if (success)
    {
        FLINT_ASSERT(G->bits == bits);
    }

    return success;
}
