"""Independent checks of Landau constants -- numberdb.org/T601.

    sage -python checks.py
    sage -python checks.py --stored   # private draft needs NUMBERDB_API_KEY

Uses the attached check.py from the campaign's standard checking library.
Controls are run before the full-range computation. The lattice algorithm
propagates counts over the four neighbours of each integer point; it neither
calls the generator's recurrence nor uses binomial coefficients. Its return
counts at time 2*k, divided by 4**(2*k), are the summands of G_n: after the
coordinate change (u,v)=(x+y,x-y), each step chooses the signs of u and v
independently. Returning after 2*k steps requires k of each sign on each
axis, giving binomial(2*k,k)**2 choices. This supplies an external combinatorial
check of every exact value, not a second implementation of the recurrence.
"""
import itertools
import json
import os
from math import comb
from pathlib import Path
from time import perf_counter

import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ

from generate import LandauConstants, authenticate_stdin
from check import agrees_with, exactness, measure, names_its_rings


STEPS = ((1, 0), (-1, 0), (0, 1), (0, -1))


def walk_returns(maximum):
    positions = {(0, 0): 1}
    returns = [1]
    for step in range(1, 2 * maximum + 1):
        following = {}
        for (x, y), count in positions.items():
            for dx, dy in STEPS:
                target = (x + dx, y + dy)
                following[target] = following.get(target, 0) + count
        positions = following
        assert sum(positions.values()) == 4 ** step
        if step % 2 == 0:
            returns.append(positions.get((0, 0), 0))
        else:
            assert positions.get((0, 0), 0) == 0
    return returns


def controls():
    # Enumerate all 256 four-step paths, independently of dynamic programming.
    brute = sum(sum(step[0] for step in path) == 0 and
                sum(step[1] for step in path) == 0
                for path in itertools.product(STEPS, repeat=4))
    assert brute == 36
    assert walk_returns(2) == [1, 4, 36]
    # A deliberately false value must be rejected by the standard comparator.
    assert agrees_with({0: QQ(2)}, lambda n: QQ(1))
    assert exactness({0: 1.0})
    return {'walk_return_counts_at_0_2_4_steps': [1, 4, brute],
            'false_comparison_rejected': True, 'float_rejected': True}


def closed_sum(n):
    return sum((QQ(comb(2*k, k)**2) / QQ(16**k)
                for k in range(n + 1)), QQ(0))


def identities(values):
    assert values[0] == 1
    assert values[1] == QQ(5) / QQ(4)
    assert values[2] == QQ(89) / QQ(64)
    assert values[3] == QQ(381) / QQ(256)
    assert not agrees_with(values, closed_sum)
    for n in range(1, 100):
        # Denominator-cleared three-term recurrence.
        assert 4*(n+1)**2*(values[n+1]-values[n]) == (2*n+1)**2*(values[n]-values[n-1])
        assert 0 < values[n+1]-values[n] < values[n]-values[n-1]
    assert values[1] > values[0]
    for value in values.values():
        assert value.denominator() > 0
        assert value.numerator().gcd(value.denominator()) == 1


def get_stored():
    document = numberdb.table('T601')
    assert document['Data properties']['type'] == 'Q'
    assert document['Data properties']['rigour'] == 'exact'
    block = document['Numbers']
    assert isinstance(block, dict)
    assert list(block) == [str(n) for n in range(101)]
    values = {}
    for key, entry in block.items():
        raw = entry['number'] if isinstance(entry, dict) else entry
        assert isinstance(raw, str)
        assert '.' not in raw and 'e' not in raw.lower()
        numerator, separator, denominator = raw.partition('/')
        value = QQ(int(numerator)) / QQ(int(denominator) if separator else 1)
        assert raw == str(value)
        values[int(key)] = value
    return values


def main():
    start = perf_counter()
    control = controls()
    generator = LandauConstants()
    assert list(generator.enumerate()) == [{'n': n} for n in range(101)]
    assert not names_its_rings(str(Path(__file__).with_name('generate.py')))
    values = {p['n']: generator.value(p, 100) for p in generator.enumerate()}
    assert not exactness(values)
    identities(values)
    returns = walk_returns(100)
    total = QQ(0)
    independent = {}
    for n, count in enumerate(returns):
        total += QQ(count) / QQ(4**(2*n))
        independent[n] = total
    assert not agrees_with(values, lambda n: independent[n])
    stored_checked = False
    if '--stored' in __import__('sys').argv or os.environ.get('CHECK_STORED') == '1':
        authenticate_stdin()
        stored = get_stored()
        identities(stored)
        assert not agrees_with(stored, lambda n: independent[n])
        assert stored == values
        stored_checked = True
    report = {
        'table': 'T601',
        'skill_manifest': 'agents/runs/20261029T051809Z-build-skills/manifest.json',
        'skill_sha256': '28ecbeeccb0c5e5d6eb541b5a5b0f88aec47b154374d909085116d34f5629199',
        'controls': control,
        'enumeration': {'parameter': 'n', 'minimum': 0, 'maximum': 100, 'count': 101},
        'measure': measure(values),
        'exactness_passed': True,
        'closed_sum_matches': 101,
        'square_lattice_walk_matches': 101,
        'three_term_recurrence_matches': 99,
        'strict_increase_and_decreasing_increments': True,
        'stored_checked': stored_checked,
        'samples': {str(n): str(values[n]) for n in [0,1,2,3,10,100]},
        'versions': {'numberdb': numberdb.__version__ if hasattr(numberdb, '__version__') else 'see package metadata'},
        'elapsed_seconds': round(perf_counter()-start, 3)
    }
    print(json.dumps(report, indent=2))


if __name__ == '__main__':
    main()
