"""Independent repair checks for lambda_g Hodge integrals, numberdb.org/T462.

Run with SageMath and numberdb installed, alongside the attached generate.py:
    sage -python repair_checks.py T462.json
Without a JSON filename, use T462-check-input.json if present; otherwise read
T462 through numberdb (its owner's API key is needed while it is a draft).
Checks are read-only. This script does not write values or offer a draft.

The Bernoulli formula uses Sage's Bernoulli numbers, independently of the
attached generator's reciprocal-sine recurrence. A separate genus-zero
string recursion checks the genus-zero formula. Permutations are enumerated
to check the moved profile convention on every stored exponent profile.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
# Initialize mixed Python-int/rational coercions on a named-import Sage.
from sage.rings.real_mpfr import RealField
from sage.arith.misc import bernoulli
from collections import Counter
from functools import lru_cache
from itertools import permutations
from math import factorial
from pathlib import Path
import json
import sys
from generate import integral as generator_integral, LambdaGHodge

ROOT = Path(__file__).resolve().parent


def scalar(value):
    return value['number'] if isinstance(value, dict) else value


def independent_integral(g, ds):
    n = len(ds)
    assert g >= 0 and all(d >= 0 for d in ds)
    assert 2*g-2+n > 0 and sum(ds) == 2*g-3+n
    base = QQ(1) if g == 0 else (1-QQ(1)/2**(2*g-1))*abs(bernoulli(2*g))/factorial(2*g)
    value = base*factorial(sum(ds))
    for d in ds:
        value /= factorial(d)
    return value


@lru_cache(None)
def zero_string(ds):
    if len(ds) < 3 or min(ds) < 0 or sum(ds) != len(ds)-3:
        return QQ(0)
    if ds == (0,0,0):
        return QQ(1)
    rest = list(ds)
    rest.remove(0)
    result = QQ(0)
    for j,d in enumerate(rest):
        if d:
            reduced = rest.copy()
            reduced[j] -= 1
            result += zero_string(tuple(sorted(reduced)))
    return result


def enumerate_profiles(total, n, minimum=0):
    if n == 0:
        if total == 0:
            yield ()
        return
    for d in range(minimum, total//n+1):
        for tail in enumerate_profiles(total-d, n-1, d):
            yield (d,)+tail


def check(doc):
    assert independent_integral(1,(0,)) == QQ(1)/24
    assert independent_integral(2,(1,2)) == QQ(7)/1920
    assert independent_integral(3,(1,2,3)) == QQ(31)/16128
    assert independent_integral(1,(0,)) != QQ(1)/12
    assert zero_string((0,0,0)) == 1
    assert zero_string((0,0,0,1,1)) == 2
    values = {(int(g),tuple(map(int,ds.split(',')))):QQ(scalar(v))
              for g,ns in doc['Numbers'].items() for n,rows in ns.items()
              for ds,v in rows.items()}
    expected = {(g,ds) for g in range(1,6) for n in range(1,6)
                for ds in enumerate_profiles(2*g-3+n,n)}
    assert len(values) == 232 and set(values) == expected
    gen = LambdaGHodge()
    actual = {}
    for p in gen.enumerate():
        actual[(int(p['g']),tuple(map(int,p['d'].split(','))))] = QQ(scalar(gen.value(p)))
    assert actual == values
    ns = {}
    exec(doc['Programs']['sage']['code'], ns)
    count_permutations = 0
    count_strings = count_dilatons = 0
    for (g,ds),value in values.items():
        assert value == independent_integral(g,ds) == ns['lambda_g_integral'](g,list(ds))
        for perm in set(permutations(ds)):
            assert generator_integral(g,perm) == value
            assert independent_integral(g,perm) == value
            count_permutations += 1
        if 0 in ds and len(ds)>1:
            rest=list(ds);rest.remove(0)
            rhs=QQ(0)
            for j,d in enumerate(rest):
                if d:
                    lowered=rest.copy();lowered[j]-=1
                    rhs += values[(g,tuple(sorted(lowered)))]
            assert rhs == value
            count_strings += 1
        if 1 in ds and len(ds)>1:
            rest=list(ds);rest.remove(1)
            assert value == (2*g-2+len(rest))*values[(g,tuple(rest))]
            count_dilatons += 1
    genus_zero=0
    for n in range(3,9):
        for ds in enumerate_profiles(n-3,n):
            assert independent_integral(0,ds) == zero_string(ds) == generator_integral(0,ds)
            genus_zero += 1
    assert ns['lambda_g_integral'](0,[0,0,0,1,1]) == 2
    result = {'exact_stored_entries':len(values),'entries_by_genus':dict(Counter(g for g,ds in values)),
              'permuted_profiles_checked':count_permutations,'string_equations':count_strings,
              'dilaton_equations':count_dilatons,'genus_zero_profiles':genus_zero,
              'attached_generator_matches':True,'page_program_matches':True,
              'known_and_wrong_normalization_controls_passed':True}
    print(json.dumps(result,indent=2))
    return result


if __name__ == '__main__':
    path=Path(sys.argv[1]) if len(sys.argv)>1 else ROOT/'T462-check-input.json'
    doc=json.loads(path.read_text()) if path.exists() else numberdb.table('T462')
    check(doc)
