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

#include "fmpz.h"
#include "mpoly.h"
#include "fmpz_mod_mpoly.h"

static void _fmpz_max(fmpz_t a, const fmpz_t b, const fmpz_t c)
{
    fmpz_set(a, fmpz_cmp(b, c) > 0 ? b : c);
}

#define COEFF(A, i) ((void*)(A->coeffs + (i)*R->elem_size))
#define IS_ZERO(A) (R->is_zero(A, R->ctx))
#define ZERO(A) (R->zero(A, R->ctx))
#define ONE(A) (R->one(A, R->ctx))
#define SET(A, B) (R->set(A, B, R->ctx))
#define SWAP(A, B) (R->swap(A, B, R->ctx))
#define NEG(A, B) (R->neg(A, B, R->ctx))
#define ADD(A, B, C) (R->add(A, B, C, R->ctx))
#define SUB(A, B, C) (R->sub(A, B, C, R->ctx))
#define MUL_FMPZ(A, B, C) (R->mul_fmpz(A, B, C, R->ctx))
#define MUL(A, B, C) (R->mul(A, B, C, R->ctx))
#define DIVEXACT(A, B, C) (R->divexact(A, B, C, R->ctx))
#define DIVIDES(A, B, C) (R->divides(A, B, C, R->ctx))
#define POW_FMPZ(A, B, C) (R->pow_fmpz(A, B, C, R->ctx))

void * mpoly_void_ring_elem_init(mpoly_void_ring_t R)
{
    void * a = flint_malloc(R->elem_size);
    R->init(a, R->ctx);
    return a;
}

void mpoly_void_ring_elem_clear(void * a, mpoly_void_ring_t R)
{
    R->clear(a, R->ctx);
    flint_free(a);
}

void mpoly_univar_init(mpoly_univar_t A, mpoly_void_ring_t R)
{
    A->coeffs = NULL;
    A->exps = NULL;
    A->alloc = 0;
    A->length = 0;
}

void mpoly_univar_clear(mpoly_univar_t A, mpoly_void_ring_t R)
{
    slong i;

    for (i = 0; i < A->alloc; i++)
    {
        R->clear(COEFF(A, i), R->ctx);
        fmpz_clear(A->exps + i);
    }

    flint_free(A->coeffs);
    flint_free(A->exps);
}

void mpoly_univar_swap(mpoly_univar_t A, mpoly_univar_t B)
{
    mpoly_univar_struct t = *B;
    *B = *A;
    *A = t;
}

void mpoly_univar_fit_length(mpoly_univar_t A, slong len, mpoly_void_ring_t R)
{
    slong i;
    slong old_alloc = A->alloc;
    slong new_alloc = FLINT_MAX(len, 2*A->alloc);

    if (len <= old_alloc)
        return;

    A->exps = FLINT_ARRAY_REALLOC(A->exps, new_alloc, fmpz);
    A->coeffs = flint_realloc(A->coeffs, new_alloc*R->elem_size);

    for (i = old_alloc; i < new_alloc; i++)
    {
        fmpz_init(A->exps + i);
        R->init(COEFF(A, i), R->ctx);
    }

    A->alloc = new_alloc;
}

void mpoly_univar_init2(mpoly_univar_t A, slong len, mpoly_void_ring_t R)
{
    mpoly_univar_init(A, R);
    mpoly_univar_fit_length(A, len, R);
}

