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

    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) 2013 Tom Bachmann

******************************************************************************/

#include <iostream>

#include "arithxx.h"

#include "helpers.h"

using namespace flint;

void
test_stirling()
{
#define STIRLING(func, matfunc, n, k) \
{\
    tassert(func##_vec(n, k).size() == k); \
    fmpz_vecxx v1(func##_vec(n, k).evaluate() /* test temporary alloc */); \
    for(slong i = 0;i < v1.size();++i) \
        tassert(v1[i] == func(n, i)); \
    tassert(func##_vec_next(func##_vec(n, k), n+1).size() == k); \
    fmpz_vecxx v2(func##_vec_next(v1, n+1)); \
    for(slong i = 0;i < v2.size();++i) \
        tassert(v2[i] == func(n+1, i)); \
    fmpz_vecxx v3(func##_vec(n, n+1)); \
    fmpz_vecxx v4(func##_vec_next(v3, n+1)); \
    tassert(v4.size() == n+2 && v3.size() == n+1); \
    for(slong i = 0;i < v4.size();++i) \
        tassert(v4[i] == func(n+1, i)); \
    tassert(matfunc(n, k).rows() == n && matfunc(n, k).cols() == k); \
    fmpz_matxx M(matfunc(n, k).evaluate() /* test temporaries */); \
    for(slong i = 0;i < M.rows();++i) \
        for(slong j = 0;j < M.cols();++j) \
            tassert(M.at(i, j) == func(i, j)); \
}
STIRLING(stirling_number_1u, stirling_matrix_1u, 10, 6)
STIRLING(stirling_number_1, stirling_matrix_1, 10, 6)
STIRLING(stirling_number_2, stirling_matrix_2, 10, 6)
}

void
test_bell()
{
    tassert(bell_number_vec(10).size() == 10);
    fmpz_vecxx v(bell_number_vec(10));
    tassert(v == bell_number_vec_recursive(10));
    tassert(v == bell_number_vec_multi_mod(10));
    for(slong i = 0;i < v.size();++i)
        tassert(v[i] == bell_number(static_cast<unsigned>(i)));
    tassert(bell_number(10u) == bell_number_bsplit(10u));
    tassert(bell_number(10u) == bell_number_multi_mod(10u));

    nmodxx_ctx p(1031);
    tassert(bell_number_nmod(10u, p) == nmodxx::red(bell_number(10u), p));
    nmod_vecxx v2(bell_number_nmod_vec(10u, p));
    tassert(v2.size() == 10);
    for(slong i = 0;i < v2.size();++i)
        tassert(v2[i] == nmodxx::red(v[i], p));

    tassert(v2 == bell_number_nmod_vec_series(10u, p));
    tassert(v2 == bell_number_nmod_vec_recursive(10u, p));

    tassert(fmpzxx(2).pow(
                static_cast<unsigned> (bell_number_size(10u)))
            > bell_number(10u));
}

void
test_bernoulli()
{
    tassert(bernoulli_number(10u).den() == bernoulli_number_denom(10u));
    tassert(fmpqxx(2, 1u).pow(
                static_cast<slong> (bernoulli_number_size(10u)))
            > bernoulli_number(10u));
    fmpq_polyxx poly(bernoulli_polynomial(10u));
    tassert(poly.degree() == 10);
    for(slong i = 0;i < poly.length();++i)
        tassert(poly.get_coeff(i)
                == fmpqxx(bin(10u, (unsigned) i)*bernoulli_number(10u - (unsigned) i)));
    tassert(bernoulli_number_vec(10u).size() == 10);
    for(unsigned i = 0;i < 10;++i)
        tassert(bernoulli_number_vec(10u)[i] == bernoulli_number(i));
}

void
test_euler()
{
    tassert(fmpzxx(2).pow(
                static_cast<unsigned> (euler_number_size(10u)))
            > euler_number(10u));
    fmpq_polyxx poly(euler_polynomial(10u));
    tassert(poly.degree() == 10);
    tassert(poly(fmpqxx(1, 2u))*fmpqxx(2, 1u).pow(10)
            == fmpqxx(euler_number(10u).evaluate(), fmpzxx(1)));
    tassert(euler_number_vec(10u).size() == 10);
    for(unsigned i = 0;i < 10;++i)
        tassert(euler_number_vec(10u)[i] == euler_number(i));
}

void
test_legendre()
{
    const unsigned short N = 10;
    fmpq_polyxx f; f = "3  -1 0 1";
    f = f.pow(N);
    for(unsigned i = 0;i < N;++i)
        f = f.derivative();
    tassert(legendre_polynomial(N)
            == f * fmpqxx(fmpzxx(1), (fmpzxx(2).pow(N)*fac(N)).evaluate()));

    fmpq_polyxx x; x = "2  0 1";
    f = chebyshev_t_polynomial(N);
    f = f(cos_series(x, N));
    f.truncate(N);
    tassert(f == cos_series(N*x, N));
    tassert(chebyshev_u_polynomial(N)*(N + 1)
            == chebyshev_t_polynomial(N+1u).derivative());
}

void
test_multiplicative()
{
    fmpzxx p(1031);
    tassert(euler_phi(p) == p-1u);
    tassert(moebius_mu(p) == -1);
    tassert(divisor_sigma(p, 4u) == 1u + p.pow(4u));
    tassert(divisors(p).to_string() == "2  1 " + p.to_string());

    fmpz_polyxx q, one;
    q = "2  0 1";
    one = "1  1";
    fmpz_polyxx res(q * (one - q).pow(24u) * (one - q*q).pow(24u));
    for(unsigned i = 3;i < 10;++i)
        res *= (one - q.pow(i)).pow(24u);
    res.truncate(10);
    tassert(res == ramanujan_tau_series(10));
    for(int i = 0;i < 10;++i)
        tassert(ramanujan_tau_series(i+1).get_coeff(i)
                == ramanujan_tau(fmpzxx(i)));
}

void
test_polys()
{
    // just very basic tests ...
    ulong N = 1234;
    tassert(cyclotomic_polynomial(N).degree() == euler_phi(fmpzxx(N)));
    tassert(cos_minpoly(N).degree() == euler_phi(fmpzxx(N))/2);

    tassert(swinnerton_dyer_polynomial(8u).degree() == fmpzxx(2).pow(8u));
}

void
test_dedekind()
{
    frandxx state;
    fmpzxx h = fmpzxx::randtest_unsigned(state, 10);
    fmpzxx k = fmpzxx::randtest_unsigned(state, 10);
    tassert(dedekind_sum_naive(h, k) == dedekind_sum(h, k));
    k /= gcd(h, k);
    tassert(dedekind_sum_coprime_large(h, k) == dedekind_sum(h, k));
    tassert(dedekind_sum_coprime(h, k) == dedekind_sum(h, k));
    // untested: dedekind_sum_coprime_d
}

void
test_number_of_partitions()
{
    unsigned short N = 15;
    nmodxx_ctx p(1031);
    fmpz_vecxx v1(number_of_partitions_vec(N));
    nmod_vecxx v2(number_of_partitions_nmod_vec(N, p));
    tassert(v1.size() == v2.size() && v1.size() == N);
    for(unsigned i = 0;i < N;++i)
    {
        tassert(v1[i] == number_of_partitions(i));
        tassert(nmodxx::red(v1[i], p) == v2[i]);
    }
}

void
test_sum_of_squares()
{
    unsigned short N = 15;
    fmpz_vecxx v(sum_of_squares_vec(7u, N));
    tassert(v.size() == N);
    for(unsigned i = 0;i < N;++i)
        tassert(v[i] == sum_of_squares(7u, fmpzxx(i)));
}

int
main()
{
    std::cout << "arithxx....";

    tassert(primorial(4) == 2*3);
    tassert(harmonic_number(3)
            == fmpqxx(1, 1u) + fmpqxx(1, 2u) + fmpqxx(1, 3u));

    test_stirling();
    test_bell();
    test_bernoulli();
    test_euler();
    test_legendre();
    test_multiplicative();
    test_polys();
    test_dedekind();
    test_number_of_partitions();
    test_sum_of_squares();

    tassert(landau_function_vec(9)[8] == 15);

    std::cout << "PASS\n";
    return 0;
}
