r"""
Bernoulli numbers -- numberdb.org/T13

Run it with SageMath:

    $ sage -pip install numberdb          # once
    $ sage -python generate.sage          # check the table against this code
    $ sage -python generate.sage --publish  # send it, with NUMBERDB_API_KEY set

This generator writes the exact Bernoulli numbers $B_n$ with
$t/(e^t-1)=\sum B_n t^n/n!$. The default range is $0\leq n\leq 250$.
"""

import os
import sys
from math import comb

import numberdb.sage as numberdb
from sage.arith.misc import bernoulli
from sage.rings.rational_field import QQ


DEFAULT_LIMIT = 250


class BernoulliNumbers(numberdb.Generator):
    table = 'T13'
    parameters = ('n',)
    type = 'Q'
    rigour = 'exact'

    def enumerate(self, limit=DEFAULT_LIMIT):
        for n in range(limit + 1):
            yield {'n': n}

    def value(self, params, digits):
        return bernoulli(int(params['n']))


def bernoulli_by_recurrence(limit):
    """Compute B_n from sum binomial(m+1,k) B_k = 0 for m >= 1."""
    values = [QQ(1)]
    for m in range(1, limit + 1):
        previous = sum(QQ(comb(m + 1, k)) * values[k] for k in range(m))
        values.append(-previous / QQ(m + 1))
    return values


def independent_check(limit):
    generated = BernoulliNumbers()
    recurrence = bernoulli_by_recurrence(limit)
    for n, expected in enumerate(recurrence):
        found = generated.value({'n': n}, digits=0)
        if found != expected:
            raise AssertionError(
                'B_%d: Sage gave %s, recurrence gave %s'
                % (n, found, expected)
            )
    print('independent recurrence check passed for 0 <= n <= %d' % limit)


def limit_from_args(args):
    if '--limit' not in args:
        return DEFAULT_LIMIT
    index = args.index('--limit')
    try:
        return int(args[index + 1])
    except (IndexError, ValueError):
        raise SystemExit('--limit must be followed by an integer')


def main(args):
    limit = limit_from_args(args)
    independent_check(limit)
    generator = BernoulliNumbers()
    publish = (
        '--publish' in args
        or os.environ.get('NUMBERDB_PUBLISH', '').strip() == '1'
    )

    if publish:
        if os.environ.get('NUMBERDB_KEY_FROM_STDIN', '').strip() == '1':
            key = sys.stdin.read().strip()
            os.environ['NUMBERDB_API_KEY'] = key
        outcome = generator.publish(
            overwrite=False,
            limit=limit,
            message='extended Bernoulli numbers to n = %d' % limit,
        )
    else:
        outcome = generator.preview(overwrite=False, limit=limit)
    print(outcome)


if __name__ == '__main__':
    main(sys.argv[1:])
