/*
    Copyright (C) 2016 Fredrik Johansson

    This file is part of Arb.

    Arb 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 <http://www.gnu.org/licenses/>.
*/

#include "acb_hypgeom.h"

#define FAST_T 0

void acb_poly_reciprocal_majorant(arb_poly_t res, const acb_poly_t poly, slong prec);

static void
rsplit(acb_poly_t res, acb_poly_t term,
    const acb_poly_struct * a, slong p,
    const acb_poly_struct * b, slong q,
    const acb_poly_t z, slong offset, slong n, slong len, slong prec)
{
    acb_poly_struct * zpow;
    acb_poly_t s, t, u;
    slong i, j, k, m, tprec;
    int is_real;

#if FAST_T
    arb_poly_t B, C, D;
#else
    acb_poly_t B, C, D;
#endif

    if (n == 0)
    {
        acb_poly_zero(res);
        acb_poly_one(term);
        return;
    }

    if (n < 0)
        flint_abort();

    m = n_sqrt(n);
    m = FLINT_MIN(m, 150);

    acb_poly_init(s);
    acb_poly_init(t);
    acb_poly_init(u);

#if FAST_T
    tprec = MAG_BITS;

    arb_poly_init(B);
    arb_poly_init(C);
    arb_poly_init(D);

    arb_poly_one(B);
    arb_poly_one(D);

    is_real = 1;

    for (i = 0; i < p; i++)
        for (j = 0; j < FLINT_MIN(acb_poly_length(a + i), len); j++)
            is_real = is_real && acb_is_real((a + i)->coeffs + j);

    for (i = 0; i < q; i++)
        for (j = 0; j < FLINT_MIN(acb_poly_length(b + i), len); j++)
            is_real = is_real && acb_is_real((b + i)->coeffs + j);

    for (j = 0; j < FLINT_MIN(acb_poly_length(z), len); j++)
        is_real = is_real && acb_is_real(z->coeffs + j);
#else
    tprec = 2 * MAG_BITS;
    is_real = 0; (void) is_real;

    acb_poly_init(B);
    acb_poly_init(C);
    acb_poly_init(D);

    acb_poly_one(B);
    acb_poly_one(D);
#endif

    zpow = flint_malloc(sizeof(acb_poly_struct) * (m + 1));

    for (i = 0; i <= m; i++)
        acb_poly_init(zpow + i);

    for (i = 0; i <= m; i++)
    {
        if (i == 0)
            acb_poly_one(zpow + i);
        else if (i == 1)
            acb_poly_set_round(zpow + i, z, prec);
        else if (i % 2 == 0)
            acb_poly_mullow(zpow + i, zpow + i / 2, zpow + i / 2, len, prec);
        else
            acb_poly_mullow(zpow + i, zpow + i - 1, zpow + 1, len, prec);
    }

    for (k = n; k >= 0; k--)
    {
        j = k % m;

        if (k < n)
            acb_poly_add(s, s, zpow + j, prec);

        if (k > 0)
        {
            if (p > 0)
            {
                acb_poly_add_si(u, a, offset + k - 1, prec);

                for (i = 1; i < p; i++)
                {
                    acb_poly_add_si(t, a + i, offset + k - 1, prec);
                    acb_poly_mullow(u, u, t, len, prec);
                }

                if (k < n)
                    acb_poly_mullow(s, s, u, len, prec);

#if FAST_T
                acb_poly_majorant(C, u, tprec);
                arb_poly_mullow(B, B, C, len, tprec);
#else
                acb_poly_set_round(C, u, tprec);
                acb_poly_mullow(B, B, C, len, tprec);

#endif
            }

            if (q > 0)
            {
                acb_poly_add_si(u, b, offset + k - 1, prec);

                for (i = 1; i < q; i++)
                {
                    acb_poly_add_si(t, b + i, offset + k - 1, prec);
                    acb_poly_mullow(u, u, t, len, prec);
                }

                if (k < n)
                    acb_poly_div_series(s, s, u, len, prec);

#if FAST_T
                acb_poly_reciprocal_majorant(C, u, tprec);
                arb_poly_mullow(D, D, C, len, tprec);
#else
                acb_poly_set_round(C, u, tprec);
                acb_poly_mullow(D, D, C, len, tprec);
#endif
            }

            if (j == 0 && k < n)
            {
                acb_poly_mullow(s, s, zpow + m, len, prec);
            }
        }
    }

#if FAST_T
    arb_poly_div_series(B, B, D, len, tprec);
    acb_poly_majorant(C, z, tprec);
    arb_poly_pow_ui_trunc_binexp(C, C, n, len, tprec);
    arb_poly_mullow(C, B, C, len, tprec);

    acb_poly_fit_length(term, arb_poly_length(C));

    for (i = 0; i < arb_poly_length(C); i++)
    {
        arb_get_mag(arb_radref(acb_realref(term->coeffs + i)), C->coeffs + i);
        arf_zero(arb_midref(acb_realref(term->coeffs + i)));

        if (is_real)
            arb_zero(acb_imagref(term->coeffs + i));
        else
            arb_set(acb_imagref(term->coeffs + i), acb_realref(term->coeffs + i));
    }

    _acb_poly_set_length(term, arb_poly_length(C));
    _acb_poly_normalise(term);

#else
    acb_poly_div_series(B, B, D, len, tprec);
    acb_poly_set_round(C, z, tprec);
    acb_poly_pow_ui_trunc_binexp(C, C, n, len, tprec);
    acb_poly_mullow(term, B, C, len, tprec);
#endif

    acb_poly_set(res, s);

#if FAST_T
    arb_poly_clear(B);
    arb_poly_clear(C);
    arb_poly_clear(D);
#else
    acb_poly_clear(B);
    acb_poly_clear(C);
    acb_poly_clear(D);
#endif

    acb_poly_clear(s);
    acb_poly_clear(t);
    acb_poly_clear(u);

    for (i = 0; i <= m; i++)
        acb_poly_clear(zpow + i);
    flint_free(zpow);
}