/*
    A = prem(A, -B)
    C is used for working space
*/
void mpoly_univar_prem(
    mpoly_univar_t A,
    const mpoly_univar_t B,
    mpoly_univar_t C,
    mpoly_void_ring_t R)
{
    slong i, j;
    fmpz_t z1, delta, delta_org;
    void * u = mpoly_void_ring_elem_init(R);
    void * v = mpoly_void_ring_elem_init(R);

    FLINT_ASSERT(A != B);
    FLINT_ASSERT(B != C);
    FLINT_ASSERT(C != A);
    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(B->length > 0);
    FLINT_ASSERT(fmpz_cmp(A->exps + 0, B->exps + 0) >= 0);

    fmpz_init(z1);
    fmpz_init(delta);
    fmpz_init(delta_org);

    fmpz_sub(delta_org, A->exps + 0, B->exps + 0);
    fmpz_add_ui(delta_org, delta_org, 1);

looper:

    if (A->length < 1)
        goto done;

    fmpz_sub(delta, A->exps + 0, B->exps + 0);
    if (fmpz_sgn(delta) < 0)
        goto done;

    i = 1;
    j = 1;
    C->length = 0;
    while (i < A->length || j < B->length)
    {
        mpoly_univar_fit_length(C, C->length + 1, R);

        if (j < B->length)
            fmpz_add(z1, B->exps + j, delta);

        if (i < A->length && j < B->length && fmpz_equal(A->exps + i, z1))
        {
            MUL(u, COEFF(A, i), COEFF(B, 0));
            MUL(v, COEFF(A, 0), COEFF(B, j));
            SUB(COEFF(C, C->length), v, u);
            fmpz_set(C->exps + C->length, A->exps + i);
            i++;
            j++;
        }
        else if (i < A->length && (j >= B->length ||
                                                fmpz_cmp(A->exps + i, z1) > 0))
        {
            MUL(COEFF(C, C->length), COEFF(A, i), COEFF(B, 0));
            NEG(COEFF(C, C->length), COEFF(C, C->length));
            fmpz_set(C->exps + C->length, A->exps + i);
            i++;
        }
        else
        {
            FLINT_ASSERT(j < B->length && (i >= A->length ||
                                               fmpz_cmp(A->exps + i, z1) < 0));

            MUL(COEFF(C, C->length), COEFF(A, 0), COEFF(B, j));
            fmpz_set(C->exps + C->length, z1);
            j++;
        }

        C->length += !IS_ZERO(COEFF(C, C->length));
    }

    mpoly_univar_swap(A, C);
    fmpz_sub_ui(delta_org, delta_org, 1);
    goto looper;

done:

    FLINT_ASSERT(fmpz_sgn(delta_org) >= 0);

    if (!fmpz_is_zero(delta_org))
    {
        NEG(v, COEFF(B, 0));
        POW_FMPZ(u, v, delta_org);
        for (i = 0; i < A->length; i++)
            MUL(COEFF(A, i), COEFF(A, i), u);
    }

    mpoly_void_ring_elem_clear(u, R);
    mpoly_void_ring_elem_clear(v, R);

    fmpz_clear(z1);
    fmpz_clear(delta);
    fmpz_clear(delta_org);
}

