"""Yablonskii–Vorob’ev polynomials -- numberdb.org/T556.

Run with SageMath:
    sage -pip install numberdb              # once
    sage -python generate.py                 # verify every stored entry
    sage -python generate.py --fill-draft    # write entries to this draft

The fill operation requires NUMBERDB_API_KEY. For a private draft, verification
also requires its author's key. NUMBERDB_KEY_FROM_STDIN=1 reads the key from
standard input; NUMBERDB_PUBLISH=1 is the campaign wrapper's fill switch.
Neither operation offers or publishes the table.

Q_0=1, Q_1=x and Q_(n+1) Q_(n-1)=x Q_n^2-4(Q_n Q_n''-(Q_n')^2).
Reference: DLMF 32.8.6; the variable is x with no scaling. All arithmetic is
in ZZ[x], and each quotient is checked for a zero remainder. The finite
selection is documented in argument-selection.md. checks.py independently
reconstructs the polynomials by a determinant and verifies the Painleve II
rational-solution identity.
"""
import os
import sys
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing

R = PolynomialRing(ZZ, 'x')
x = R.gen()
MAX_N = 12  # Q_12 has 1076 characters; Q_13 has 1358.


def polynomials(max_n):
    """Compute all Q_n through max_n by exact differential recurrence."""
    if max_n < 0:
        raise ValueError('n must be nonnegative')
    values = [R.one(), x]
    for n in range(1, max_n):
        q = values[n]
        numerator = x*q*q - 4*(q*q.derivative(2)-q.derivative()**2)
        quotient, remainder = numerator.quo_rem(values[n-1])
        if remainder:
            raise ArithmeticError('Nonzero remainder at index %d' % (n+1))
        values.append(R(quotient))
    return values[:max_n+1]


class YablonskiiVorobev(numberdb.Generator):
    table = 'T556'
    parameters = ('n',)
    type = 'Z[]'
    rigour = 'exact'
    files = ('generate.py', 'checks.py', 'check.py', 'argument-selection.md',
             'source-notes.md', 'skill-manifest.json', 'skill-evidence.json')

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

    def value(self, params, digits=100):
        n = int(params['n'])
        if n < 0:
            raise ValueError('n must be nonnegative')
        if not hasattr(self, '_values') or n >= len(self._values):
            self._values = polynomials(max(n, MAX_N))
        return self._values[n]


def configure_key_from_stdin():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=', 1)[0].strip().isidentifier():
            key = key.split('=', 1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=key)


if __name__ == '__main__':
    configure_key_from_stdin()
    generator = YablonskiiVorobev()
    if '--fill-draft' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill exact polynomials after independent determinant and differential-equation checks.'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)
