/*
    Copyright (C) 2017 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 "arb_hypgeom.h"

#define UNROLL 4

static void
asymp_series(acb_t res, ulong n, acb_srcptr xpow, slong m, slong K, slong prec)
{
    slong j, k, khi, klo, u, r;
    fmpz * c;
    acb_t s;

    acb_init(s);
    c = _fmpz_vec_init(UNROLL + 1);

    k = K - 1;
    while (k >= 1)
    {
        u = FLINT_MIN(UNROLL, k);

        khi = k;
        klo = k - u + 1;

        for (j = klo; j <= khi; j++)
        {
            ulong aa = (2 * j - 1);
            ulong bb = (2 * j - 1);

            if (j == klo)
                fmpz_ui_mul_ui(c + khi - j, aa, bb);
            else
                fmpz_mul2_uiui(c + khi - j, c + khi - j + 1, aa, bb);
        }

        for (j = khi; j >= klo; j--)
        {
            ulong aa = (j);
            ulong bb = (2 * j + 2 * n + 1);

            if (n < UWORD_MAX / 8)
            {
                if (j == khi)
                {
                    fmpz_ui_mul_ui(c + u, aa, bb);
                }
                else
                {
                    fmpz_mul(c + khi - j, c + khi - j, c + u);
                    fmpz_mul2_uiui(c + u, c + u, aa, bb);
                }
            }
            else
            {
                fmpz_t t;
                fmpz_init(t);

                fmpz_set_ui(t, n);
                fmpz_add_ui(t, t, j);
                fmpz_mul_2exp(t, t, 1);
                fmpz_add_ui(t, t, 1);

                if (j == khi)
                {
                    fmpz_mul_ui(c + u, t, aa);
                }
                else
                {
                    fmpz_mul(c + khi - j, c + khi - j, c + u);
                    fmpz_mul_ui(t, t, aa);
                    fmpz_mul(c + u, c + u, t);
                }

                fmpz_clear(t);
            }
        }

        while (k >= klo)
        {
            r = k % m;

            if (k == khi)
            {
                acb_add(s, s, xpow + r, prec);
                acb_mul_fmpz(s, s, c + khi - k, prec);
            }
            else if (r == 0)
            {
                acb_add_fmpz(s, s, c + khi - k, prec);
            }
            else
            {
                acb_addmul_fmpz(s, xpow + r, c + khi - k, prec);
            }

            if (r == 0 && k != 0)
                acb_mul(s, s, xpow + m, prec);

            k--;
        }

        acb_div_fmpz(s, s, c + u, prec);
    }

    acb_add_ui(res, s, 1, prec);

    acb_clear(s);
    _fmpz_vec_clear(c, UNROLL + 1);
}


/* error: 0.50795 / (y^K sqrt(ny)) * [K! n! / (2^K (n+K)!)] */
/*                                   [K! / (2n)^K]          */
static void
_arb_hypgeom_legendre_p_ui_asymp_error(mag_t res, ulong n, const mag_t y, slong K)
{
    mag_t t, u;
    mag_init(t);
    mag_init(u);

    /* t = K! / (y^K sqrt(ny)) */
    mag_mul_ui_lower(t, y, n);
    mag_sqrt_lower(t, t);
    mag_pow_ui_lower(u, y, K);
    mag_mul_lower(t, t, u);
    mag_fac_ui(u, K);
    mag_div(t, u, t);

    if (K < n / 16)
    {
        /* (2n)^K */
        mag_set_ui_lower(u, n);
        mag_mul_2exp_si(u, u, 1);
        mag_pow_ui_lower(u, u, K);
        mag_div(t, t, u);
    }
    else
    {
        /* n! */
        mag_fac_ui(u, n);
        mag_mul(t, t, u);
        /* (n+K)! */
        mag_rfac_ui(u, n + K);
        mag_mul(t, t, u);
        /* 2^K */
        mag_mul_2exp_si(t, t, -K);
    }

    mag_mul_ui(t, t, 131);
    mag_mul_2exp_si(t, t, -8);

    mag_set(res, t);

    mag_clear(t);
    mag_clear(u);
}

int
arb_abs_le_ui(const arb_t x, ulong n)
{
    arf_struct u[3];
    arf_t t;
    int res;

    if (!arb_is_finite(x) || arf_cmpabs_ui(arb_midref(x), n) > 0)
        return 0;

    if (arb_is_exact(x))
        return 1;

    if (arf_sgn(arb_midref(x)) >= 0)
        arf_init_set_shallow(u + 0, arb_midref(x));
    else
        arf_init_neg_shallow(u + 0, arb_midref(x));

    arf_init_set_mag_shallow(u + 1, arb_radref(x));

    arf_init(u + 2);   /* no need to free */
    arf_set_ui(u + 2, n);
    arf_neg(u + 2, u + 2);

    arf_init(t);
    arf_sum(t, u, 3, MAG_BITS, ARF_RND_DOWN);
    res = (arf_sgn(t) < 0);
    arf_clear(t);

    return res;
}