/*
    G = pseudo-gcd of B and A
      = last nonzero subresultant polynomial starting with B and A

    B and A are clobbered
*/
int mpoly_univar_pseudo_gcd_ducos(
    mpoly_univar_t G,
    mpoly_univar_t B,
    mpoly_univar_t A,
    mpoly_void_ring_t R)
{
    slong i, j, k, aJ, ae;
    fmpz_t n, d, e, J, z1, alpha;
    int iexists, jexists, kexists;
    void * u, * v, * w, * s;
    mpoly_univar_t C, D, H, T;
    mpoly_univar_struct * last;

    FLINT_ASSERT(B->length > 0);
    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(fmpz_cmp(B->exps + 0, A->exps + 0) >= 0);
    FLINT_ASSERT(fmpz_sgn(A->exps + 0) >= 0);

    if (fmpz_is_zero(A->exps + 0))
    {
        mpoly_univar_fit_length(G, 1, R);
        G->length = 1;
        fmpz_zero(G->exps + 0);
        return POW_FMPZ(COEFF(G, 0), COEFF(A, 0), B->exps + 0);
    }

    fmpz_init(n);
    fmpz_init(d);
    fmpz_init(e);
    fmpz_init(J);
    fmpz_init(z1);
    fmpz_init(alpha);

    u = mpoly_void_ring_elem_init(R);
    v = mpoly_void_ring_elem_init(R);
    w = mpoly_void_ring_elem_init(R);
    s = mpoly_void_ring_elem_init(R);

    i = FLINT_MAX(B->length, A->length);
    mpoly_univar_init2(C, i + 1, R);
    mpoly_univar_init2(D, i + 1, R);
    mpoly_univar_init2(H, i + 1, R);
    mpoly_univar_init2(T, i + 1, R);

    last = A;

    fmpz_sub(z1, B->exps + 0, A->exps + 0);
    POW_FMPZ(s, A->coeffs + 0, z1);

    mpoly_univar_prem(B, A, D, R);

looper:

    if (B->length < 1)
        goto done;

    fmpz_set(d, A->exps + 0);
    fmpz_set(e, B->exps + 0);

    last = B;

    fmpz_sub(z1, d, e);
    if (fmpz_is_one(z1))
    {
        if (fmpz_is_zero(e))
            goto done;

        /* D = (B[e]*A - A[e]*B)/A[d] */
        /*           i        j       */
        i = 1;
        j = 1;
        if (A->length > 1 && fmpz_equal(A->exps + 1, e))
            i++;
        else
            j = B->length;
        D->length = 0;
        while (i < A->length || j < B->length)
        {
            mpoly_univar_fit_length(D, D->length + 1, R);

            if (i < A->length && j < B->length &&
                                          fmpz_equal(A->exps + i, B->exps + j))
            {
                MUL(u, COEFF(A, i), COEFF(B, 0));
                MUL(v, COEFF(A, 1), COEFF(B, j));
                SUB(w, u, v);
                DIVEXACT(COEFF(D, D->length), w, COEFF(A, 0));
                fmpz_set(D->exps + D->length, A->exps + i);
                i++;
                j++;
            }
            else if (i < A->length && (j >= B->length ||
                                       fmpz_cmp(A->exps + i, B->exps + j) > 0))
            {
                MUL(u, COEFF(A, i), COEFF(B, 0));
                DIVEXACT(COEFF(D, D->length), u, COEFF(A, 0));
                fmpz_set(D->exps + D->length, A->exps + i);
                i++;
            }
            else
            {
                FLINT_ASSERT((j < B->length && (i >= A->length ||
                                     fmpz_cmp(B->exps + j, A->exps + i) > 0)));

                MUL(v, COEFF(A, 1), COEFF(B, j));
                DIVEXACT(COEFF(D, D->length), v, COEFF(A, 0));
                NEG(COEFF(D, D->length), COEFF(D, D->length));
                fmpz_set(D->exps + D->length, B->exps + j);
                j++;
            }

            D->length += !IS_ZERO(COEFF(D, D->length));
        }

        /* A = (B[e]*(D - B*x) + B[e-1]*B)/s */
        /*            i    j            k    */
        i = 0;
        fmpz_sub_ui(z1, e, 1);
        if (B->length > 1 && fmpz_equal(B->exps + 1, z1))
        {
            j = 2;
            k = 1;
        }
        else
        {
            j = 1;
            k = B->length;
        }

        A->length = 0;
        while (i < D->length || j < B->length || k < B->length)
        {
            fmpz * exp;

            mpoly_univar_fit_length(A, A->length + 1, R);

            exp = A->exps + A->length;

            fmpz_zero(exp);

            if (i < D->length)
                _fmpz_max(exp, exp, D->exps + i);

            if (j < B->length)
            {
                fmpz_add_ui(z1, B->exps + j, 1);
                _fmpz_max(exp, exp, z1);
            }

            if (k < B->length)
                _fmpz_max(exp, exp, B->exps + k);

            iexists = (i < D->length) && fmpz_equal(exp, D->exps + i);
            jexists = (j < B->length) && fmpz_equal(exp, z1);
            kexists = (k < B->length) && fmpz_equal(exp, B->exps + k);

            FLINT_ASSERT(iexists || jexists || kexists);

            if (iexists)
            {
                if (jexists)
                {
                    SUB(w, COEFF(D, i), COEFF(B, j));
                    MUL(u, COEFF(B, 0), w);
                }
                else
                {
                    MUL(u, COEFF(B, 0), COEFF(D, i));
                }

                if (kexists)
                {
                    MUL(v, COEFF(B, 1), COEFF(B, k));
                    ADD(w, u, v);
                    DIVEXACT(COEFF(A, A->length), w, s);
                }
                else
                {
                    DIVEXACT(COEFF(A, A->length), u, s);
                }
            }
            else
            {
                if (kexists)
                {
                    MUL(u, COEFF(B, 1), COEFF(B, k));
                    if (jexists)
                    {
                        MUL(v, COEFF(B, 0), COEFF(B, j));
                        SUB(w, u, v);
                        DIVEXACT(COEFF(A, A->length), w, s);
                    }
                    else
                    {
                        DIVEXACT(COEFF(A, A->length), u, s);
                    }
                }
                else
                {
                    MUL(u, COEFF(B, 0), COEFF(B, j));
                    DIVEXACT(COEFF(A, A->length), u, s);
                    NEG(COEFF(A, A->length), COEFF(A, A->length));
                }
            }

            A->length += !IS_ZERO(COEFF(A, A->length));

            i += iexists;
            j += jexists;
            k += kexists;
        }

        mpoly_univar_swap(A, B);
        SET(s, COEFF(A, 0));
        last = A;
    }
    else
    {
        fmpz_sub(n, d, e);
        fmpz_sub_ui(n, n, 1);
        fmpz_one(alpha);
        while (fmpz_add(z1, alpha, alpha), fmpz_cmp(z1, n) <= 0)
            fmpz_set(alpha, z1);

        SET(u, COEFF(B, 0));
        fmpz_sub(n, n, alpha);
        while (fmpz_cmp_ui(alpha, 1) > 0)
        {
            fmpz_tdiv_q_2exp(alpha, alpha, 1);
            MUL(v, u, u);
            DIVEXACT(u, v, s);
            if (fmpz_cmp(n, alpha) >= 0)
            {
                MUL(v, u, COEFF(B, 0));
                DIVEXACT(u, v, s);
                fmpz_sub(n, n, alpha);
            }
        }

        mpoly_univar_fit_length(C, B->length, R);
        for (i = 0; i < B->length; i++)
        {
            MUL(v, u, COEFF(B, i));
            DIVEXACT(COEFF(C, i), v, s);
            fmpz_set(C->exps + i, B->exps + i);
        }
        C->length = B->length;

        last = C;

        if (fmpz_is_zero(e))
            goto done;

        /* H = C - C[e]*x^e */
        mpoly_univar_fit_length(H, C->length, R);
        for (i = 1; i < C->length; i++)
        {
            SET(COEFF(H, i - 1), COEFF(C, i));
            fmpz_set(H->exps + i - 1, C->exps + i);
        }
        H->length = C->length - 1;

        /* D = C[e]*A - A[e]*H  (truncated to powers of x < e) */
        i = 0;
        j = H->length;
        ae = A->length;
        while (i < A->length && fmpz_cmp(A->exps + i, e) >= 0)
        {
            if (fmpz_equal(A->exps + i, e))
            {
                j = 0;
                ae = i;
            }
            i++;
        }
        D->length = 0;
        while (i < A->length || j < H->length)
        {
            mpoly_univar_fit_length(D, D->length + 1, R);

            if (i < A->length && j < H->length &&
                                          fmpz_equal(A->exps + i, H->exps + j))
            {
                MUL(u, COEFF(A, i), COEFF(C, 0));
                MUL(v, COEFF(A, ae), COEFF(H, j));
                SUB(COEFF(D, D->length), u, v);
                fmpz_set(D->exps + D->length, A->exps + i);
                i++;
                j++;
            }
            else if (i < A->length && (j >= H->length ||
                                       fmpz_cmp(A->exps + i, H->exps + j) > 0))
            {
                MUL(COEFF(D, D->length), COEFF(A, i), COEFF(C, 0));
                fmpz_set(D->exps + D->length, A->exps + i);
                i++;
            }
            else
            {
                FLINT_ASSERT(j < H->length && (i >= A->length ||
                                      fmpz_cmp(H->exps + j, A->exps + i) > 0));

                MUL(COEFF(D, D->length), COEFF(A, ae), COEFF(H, j));
                NEG(COEFF(D, D->length), COEFF(D, D->length));
                fmpz_set(D->exps + D->length, H->exps + j);
                j++;
            }

            D->length += !IS_ZERO(COEFF(D, D->length));
        }

        for (fmpz_add_ui(J, e, 1); fmpz_cmp(J, d) < 0; fmpz_add_ui(J, J, 1))
        {
            if (H->length < 1)
                break;

            /* H = H*x - H[e-1]*B/B[e] */
            fmpz_sub_ui(z1, e, 1);
            if (fmpz_equal(H->exps + 0, z1))
            {
                i = 1;
                j = 1;
                T->length = 0;
                while (i < H->length || j < B->length)
                {
                    mpoly_univar_fit_length(T, T->length + 1, R);

                    if (i < H->length)
                        fmpz_add_ui(z1, H->exps + i, 1);

                    if (i < H->length && j < B->length &&
                                                   fmpz_equal(z1, B->exps + j))
                    {
                        MUL(u, COEFF(H, 0), COEFF(B, j));
                        DIVEXACT(v, u, COEFF(B, 0));
                        SUB(COEFF(T, T->length), COEFF(H, i), v);
                        fmpz_set(T->exps + T->length, B->exps + j);
                        i++;
                        j++;
                    }
                    else if (i < H->length && (j >= B->length ||
                                                fmpz_cmp(z1, B->exps + j) > 0))
                    {
                        SET(COEFF(T, T->length), COEFF(H, i));
                        fmpz_set(T->exps + T->length, z1);
                        i++;
                    }
                    else
                    {
                        FLINT_ASSERT(j < B->length && (i >= H->length ||
                                               fmpz_cmp(z1, B->exps + j) < 0));

                        MUL(u, COEFF(H, 0), COEFF(B, j));
                        DIVEXACT(COEFF(T, T->length), u, COEFF(B, 0));
                        NEG(COEFF(T, T->length), COEFF(T, T->length));
                        fmpz_set(T->exps + T->length, B->exps + j);
                        j++;
                    }

                    T->length += !IS_ZERO(COEFF(T, T->length));
                }

                mpoly_univar_swap(H, T);
            }
            else
            {
                FLINT_ASSERT(fmpz_cmp(H->exps + 0, z1) < 0);
                for (i = 0; i < H->length; i++)
                    fmpz_add_ui(H->exps + i, H->exps + i, 1);
            }

            /* find coefficient of x^J in A */
            aJ = 0;
            while (aJ < A->length && !fmpz_equal(A->exps + aJ, J))
                aJ++;
            if (aJ >= A->length)
                continue;

            /* D = D - A[J]*H */
            i = 0;
            j = 0;
            T->length = 0;
            while (i < D->length || j < H->length)
            {
                mpoly_univar_fit_length(T, T->length + 1, R);

                if (i < D->length && j < H->length &&
                                          fmpz_equal(D->exps + i, H->exps + j))
                {
                    MUL(u, COEFF(H, j), COEFF(A, aJ));
                    SUB(COEFF(T, T->length), COEFF(D, i), u);
                    fmpz_set(T->exps + T->length, D->exps + i);
                    i++;
                    j++;
                }
                else if (i < D->length && (j >= H->length ||
                                       fmpz_cmp(D->exps + i, H->exps + j) > 0))
                {
                    SET(COEFF(T, T->length), COEFF(D, i));
                    fmpz_set(T->exps + T->length, D->exps + i);
                    i++;
                }
                else
                {
                    FLINT_ASSERT(j < H->length && (i >= D->length ||
                                      fmpz_cmp(D->exps + i, H->exps + j) < 0));

                    MUL(COEFF(T, T->length), COEFF(H, j), COEFF(A, aJ));
                    NEG(COEFF(T, T->length), COEFF(T, T->length));
                    fmpz_set(T->exps + T->length, H->exps + j);
                    j++;
                }

                T->length += !IS_ZERO(COEFF(T, T->length));
            }
            mpoly_univar_swap(D, T);
        }

        /* B = (-1)^(d-e+1) * (B[e]*(D/A[d] - H*x) +  H[e-1]*B)/s */
        i = 0;
        fmpz_sub_ui(z1, e, 1);
        if (H->length > 0 && fmpz_equal(H->exps + 0, z1))
        {
            j = 1;
            k = 1;
        }
        else
        {
            j = 0;
            k = B->length;
        }
        T->length = 0;
        while (i < D->length || j < H->length || k < B->length)
        {
            fmpz * exp;

            mpoly_univar_fit_length(T, T->length + 1, R);

            exp = T->exps + T->length;
            fmpz_zero(exp);

            if (i < D->length)
                _fmpz_max(exp, exp, D->exps + i);

            if (j < H->length)
            {
                fmpz_add_ui(z1, H->exps + j, 1);
                _fmpz_max(exp, exp, z1);
            }

            if (k < B->length)
                _fmpz_max(exp, exp, B->exps + k);

            iexists = (i < D->length && fmpz_equal(exp, D->exps + i));
            jexists = (j < H->length && fmpz_equal(exp, z1));
            kexists = (k < B->length && fmpz_equal(exp, B->exps + k));

            FLINT_ASSERT(iexists || jexists || kexists);

            if (iexists)
            {
                if (jexists)
                {
                    DIVEXACT(u, COEFF(D, i), COEFF(A, 0));
                    SUB(w, u, COEFF(H, j));
                    MUL(u, COEFF(B, 0), w);
                }
                else
                {
                    DIVEXACT(u, COEFF(D, i), COEFF(A, 0));
                    MUL(u, COEFF(B, 0), u);
                }
                if (kexists)
                {
                    MUL(v, COEFF(H, 0), COEFF(B, k));
                    ADD(w, u, v);
                    DIVEXACT(COEFF(T, T->length), w, s);
                }
                else
                {
                    DIVEXACT(COEFF(T, T->length), u, s);
                }
            }
            else
            {
                if (kexists)
                {
                    MUL(u, COEFF(H, 0), COEFF(B, k));
                    if (jexists)
                    {
                        MUL(v, COEFF(B, 0), COEFF(H, j));
                        SUB(w, u, v);
                        DIVEXACT(COEFF(T, T->length), w, s);
                    }
                    else
                    {
                        DIVEXACT(COEFF(T, T->length), u, s);
                    }
                }
                else
                {
                    MUL(u, COEFF(B, 0), COEFF(H, j));
                    DIVEXACT(COEFF(T, T->length), u, s);
                    NEG(COEFF(T, T->length), COEFF(T, T->length));
                }
            }

            if (((fmpz_get_ui(d) - fmpz_get_ui(e)) & 1) == 0)
                NEG(COEFF(T, T->length), COEFF(T, T->length));

            T->length += !IS_ZERO(COEFF(T, T->length));

            i += iexists;
            j += jexists;
            k += kexists;
        }

        mpoly_univar_swap(B, T);
        mpoly_univar_swap(A, C);
        SET(s, COEFF(A, 0));
        last = A;
    }

    goto looper;

done:

    mpoly_univar_swap(G, last);

    fmpz_clear(n);
    fmpz_clear(d);
    fmpz_clear(e);
    fmpz_clear(J);
    fmpz_clear(z1);
    fmpz_clear(alpha);
    mpoly_void_ring_elem_clear(u, R);
    mpoly_void_ring_elem_clear(v, R);
    mpoly_void_ring_elem_clear(w, R);
    mpoly_void_ring_elem_clear(s, R);
    mpoly_univar_clear(C, R);
    mpoly_univar_clear(D, R);
    mpoly_univar_clear(H, R);
    mpoly_univar_clear(T, R);
    return 1;
}



