/*
    Copyright (C) 2020 Daniel Schultz

    This file is part of FLINT.

    FLINT is free software: you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License (LGPL) as published
    by the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.  See <https://www.gnu.org/licenses/>.
*/

#include "fmpz_poly.h"
#include "mpoly.h"
#include "fmpz_mpoly.h"
#include "fmpz_mpoly_factor.h"

/*
    content wrt gen(0), ..., gen(num_vars-1)
    successful answer will be returned with g->bits == A->bits
*/
int fmpz_mpolyl_content(
    fmpz_mpoly_t g,
    const fmpz_mpoly_t A,
    slong num_vars,
    const fmpz_mpoly_ctx_t ctx)
{
    int success;
    slong i, j, off, shift;
    ulong old_shift, new_shift;
    slong N = mpoly_words_per_exp_sp(A->bits, ctx->minfo);
    ulong * Aexps = A->exps;
    slong Alen = A->length;
    fmpz_mpoly_struct * v;
    slong vlen, valloc;

    FLINT_ASSERT(g != A);
    FLINT_ASSERT(A->length > 0);
    FLINT_ASSERT(ctx->minfo->ord == ORD_LEX);
    FLINT_ASSERT(0 < num_vars && num_vars < ctx->minfo->nvars);

    mpoly_gen_offset_shift_sp(&off, &shift, num_vars - 1, A->bits, ctx->minfo);

    i = 0;
    old_shift = (Aexps + N*i)[off] >> shift;

    valloc = 4;
    v = (fmpz_mpoly_struct *) flint_malloc(valloc*sizeof(fmpz_mpoly_struct));
    vlen = 0;

    v[vlen].bits = A->bits;
    v[vlen].coeffs = A->coeffs + i;
    v[vlen].exps = Aexps + N*i;
    v[vlen].length = i;
    v[vlen].alloc = v[vlen].length;
    vlen++;

    for (i = 1; i < Alen; old_shift = new_shift, i++)
    {
        new_shift = (Aexps + N*i)[off] >> shift;

        if (new_shift != old_shift)
            goto new_one;

        for (j = off + 1; j < N; j++)
            if ((Aexps + N*(i - 1))[j] != (Aexps + N*i)[j])
                goto new_one;

        continue;

new_one:

        v[vlen - 1].length = i - v[vlen - 1].length;
        FLINT_ASSERT(v[vlen - 1].length > 0);
        v[vlen - 1].alloc = v[vlen - 1].length;

        if (vlen + 1 > valloc)
        {
            valloc += 2 + valloc/2;
            v = (fmpz_mpoly_struct *) flint_realloc(v, valloc*
                                                    sizeof(fmpz_mpoly_struct));
        }
        v[vlen].bits = A->bits;
        v[vlen].coeffs = A->coeffs + i;
        v[vlen].exps = Aexps + N*i;
        v[vlen].alloc = 0;
        v[vlen].length = i;
        vlen++;
    }

    v[vlen - 1].length = i - v[vlen - 1].length;
    FLINT_ASSERT(v[vlen - 1].length > 0);
    v[vlen - 1].alloc = v[vlen - 1].length;

    success = _fmpz_mpoly_vec_content_mpoly(g, v, vlen, ctx);

    if (success)
    {
        /* remove gen(0) ... gen(num_vars-1) from the answer */
        ulong * gexps;
        ulong mask;

        fmpz_mpoly_repack_bits_inplace(g, A->bits, ctx);
        gexps = g->exps;

        mask = (shift > 0) ? ((-UWORD(1)) >> (FLINT_BITS - shift)) : 0;
        for (i = 0; i < g->length; i++)
        {
            (gexps + N*i)[off] &= mask;
            for (j = off + 1; j < N; j++)
                (gexps + N*i)[j] = 0;
        }
    }

    flint_free(v);

    return success;
}

int fmpz_mpoly_content_vars(
    fmpz_mpoly_t g,
    const fmpz_mpoly_t A,
    slong * vars, slong num_vars,
    const fmpz_mpoly_ctx_t ctx)
{
    int success;
    slong i, j, k;
    fmpz_mpolyv_t v, w;
    fmpz_mpoly_univar_t u;

    if (num_vars < 1)
    {
        fmpz_mpoly_set(g, A, ctx);
        return 1;
    }

    for (i = 0; i < num_vars; i++)
    {
        if (vars[i] >= (ulong) ctx->minfo->nvars)
            flint_throw(FLINT_ERROR, "fmpz_mpoly_content_vars: variable out of range");
    }

    if (fmpz_mpoly_is_zero(A, ctx))
    {
        fmpz_mpoly_zero(g, ctx);
        return 1;
    }

    if (A->bits <= FLINT_BITS &&
        ctx->minfo->ord == ORD_LEX &&
        num_vars < ctx->minfo->nvars)
    {
        for (i = 0; i < num_vars; i++)
            if (vars[i] != i)
                goto do_general;

        if (g == A)
        {
            fmpz_mpoly_t t;
            fmpz_mpoly_init(t, ctx);
            success = fmpz_mpolyl_content(t, A, num_vars, ctx);
            fmpz_mpoly_swap(g, t, ctx);
            fmpz_mpoly_clear(t, ctx);
            return success;
        }

        return fmpz_mpolyl_content(g, A, num_vars, ctx);
    }

do_general:

    fmpz_mpolyv_init(v, ctx);

    fmpz_mpolyv_init(w, ctx);
    fmpz_mpoly_univar_init(u, ctx);

    i = 0;
    fmpz_mpoly_to_univar(u, A, vars[i], ctx);
    fmpz_mpolyv_fit_length(v, u->length, ctx);
    v->length = u->length;
    for (j = 0; j < u->length; j++)
        fmpz_mpoly_swap(v->coeffs + j, u->coeffs + j, ctx);

    for (i = 1; i < num_vars; i++)
    {
        w->length = 0;
        for (k = 0; k < v->length; k++)
        {
            fmpz_mpoly_to_univar(u, v->coeffs + k, vars[i], ctx);
            fmpz_mpolyv_fit_length(w, w->length + u->length, ctx);
            for (j = 0; j < u->length; j++)
            {
                fmpz_mpoly_swap(w->coeffs + w->length, u->coeffs + j, ctx);
                w->length++;
            }
        }
        fmpz_mpolyv_swap(v, w, ctx);
    }

    fmpz_mpoly_univar_clear(u, ctx);
    fmpz_mpolyv_clear(w, ctx);

    success = _fmpz_mpoly_vec_content_mpoly(g, v->coeffs, v->length, ctx);

    fmpz_mpolyv_clear(v, ctx);

    return success;
}