void
acb_hypgeom_pfq_series_sum_rs(acb_poly_t s, acb_poly_t t,
    const acb_poly_struct * a, slong p,
    const acb_poly_struct * b, slong q,
    const acb_poly_t z, int regularized,
    slong n, slong len, slong prec)
{
    acb_poly_t u, v;
    slong   i, start;

    if (n == 0)
    {
        acb_hypgeom_pfq_series_sum_forward(s, t, a, p, b, q, z,
            regularized, n, len, prec);
        return;
    }

    start = 0;

    if (regularized)
    {
        for (i = 0; i < q; i++)
        {
            if (acb_poly_is_zero(b + i))
            {
                start = FLINT_MAX(start, 1);
            }
            else
            {
                /* todo: use a fuzzier test? */
                if (acb_contains_int((b + i)->coeffs) &&
                    !arb_is_positive(acb_realref((b + i)->coeffs)) &&
                    arf_cmpabs_2exp_si(arb_midref(acb_realref((b + i)->coeffs)),
                        FLINT_BITS - 2) < 0)
                {
                    slong c = -arf_get_si(arb_midref(acb_realref((b + i)->coeffs)),
                        ARF_RND_NEAR);

                    /* if c >= n, terminates earlier, so no problem */
                    if (c < n)
                    {
                        start = FLINT_MAX(start, c + 1);
                    }
                }
            }
        }

        /* We should now have start <= n. */
        if (start > n) flint_abort();

        acb_hypgeom_pfq_series_sum_forward(s, t, a, p, b, q, z,
            regularized, start, len, prec);
    }
    else
    {
        acb_poly_zero(s);
        acb_poly_one(t);
    }

    if (start == n)
        return;

    acb_poly_init(u);
    acb_poly_init(v);

    rsplit(u, v, a, p, b, q, z, start, n - start, len, prec);

    acb_poly_mullow(u, u, t, len, prec);
    acb_poly_add(s, s, u, prec);

    acb_poly_mullow(t, t, v, len, prec);

    acb_poly_clear(u);
    acb_poly_clear(v);
}