void mpoly_univar_derivative(
    mpoly_univar_t A,
    const mpoly_univar_t B,
    mpoly_void_ring_t R)
{
    slong Ai, Bi;

    mpoly_univar_fit_length(A, B->length, R);

    Ai = 0;
    for (Bi = 0; Bi < B->length; Bi++)
    {
        if (fmpz_sgn(B->exps + Bi) <= 0)
            continue;

        MUL_FMPZ(COEFF(A, Ai), COEFF(B, Bi), B->exps + Bi);
        fmpz_sub_ui(A->exps + Ai, B->exps + Bi, 1);
        Ai += !IS_ZERO(COEFF(A, Ai));
    }
    A->length = Ai;
}

/* fx and gx are clobbered */
int mpoly_univar_resultant(
    void * r,
    mpoly_univar_t fx,
    mpoly_univar_t gx,
    mpoly_void_ring_t R)
{
    int success, change_sign;
    mpoly_univar_t rx;
    mpoly_univar_struct * F, * G;

    if (fx->length < 1 || gx->length < 1)
    {
        ZERO(r);
        return 1;
    }

    mpoly_univar_init(rx, R);

    if (fmpz_cmp(fx->exps + 0, gx->exps + 0) < 0)
    {
        change_sign = 1 & fmpz_get_ui(fx->exps + 0) & fmpz_get_ui(gx->exps + 0);
        F = gx;
        G = fx;
    }
    else
    {
        change_sign = 0;
        F = fx;
        G = gx;
    }

    if (fmpz_is_zero(G->exps + 0))
    {
        success = POW_FMPZ(r, COEFF(G, 0), F->exps + 0);
    }
    else
    {
        success = mpoly_univar_pseudo_gcd_ducos(rx, F, G, R);

        if (success && rx->length == 1 && fmpz_is_zero(rx->exps + 0))
            SWAP(r, COEFF(rx, 0));
        else
            ZERO(r);
    }

    if (success && change_sign)
        NEG(r, r);

    mpoly_univar_clear(rx, R);

    return success;
}

