"""Session 40. Exact peak-frequency limits for word-reset two-rune feedback.
Requires Python 3 and NumPy; all source inputs local. No plaintext/key-word search.
"""
from pathlib import Path
import hashlib,json
import numpy as np
from word_core import R,LET,vals,lp,GROUPS,tokenize
ROOT=Path(__file__).resolve().parent
EXPECTED='b0b2e13c3cd35512ee5955a2d9a56c22d7410e283d35dd9c90abbaa7b99279eb'

def words(source):
    return [np.array(w['values'],dtype=np.int64) for w in tokenize(source)]

def hist(ws,k=29):
    return np.bincount(np.concatenate(ws),minlength=k)

def decode(ws,A,B,C,K,initial,k=29):
    output=[]
    for w in ws:
        padded=np.r_[initial,w]
        output.append((A*padded[2:]+B*padded[1:-1]+C*padded[:-2]+K)%k)
    return output

def encode(ws,A,B,C,K,initial,k=29):
    output=[];inverse=pow(A,-1,k)
    for w in ws:
        older,prev=initial;c=[]
        for x in w:
            current=((int(x)-B*prev-C*older-K)*inverse)%k
            c.append(current);older,prev=prev,current
        output.append(np.array(c,dtype=np.int64))
    return output

def bound(ws,k=29,keep=False):
    first=np.array([w[0] for w in ws]);second=np.array([w[1] for w in ws if len(w)>1]);before_second=np.array([w[0] for w in ws if len(w)>1])
    triples=np.array([(w[j],w[j-1],w[j-2]) for w in ws for j in range(2,len(w))],dtype=np.int64).reshape(-1,3)
    hfirst=np.bincount(first,minlength=k);first_peak=int(hfirst.max());first_bin=int(hfirst.argmax())
    total=sum(map(len,ws));records=[]
    for a in range(k):
        hsecond=np.bincount((second+a*before_second)%k,minlength=k)
        second_peak=int(hsecond.max());second_bin=int(hsecond.argmax())
        for b in range(1,k):
            interior=(triples[:,0]+a*triples[:,1]+b*triples[:,2])%k
            hinterior=np.bincount(interior,minlength=k);target=int(hinterior.argmax())
            peak=first_peak+second_peak+int(hinterior.max())
            # u and v independently range over all residues because b != 0.
            u=(target-first_bin)%k;v=(target-second_bin)%k;inv=pow(b,-1,k)
            prev=v*inv%k;older=(u-a*prev)*inv%k
            records.append(dict(a=a,b=b,peak_count=peak,interior_peak_count=int(hinterior.max()),
                second_peak_count=second_peak,target_bin=target,initial=[older,prev],
                first_histogram=hfirst.tolist(),second_histogram=hsecond.tolist(),interior_histogram=hinterior.tolist()))
    best=max(records,key=lambda x:x['peak_count'])
    witness=hist(decode(ws,1,best['a'],best['b'],0,best['initial'],k),k)
    assert int(witness[best['target_bin']])==best['peak_count'] and int(witness.max())==best['peak_count']
    result=dict(runes=total,words=len(ws),first_positions=len(first),second_positions=len(second),interior_positions=len(triples),
        normalized_rules=len(records),maximum_single_rune_count=best['peak_count'],maximum_single_rune_frequency=best['peak_count']/total,
        first_position_peak_count=first_peak,witness=dict(A=1,B=best['a'],C=best['b'],K=0,initial=best['initial'],
            target_bin=best['target_bin'],histogram=witness.tolist(),exact_count_verified=True))
    if keep:result['all_normalized_rules']=records
    return result

def benchmark(ws):
    h=hist(ws);return dict(runes=int(h.sum()),words=len(ws),histogram=h.tolist(),peak_label=LET[int(h.argmax())],
                          peak_count=int(h.max()),maximum_single_rune_frequency=float(h.max()/h.sum()))

def recovery_controls():
    result=[]
    for name,plain,key,initial in [('Solved LP1 groups',sum([words(s) for s in GROUPS.values()],[]),(7,11,23,3),(2,13)),
                                  ('Solved LP2 page 57',words(lp[57]),(19,0,5,28),(17,4))]:
        cipher=encode(plain,*key,initial);recovered=decode(cipher,*key,initial)
        assert all(np.array_equal(a,b) for a,b in zip(plain,recovered))
        actual=benchmark(plain);limit=bound(cipher)
        assert actual['peak_count']<=limit['maximum_single_rune_count']
        result.append(dict(name=name,key=list(key),initial=list(initial),full_word_roundtrip_exact=True,
                           plaintext=actual,bound=limit))
    return result

def exhaustive_fixture():
    # Independent sequential decoder evaluates every key and reset state over F_5.
    k=5;ws=[np.array(w) for w in [[0],[3,1],[0,2,2,4],[4,0,1,3,2,0],[1,1,4]]]
    high=0;settings=0
    for A in range(1,k):
        for B in range(k):
            for C in range(1,k):
                for K in range(k):
                    for older in range(k):
                        for prev in range(k):
                            h=np.zeros(k,dtype=np.int64)
                            for w in ws:
                                x,y=older,prev
                                for cur in w:
                                    p=(A*int(cur)+B*y+C*x+K)%k
                                    h[p]+=1;x,y=y,int(cur)
                            high=max(high,int(h.max()));settings+=1
    result=bound(ws,k)
    assert result['maximum_single_rune_count']==high
    return dict(alphabet=5,coefficient_and_initial_state_settings=settings,brute_force_peak=high,
                analytic_peak=result['maximum_single_rune_count'],agreement=True)

