/*=============================================================================

    This file is part of FLINT.

    FLINT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    FLINT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with FLINT; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

=============================================================================*/
/******************************************************************************

    Copyright (C) 2012 Sebastian Pancratz
 
******************************************************************************/

#include "fmpz_mod_poly.h"
#include "qadic.h"

extern slong _padic_log_bound(slong v, slong N, const fmpz_t p);

/*
    Assumes that P, T are vectors of length 2 d - 1.

    Assumes that 0 < len <= d.

    Assumes that 1 <= lo < hi.

    Does not support aliasing.
 */

static void 
_qadic_log_bsplit_series(fmpz *P, fmpz_t B, fmpz *T, 
                         const fmpz *y, slong len, slong lo, slong hi, 
                         const fmpz *a, const slong *j, slong lena)
{
    const slong d = j[lena - 1];

    if (hi - lo == 1)
    {
        _fmpz_vec_set(P, y, len);
        _fmpz_vec_zero(P + len, 2*d - 1 - len);

        fmpz_set_si(B, lo);

        _fmpz_vec_set(T, P, 2*d - 1);
    }
    else if (hi - lo == 2)
    {
        _fmpz_poly_sqr(P, y, len);
        _fmpz_vec_zero(P + (2*len - 1), d - (2*len - 1));
        _fmpz_poly_reduce(P, 2*len - 1, a, j, lena);

        fmpz_set_si(B, lo);
        fmpz_mul_si(B, B, lo + 1);

        _fmpz_vec_scalar_mul_si(T, y, len, lo + 1);
        _fmpz_vec_zero(T + len, d - len);
        _fmpz_vec_scalar_addmul_si(T, P, d, lo);
    }
    else
    {
        const slong m = (lo + hi) / 2;

        fmpz *RP, *RT, *W;
        fmpz_t RB;

        RP = _fmpz_vec_init(2*d - 1);
        RT = _fmpz_vec_init(2*d - 1);
        W  = _fmpz_vec_init(2*d - 1);
        fmpz_init(RB);

        _qadic_log_bsplit_series(P, B, T, y, len, lo, m, a, j, lena);

        _qadic_log_bsplit_series(RP, RB, RT, y, len, m, hi, a, j, lena);

        _fmpz_poly_mul(W, RT, d, P, d);
        _fmpz_poly_reduce(W, 2*d - 1, a, j, lena);
        _fmpz_vec_swap(RT, W, d);

        _fmpz_vec_scalar_mul_fmpz(T, T, d, RB);
        _fmpz_vec_scalar_addmul_fmpz(T, RT, d, B);

        _fmpz_poly_mul(W, P, d, RP, d);
        _fmpz_poly_reduce(W, 2*d - 1, a, j, lena);
        _fmpz_vec_swap(P, W, d);

        fmpz_mul(B, B, RB);

        _fmpz_vec_clear(RP, 2*d - 1);
        _fmpz_vec_clear(RT, 2*d - 1);
        _fmpz_vec_clear(W,  2*d - 1);
        fmpz_clear(RB);
    }
}

/*
    Sets (z, d) to the sum 

        sum_{i=1}^{\infty} y^i / i mod p^N.

    The result may not be reduced modulo p^N, but it is 
    reduced modulo f(X) given by the data (a, j, lena).

    Supports aliasing between y and z.
 */

static void 
_qadic_log_bsplit(fmpz *z, const fmpz *y, slong v, slong len, 
                  const fmpz *a, const slong *j, slong lena, 
                  const fmpz_t p, slong N)
{
    const slong d = j[lena - 1];

    fmpz *P, *T;
    fmpz_t B, C;
    slong n;

    n = _padic_log_bound(v, N, p);
    n = FLINT_MAX(n, 2);

    P = _fmpz_vec_init(2*d - 1);
    T = _fmpz_vec_init(2*d - 1);
    fmpz_init(B);
    fmpz_init(C);

    _qadic_log_bsplit_series(P, B, T, y, len, 1, n, a, j, lena);

    n = fmpz_remove(B, B, p);
    fmpz_pow_ui(C, p, n);
    _fmpz_vec_scalar_divexact_fmpz(T, T, d, C);

    _padic_inv(B, B, p, N);
    _fmpz_vec_scalar_mul_fmpz(z, T, d, B);

    _fmpz_vec_clear(P, 2*d - 1);
    _fmpz_vec_clear(T, 2*d - 1);
    fmpz_clear(B);
    fmpz_clear(C);
}