/* fx is clobbered */
int mpoly_univar_discriminant(
    void * d,
    mpoly_univar_t fx,
    mpoly_void_ring_t R)
{
    int success;
    mpoly_univar_t rx, fxp;

    if (fx->length < 1 || fmpz_cmp_ui(fx->exps + fx->length - 1, 1) > 0)
    {
        /* the discriminant of the zero polynomial should be zero */
        ZERO(d);
        return 1;
    }

    if (fmpz_is_zero(fx->exps + 0))
    {
        /* the discriminant of the constant polynomial a should be 1/a^2 */
        ONE(d);
        success = DIVIDES(d, d, COEFF(fx, 0));
        if (success)
            MUL(d, d, d);
        return success;
    }

    if (fmpz_is_one(fx->exps + 0))
    {
        /* the discriminant of a linear polynomial should be 1 */
        ONE(d);
        return 1;
    }

    /* the discriminant is (-1)^(n*(n-1)/2)*res(f,f')*a_n^(n-m-2), m=deg(f') */
    mpoly_univar_init(rx, R);
    mpoly_univar_init(fxp, R);
    mpoly_univar_derivative(fxp, fx, R);

    if (fxp->length < 1)
    {
        ZERO(d);
        success = 1;
    }
    else
    {
        int change_sign = fmpz_get_ui(fx->exps + 0) & 2;
        fmpz_t exp_diff;
        void * u;

        fmpz_init(exp_diff);
        fmpz_sub(exp_diff, fx->exps + 0, fxp->exps + 0);
        fmpz_sub_ui(exp_diff, exp_diff, 2);

        u = mpoly_void_ring_elem_init(R);
        SET(u, COEFF(fx, 0));   /* will be clobbered */

        success = mpoly_univar_pseudo_gcd_ducos(rx, fx, fxp, R);

        if (success && rx->length == 1 && fmpz_is_zero(rx->exps + 0))
        {
            if (change_sign != 0)
                NEG(COEFF(rx, 0), COEFF(rx, 0));

            if (fmpz_sgn(exp_diff) < 0)
            {
                FLINT_ASSERT(fmpz_equal_si(exp_diff, -1));
                DIVEXACT(d, COEFF(rx, 0), u);
            }
            else
            {
                success = POW_FMPZ(u, u, exp_diff);
                if (success)
                    MUL(d, COEFF(rx, 0), u);
            }
        }
        else
        {
            ZERO(d);
        }

        fmpz_clear(exp_diff);
        mpoly_void_ring_elem_clear(u, R);
    }

    mpoly_univar_clear(rx, R);
    mpoly_univar_clear(fxp, R);

    return success;
}