void
_arb_hypgeom_legendre_p_ui_asymp(arb_t res, ulong n, const arb_t x,
    const arb_t y, acb_srcptr w4pow, const arb_t binom, slong m, slong K, slong prec)
{
    arb_t t, u;
    acb_t s, z;
    fmpz_t e;
    mag_t err;

    arb_init(t);
    arb_init(u);
    acb_init(s);
    acb_init(z);
    mag_init(err);
    fmpz_init(e);

    /* u = n + 1/2 */
    arb_set_d(u, 0.5);
    arb_add_ui(u, u, n, prec);

    arb_get_mag_lower(err, y);
    _arb_hypgeom_legendre_p_ui_asymp_error(err, n, err, K);

    /* z = (x + yi)^(n+0.5) * (1-i) */
    if (n < 256 || prec > 2000)
    {
        arb_set(acb_realref(z), x);
        arb_set(acb_imagref(z), y);
        acb_pow_arb(z, z, u, prec);
    }
    else
    {
        arb_atan2(t, y, x, prec);
        arb_mul(t, t, u, prec);
        arb_sin_cos(acb_imagref(z), acb_realref(z), t, prec);
    }
    arb_one(acb_realref(s));
    arb_set_si(acb_imagref(s), -1);
    acb_mul(z, z, s, prec);

    /* main series */
    asymp_series(s, n, w4pow, m, K, prec);

    /* we will use Re(z * s) */
    acb_mul(z, z, s, prec);

    /* prefactor: t = 4^n / (pi * sqrt(y) * (n+0.5) binomial(2n,n)) */
    arb_mul(t, binom, u, prec);
    arb_sqrt(u, y, prec);
    arb_mul(t, t, u, prec);
    arb_const_pi(u, prec);
    arb_mul(t, t, u, prec);

    arb_div(res, acb_realref(z), t, prec);

    fmpz_set_ui(e, n);
    arb_mul_2exp_fmpz(res, res, e);
    arb_mul_2exp_fmpz(res, res, e);

    arb_add_error_mag(res, err);

    arb_clear(t);
    arb_clear(u);
    acb_clear(s);
    acb_clear(z);
    mag_clear(err);
    fmpz_clear(e);
}

void
arb_hypgeom_legendre_p_ui_asymp(arb_t res, arb_t res2, ulong n, const arb_t x, slong K, slong prec)
{
    arb_t y, binom;
    acb_t w;
    acb_ptr w4pow;
    slong m;

    if (n == 0)
    {
        if (res != NULL) arb_one(res);
        if (res2 != NULL) arb_zero(res2);
        return;
    }

    if (!arb_abs_le_ui(x, 1))
    {
        if (res != NULL) arb_indeterminate(res);
        if (res2 != NULL) arb_indeterminate(res2);
        return;
    }

    K = FLINT_MAX(K, 1);

    if (res2 != NULL)
        m = n_sqrt(2 * K);
    else
        m = n_sqrt(K);

    arb_init(y);
    arb_init(binom);
    acb_init(w);
    w4pow = _acb_vec_init(m + 1);

    /* y = sqrt(1-x^2) */
    arb_one(y);
    arb_submul(y, x, x, 2 * prec);
    arb_sqrt(y, y, prec);

    /* w = 1 - (x/y)i */
    arb_one(acb_realref(w));
    arb_div(acb_imagref(w), x, y, prec);
    arb_neg(acb_imagref(w), acb_imagref(w));

    acb_mul_2exp_si(w, w, -2);
    _acb_vec_set_powers(w4pow, w, m + 1, prec);

    /* binomial(2n,n) */
    arb_hypgeom_central_bin_ui(binom, n, prec);

    if (res2 == NULL)
    {
        _arb_hypgeom_legendre_p_ui_asymp(res, n, x, y, w4pow, binom, m, K, prec);
    }
    else
    {
        arb_t t, u, v;

        arb_init(t);
        arb_init(u);
        arb_init(v);

        _arb_hypgeom_legendre_p_ui_asymp(t, n, x, y, w4pow, binom, m, K, prec);

        /* recurrence for central binomial */
        arb_mul_ui(binom, binom, n, prec);
        arb_set_ui(u, n);
        arb_mul_2exp_si(u, u, 2);
        arb_sub_ui(u, u, 2, prec);
        arb_div(binom, binom, u, prec);

        _arb_hypgeom_legendre_p_ui_asymp(u, n - 1, x, y, w4pow, binom, m, K, prec);

        /* P' = n (P[n-1] - x P) / (1 - x^2) */
        arb_submul(u, t, x, prec);
        arb_mul(v, x, x, 2 * prec);
        arb_neg(v, v);
        arb_add_ui(v, v, 1, prec);
        arb_div(u, u, v, prec);
        arb_mul_ui(res2, u, n, prec);
        if (res != NULL)
            arb_set(res, t);

        arb_clear(t);
        arb_clear(u);
        arb_clear(v);
    }

    arb_clear(y);
    arb_clear(binom);
    acb_clear(w);
    _acb_vec_clear(w4pow, m + 1);
}