/*
    Computes 
    \begin{equation*}
    z = - \sum_{i = 1}^{\infty} \frac{y^i}{i} \pmod{p^N}.
    \end{equation*}

    Note that this can be used to compute the $p$-adic logarithm 
    via the equation 
    \begin{align*}
    \log(x) & = \sum_{i=1}^{\infty} (-1)^{i-1} \frac{(x-1)^i}{i} \\
            & = - \sum_{i=1}^{\infty} \frac{(1-x)^i}{i}.
    \end{align*}

    Assumes that $y = 1 - x$ is non-zero and that $v = \ord_p(y)$ 
    is at least $1$ when $p$ is odd and at least $2$ when $p = 2$ 
    so that the series converges.

    Assumes that $v < N$.

    Sets $(z, d)$.

    Ensures that the result is reduced modulo $p^N$.

    Does not support aliasing between $y$ and $z$.
 */

void _qadic_log_balanced(fmpz *z, const fmpz *y, slong len, 
                         const fmpz *a, const slong *j, slong lena, 
                         const fmpz_t p, slong N, const fmpz_t pN)
{
    const slong d = j[lena - 1];

    fmpz_t pv;
    fmpz *r, *s, *t, *u;
    slong i, w;

    r = _fmpz_vec_init(d);
    s = _fmpz_vec_init(2*d - 1);
    t = _fmpz_vec_init(d);
    u = _fmpz_vec_init(d);
    fmpz_init(pv);

    fmpz_set(pv, p);
    _fmpz_vec_scalar_mod_fmpz(t, y, len, pN);
    _fmpz_vec_zero(z, d);
    w = 1;

    while (!_fmpz_vec_is_zero(t, d))
    {
        fmpz_mul(pv, pv, pv);

        for (i = 0; i < d; i++)
        {
            fmpz_fdiv_qr(t + i, r + i, t + i, pv);
        }

        if (!_fmpz_vec_is_zero(t, d))
        {
            _fmpz_vec_scalar_mul_fmpz(t, t, d, pv);

            /* Temporarily set r = 1 - r to compute u = (1-r)^{-1} */
            fmpz_sub_ui(r + 0, r + 0, 1);
            _fmpz_vec_neg(r, r, d);
            _qadic_inv(u, r, d, a, j, lena, p, N);
            _fmpz_vec_neg(r, r, d);
            fmpz_add_ui(r + 0, r + 0, 1);

            _fmpz_poly_mul(s, t, d, u, d);
            _fmpz_poly_reduce(s, 2 * d - 1, a, j, lena);
            _fmpz_vec_scalar_mod_fmpz(t, s, d, pN);
        }

        if (!_fmpz_vec_is_zero(r, d))
        {
            _qadic_log_bsplit(r, r, w, d, a, j, lena, p, N);
            _fmpz_vec_sub(z, z, r, d);
            _fmpz_vec_scalar_mod_fmpz(z, z, d, pN);
        }

        w *= 2;
    }

    _fmpz_vec_clear(r, d);
    _fmpz_vec_clear(s, 2*d - 1);
    _fmpz_vec_clear(t, d);
    _fmpz_vec_clear(u, d);
    fmpz_clear(pv);
}

int qadic_log_balanced(qadic_t rop, const qadic_t op, const qadic_ctx_t ctx)
{
    const fmpz *p  = (&ctx->pctx)->p;
    const slong d   = qadic_ctx_degree(ctx);
    const slong N   = qadic_prec(rop);
    const slong len = op->length;

    if (op->val < 0)
    {
        return 0;
    }
    else
    {
        fmpz *x;
        fmpz_t pN;
        int alloc, ans;

        x = _fmpz_vec_init(len + 1);
        alloc = _padic_ctx_pow_ui(pN, N, &ctx->pctx);

        /* Set x := (1 - op) mod p^N */
        fmpz_pow_ui(x + len, p, op->val);
        _fmpz_vec_scalar_mul_fmpz(x, op->coeffs, len, x + len);
        fmpz_sub_ui(x + 0, x + 0, 1);
        _fmpz_vec_neg(x, x, len);
        _fmpz_vec_scalar_mod_fmpz(x, x, len, pN);

        if (_fmpz_vec_is_zero(x, len))
        {
            padic_poly_zero(rop);
            ans = 1;
        }
        else
        {
            const slong v = _fmpz_vec_ord_p(x, len, p);

            if (v >= 2 || (*p != WORD(2) && v >= 1))
            {
                if (v >= N)
                {
                    padic_poly_zero(rop);
                }
                else
                {
                    padic_poly_fit_length(rop, d);

                    _qadic_log_balanced(rop->coeffs, x, len, 
                                        ctx->a, ctx->j, ctx->len, p, N, pN);
                    rop->val = 0;

                    _padic_poly_set_length(rop, d);
                    _padic_poly_normalise(rop);
                    padic_poly_canonicalise(rop, p);
                }
                ans = 1;
            }
            else
            {
                ans = 0;
            }
        }

        _fmpz_vec_clear(x, len + 1);
        if (alloc)
            fmpz_clear(pN);
        return ans;
    }
}