def main():
    source_hash=hashlib.sha256((ROOT/'liber-primus__transcription--master.txt').read_bytes()).hexdigest()
    assert source_hash==EXPECTED
    sections=[(i,s) for i,s in enumerate('%'.join(lp[:56]).split('$')) if len(vals(s))>100]
    grouped=[(i,words(s)) for i,s in sections];all_words=sum([ws for i,ws in grouped],[])
    assert [i for i,ws in grouped]==[0,1,2,4,5,6,7,8]
    assert sum(map(len,all_words))==12947
    pooled=bound(all_words,keep=True);per_section=[dict(section=i,**bound(ws)) for i,ws in grouped]
    separate_peak=sum(x['maximum_single_rune_count'] for x in per_section)
    # Construct the independently keyed optimum with each section peak shifted to bin 0.
    independent_hist=np.zeros(29,dtype=np.int64)
    for (_,ws),b in zip(grouped,per_section):
        w=b['witness'];independent_hist+=hist(decode(ws,w['A'],w['B'],w['C'],-w['target_bin'],w['initial']))
    assert independent_hist[0]==separate_peak and independent_hist.max()==separate_peak
    phrase=np.array([23,11,1,17,18,10]);hits=[];records=tokenize('%'.join(lp[:56]))
    for left,right in zip(records,records[1:]):
        if left['values']==phrase[:3].tolist() and right['values']==phrase[3:].tolist():
            hits.append(dict(page=left['page'],offset=left['page_rune_offset'],word_lengths=[3,3]))
    assert [(x['page'],x['offset']) for x in hits]==[(27,28),(55,70)]
    # Word reset erases the differing external lead-ins, including at the middle word break.
    key=(7,11,23,3);initial=(2,13)
    contextual=[]
    for lead in [[17,0],[5,14]]:
        contextual.append(decode([np.array(lead),phrase[:3],phrase[3:]],*key,initial)[1:])
    assert all(np.array_equal(a,b) for a,b in zip(*contextual))
    result=dict(formula='p_i=(A*c_i+B*c_(i-1)+C*c_(i-2)+K) mod 29; A,C nonzero; same initial pair at every word start',
        natural_rune_order=R,scope='All positions in eight substantial LP2 sections, pages 0..55. Nine-rune section 3 excluded.',
        normalization=dict(a='B/A mod 29',b='C/A mod 29',first_word_position='c_0+u',second_word_position='c_1+a*c_0+v',
            later_word_positions='c_i+a*c_(i-1)+b*c_(i-2)',u='a*s1+b*s2',v='b*s1',
            exact_peak_for_fixed_ab='max(first histogram)+max(second histogram for a)+max(interior histogram for a,b)',
            justification='Nonzero b makes (s2,s1)->(u,v) invertible, so first and second histograms can be shifted independently to the interior peak. Final A and K only permute bins.'),
        pooled=pooled,per_section=per_section,
        independent_sections=dict(maximum_count=separate_peak,maximum_frequency=separate_peak/pooled['runes'],
            attainable_histogram=independent_hist.tolist(),witness_verified=True),
        lp1_benchmark=benchmark(sum([words(s) for s in GROUPS.values()],[])),lp2_page57_benchmark=benchmark(words(lp[57])),
        repeated_phrase=dict(occurrences=hits,rule='All six plaintext runes must match under shared coefficients and shared reset state.',
            reason='The same two ciphertext words start from the same state, regardless of preceding text.',fixture_checked=True,
            fixture_plaintext_values=[w.tolist() for w in contextual[0]],fixture_is_not_a_plaintext_proposal=True),
        controls=recovery_controls(),exhaustive_small_alphabet_fixture=exhaustive_fixture(),
        source_sha256=source_hash,script_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
        limitations=['Exact empirical peak bound, not a p-value or a theorem excluding all possible plaintexts.',
            'Existing rune-only word parser; spaces/newlines/slashes/page breaks do not by themselves end a word; other non-rune delimiters do.',
            'Coefficients and initial pair fixed across words; separate section keys/state allowed only in the separately reported relaxation.',
            'Natural input rune order, fixed linear arithmetic, no skipped symbols, extra key stream or changing per-word state.',
            'Plaintext word lengths presumed preserved. No nonlinear rules, arbitrary input alphabet permutations or intermediate encodings tested.',
            'Recovery controls use supplied known keys, not an unknown-key language search. This is retrospective analysis of an already studied corpus.'])
    (ROOT/'word_reset_feedback_results.json').write_text(json.dumps(result,indent=2)+'\n')
    summary={key:value for key,value in result.items() if key not in ['pooled','per_section','controls']}
    summary['pooled']={key:value for key,value in pooled.items() if key!='all_normalized_rules'}
    summary['sections']=[{k:v for k,v in x.items() if k!='witness'} for x in per_section]
    summary['controls']=[dict(name=x['name'],roundtrip=x['full_word_roundtrip_exact'],true_peak=x['plaintext']['maximum_single_rune_frequency'],
        allowed_peak=x['bound']['maximum_single_rune_frequency']) for x in result['controls']]
    print(json.dumps(summary,indent=2))
if __name__=='__main__':main()
