"""Session 53: exact histogram bounds for arbitrary page-phase permutations.
No A/I singleton premise, language fitting, or key search.
"""
from pathlib import Path
from itertools import permutations,product
import hashlib,json
import numpy as np
from lp_core import vals,LET
from word_core import GROUPS
from reset_state_audit import ROWS,SOURCE
ROOT=Path(__file__).resolve().parent

def entropy(h):
    p=h[h>0]/h.sum();return float(-np.sum(p*np.log2(p)))

def bounds(h):
    # Align rank-ordered row counts. Each row's permutation is unrestricted.
    concentrated=np.sort(h,axis=1)[:,::-1].sum(0)
    return {'runes':int(h.sum()),'states':len(h),'maximum_single_rune_count':int(concentrated[0]),'maximum_single_rune_frequency':float(concentrated[0]/h.sum()),'minimum_entropy_bits':entropy(concentrated),'concentrated_histogram':concentrated.tolist(),'max_top_k_fractions':(np.cumsum(concentrated)/h.sum()).tolist()}

def histogram_info(p):
    h=np.bincount(p,minlength=29)
    return {'runes':len(p),'histogram':h.tolist(),'peak_label':LET[int(h.argmax())],'peak_frequency':float(h.max()/h.sum()),'entropy_bits':entropy(h),'sorted_cumulative_fractions':(np.cumsum(np.sort(h)[::-1])/len(p)).tolist()}

def main():
    sec=np.concatenate([np.full(len(vals(s)),i) for i,s in enumerate(SOURCE.split('$'))]);v=vals(SOURCE)
    keep=np.isin(sec,[6,8]);v=v[keep];sec=sec[keep];page=np.array([r['page'] for r in ROWS])[keep]
    models=[]
    for period in [21,42]:
        phase=page%period;h=np.zeros((period,29),dtype=int);np.add.at(h,(phase,v),1)
        separate=np.zeros((2*period,29),dtype=int);np.add.at(separate,(phase+(sec==8)*period,v),1)
        models.append({'period':period,'shared_sections_6_8':bounds(h),'separate_section_tables':bounds(separate),'row_counts':h.tolist()})
    benchmarks={k:histogram_info(vals(s)) for k,s in GROUPS.items()}
    benchmarks['pooled']=histogram_info(np.concatenate([vals(s) for s in GROUPS.values()]))
    for model in models:
        for name in ['shared_sections_6_8','separate_section_tables']:
            b=model[name]
            b['benchmark_top_k_violations']={k:(np.flatnonzero(np.array(b['max_top_k_fractions'][:-1])+1e-12<np.array(p['sorted_cumulative_fractions'][:-1]))+1).tolist() for k,p in benchmarks.items()}
    rng=np.random.default_rng(53001);controls=[];phase=page%42
    for group in ['koan','divinity']:
        original=vals(GROUPS[group]);p=np.resize(original,len(v));maps=np.array([rng.permutation(29) for _ in range(42)])
        c=maps[phase,p];inverse=np.argsort(maps,axis=1);decoded=inverse[phase,c]
        assert np.array_equal(decoded,p)
        h=np.zeros((42,29),int);np.add.at(h,(phase,c),1);b=bounds(h);truth=histogram_info(p)
        assert np.all(np.array(b['max_top_k_fractions'])+1e-12>=np.array(truth['sorted_cumulative_fractions']))
        controls.append({'source_group':group,'plaintext_repeated_to_fill_real_positions':True,'exact_inverse_recovery':True,'true_plaintext':truth,'bound':b})
    # All 6^2 possible substitution tables in a three-symbol, two-state fixture.
    h=np.array([[3,7,1],[6,2,4]]);observed=[]
    for a,b in product(permutations(range(3)),repeat=2):
        out=np.zeros(3,dtype=int)
        for row,m in zip(h,[a,b]):
            for c,p in enumerate(m):out[p]+=row[c]
        observed.append((int(out.max()),entropy(out)))
    analytic=bounds(h)
    assert max(x[0] for x in observed)==analytic['maximum_single_rune_count']
    assert abs(min(x[1] for x in observed)-analytic['minimum_entropy_bits'])<1e-12
    out={'source_sha256':hashlib.sha256((ROOT/'liber-primus__transcription--master.txt').read_bytes()).hexdigest(),'scope':{'sections':[6,8],'section_lengths':[int(sum(sec==i)) for i in [6,8]],'runes':len(v),'counter':'zero-based rune offset from each page start; punctuation does not consume positions','assumptions':'One bijective rune substitution per phase; same table across sections unless separate sensitivity stated; no extra state, skips, insertions, deletions or reordered input'},'models':models,'benchmarks':benchmarks,'controls':controls,'small_fixture':{'tables_exhausted':len(observed),'peak_and_entropy_extrema_agree':True}}
    (ROOT/'phase_frequency_results.json').write_text(json.dumps(out,indent=2)+'\n')
    for m in models:
        for mode in ['shared_sections_6_8','separate_section_tables']:print(m['period'],mode,m[mode]['maximum_single_rune_count'],m[mode]['maximum_single_rune_frequency'],m[mode]['minimum_entropy_bits'])
    print('benchmark peaks',{k:p['peak_frequency'] for k,p in benchmarks.items()})
    print('controls',[(c['source_group'],c['true_plaintext']['peak_frequency'],c['bound']['maximum_single_rune_frequency']) for c in controls])
if __name__=='__main__':main()
