import csv,hashlib,json,pathlib,time,resource,sys
import numpy as np
from task import seed,permutations,episode
from learner import Model,Branch,run_episode
R=pathlib.Path(__file__).resolve().parent

def main():
    started=time.monotonic();cpu=time.process_time();config=json.loads((R/'frozen.json').read_text());gates=json.loads((R/'gate_results.json').read_text())
    assert gates['status']=='passed'
    for n,h in gates['source_hashes'].items():assert hashlib.sha256((R/n).read_bytes()).hexdigest()==h,'Gate source changed: '+n
    assert not (R/'pilot_results.json').exists(),'No duplicate pilot or outcome overwrite'
    rows=[];totals={'training_events':gates['calibration_training_events'],'learning_mac_equivalents':gates['calibration_mac_equivalents'],'training_query_labels':0,'evaluation_events':0,'evaluation_mac_equivalents':0};seeds=[];bin_totals=[0,0,0]
    def data(group,kind,index,pi,K,namespace):
        events,answers,meta=episode(K,K,pi,seed(namespace,group,kind,index))
        for j,c in enumerate(meta['delay_bins']):bin_totals[j]+=c
        return events,answers
    def train(model,events,answers,K):
        result=run_episode(model,events,answers,K,K,True,config['learning_rate']);totals['training_events']+=128;totals['training_query_labels']+=result['queries'];totals['learning_mac_equivalents']+=result['mac_equivalents'];assert totals['training_events']<=2**18 and totals['learning_mac_equivalents']<=10**9
    def evaluate(model,group,task,pi,K,ablate=False):
        scores=[]
        for index in range(4):
            events,answers=data(group,task,index,pi,K,'scored');x=run_episode(model,events,answers,K,K,ablate=ablate);totals['evaluation_events']+=128;totals['evaluation_mac_equivalents']+=x['mac_equivalents'];scores.append(x)
        return {'accuracy':float(np.mean([x['accuracy'] for x in scores])),'loss':float(np.mean([x['loss'] for x in scores])),'queries':sum(x['queries'] for x in scores)}
    for group in config['seed_groups']:
        oldpi=permutations(3,3,seed('train',group,'old-pi'));newpi=permutations(4,4,seed('train',group,'new-pi'))
        parent=Model(seed=group);history_start=dict(totals);history_clock=time.monotonic();history_cpu=time.process_time()
        for i in range(32):
            events,answers=data(group,'old-history',i,oldpi,3,'train');train(parent,events,answers,3)
        historical_mac=totals['learning_mac_equivalents']-history_start['learning_mac_equivalents'];historical_events=4096;historical_wall=time.monotonic()-history_clock;historical_cpu=time.process_time()-history_cpu
        grown=parent.grow(seed('train',group,'new-branch'));scratch=Model(seed=seed('train',group,'scratch-old'));scratch.branches.append(Branch(4,4,seed('train',group,'scratch-new'),addresses=[0,1,2,3]))
        zero_step={}
        for mode in ['reset','carry']:
            a=parent.snapshot();b=grown.snapshot()
            if mode=='reset':a.reset();b.reset()
            events,answers=data(group,'trained-parent-retention',0,oldpi,3,'gates');maximum=0.;stateerror=0.
            for e in events:
                maximum=max(maximum,abs(a.step(e,3,3)[0]-b.step(e,3,3)[0]));stateerror=max(stateerror,float(np.max(np.abs(a.branches[0].h-b.branches[0].h))),float(np.max(np.abs(a.branches[0].delay-b.branches[0].delay))))
            zero_step[mode]={'max_output_error':maximum,'max_old_state_delay_error':stateerror}
            assert maximum<=config['zero_step_tolerance'] and stateerror<=config['zero_step_tolerance'],'Trained-parent transfer gate failed'
            totals['evaluation_events']+=256;totals['evaluation_mac_equivalents']+=128*(sum(x.cost() for x in a.branches)+sum(x.cost() for x in b.branches))
        scratch_start=dict(totals);scratch_clock=time.monotonic();scratch_cpu=time.process_time()
        for i in range(32):
            events,answers=data(group,'old-history',i,oldpi,3,'train');train(scratch,events,answers,3)
        scratch_history_mac=totals['learning_mac_equivalents']-scratch_start['learning_mac_equivalents'];scratch_wall=time.monotonic()-scratch_clock;scratch_cpu_elapsed=time.process_time()-scratch_cpu
        seedrecord={'zero_step_trained_parent':zero_step,'parent_history_wall_seconds':historical_wall,'parent_history_cpu_seconds':historical_cpu,'scratch_history_wall_seconds':scratch_wall,'scratch_history_cpu_seconds':scratch_cpu_elapsed,'seed_group':group,'parent_training_events':historical_events,'parent_learning_mac_equivalents':historical_mac,'arms':{}}
        for name,model in [('continue-parent',parent),('ordinary-growth',grown),('scratch-child',scratch)]:
            before=dict(totals);curve=[];arm_clock=time.monotonic();arm_cpu=time.process_time()
            for checkpoint in range(33):
                if checkpoint in config['checkpoints_episodes']:
                    old=evaluate(model,group,'old',oldpi,3);new=evaluate(model,group,'new',newpi,4)
                    point={'seed_group':group,'arm':name,'adaptation_episodes':checkpoint,'adaptation_events':checkpoint*128,'adaptation_mac_equivalents':totals['learning_mac_equivalents']-before['learning_mac_equivalents'],'old_accuracy':old['accuracy'],'new_accuracy':new['accuracy'],'old_loss':old['loss'],'new_loss':new['loss']};curve.append(point);rows.append(point);print(json.dumps(point),flush=True)
                if checkpoint<32:
                    K=3 if checkpoint%2==0 else 4;pi=oldpi if K==3 else newpi;events,answers=data(group,'adaptation',checkpoint,pi,K,'train');train(model,events,answers,K)
            ablation=None
            if name=='ordinary-growth':ablation={'old':evaluate(model,group,'old',oldpi,3,True),'new':evaluate(model,group,'new',newpi,4,True)}
            seedrecord['arms'][name]={'curves':curve,'adaptation_plus_evaluation_wall_seconds':time.monotonic()-arm_clock,'adaptation_plus_evaluation_cpu_seconds':time.process_time()-arm_cpu,'added_branch_ablation':ablation,'adaptation_training_events':4096,'adaptation_mac_equivalents':totals['learning_mac_equivalents']-before['learning_mac_equivalents'],'parameters':model.parameters(),'state_scalars':sum(b.h.size+b.delay.size for b in model.branches),'historical_events_charged':historical_events,'historical_compute_charged':historical_mac if name!='scratch-child' else scratch_history_mac,'matching':'same supervision; grown/continued share actual parent; scratch repeats actual history. Compute and elapsed time not matched.'}
        seeds.append(seedrecord)
    assert totals['learning_mac_equivalents']<config['learning_mac_upper_bound']
    usage=resource.getrusage(resource.RUSAGE_SELF)
    result={'status':'completed','executed_at_utc':__import__('datetime').datetime.now(__import__('datetime').timezone.utc).isoformat(),'config_sha256':hashlib.sha256((R/'frozen.json').read_bytes()).hexdigest(),'source_hashes':{n:hashlib.sha256((R/n).read_bytes()).hexdigest() for n in ['pilot.py','learner.py','builder.py','task.py']},'numpy_version':np.__version__,'python':sys.version.split()[0],'seed_groups':seeds,'totals':totals,'realized_query_delay_bins_all_generated_episodes':bin_totals,'wall_seconds':time.monotonic()-started,'cpu_seconds':time.process_time()-cpu,'peak_rss_bytes':usage.ru_maxrss if sys.platform=='darwin' else usage.ru_maxrss*1024,'limitations':['Two exploratory seeds, no powered comparison','No held-out widths or task strata','No developmental rule or advantage tested','Hand-coded public WRITE memory gates; routing learned','Scratch/growth/parent data matched, compute and elapsed time differ','Pilot scored streams cannot become confirmatory evidence']}
    (R/'pilot_results.json').write_text(json.dumps(result,indent=2)+'\n')
    with (R/'curves.csv').open('w') as f:w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
    print(json.dumps({'status':result['status'],'totals':totals,'wall_seconds':result['wall_seconds'],'peak_rss_bytes':result['peak_rss_bytes']}))
if __name__=='__main__':main()
