"""Generic local scheduler. Scheduling/charging/packaging never chooses sites.
Tree discovery is the unchanged previous program's growth/registration routine.
Fixed tree can be cached; placement always runs the actual local program anew.
"""
import copy,heapq,time
from common import CAP,BudgetError,IntegrityError,canonical,digest
from host import neighbors,cues
from program import local_update
from policy import update
from legacy import package
PLACEMENT_CAP=dict(ticks=16384,messages=12000,bytes=1048576,queue=2048,local_bytes=2048,proposals=1728,retries=0)
def discover(h):
    started=time.perf_counter(); cpu=time.process_time(); adj=neighbors(h); boundary=cues(h)
    root=next(t['site'] for t in h['terminals'] if t['kind']=='query')
    states={}; queue=[(0,root,'',0,'grow',None)]; seq=1; sent=0; tick=0; peak=0; trace=[]; applications=0
    while queue:
        tick,u,source,_,kind,p=heapq.heappop(queue); applications+=1
        if tick>CAP['development_ticks']: raise BudgetError('growth ticks')
        born=u not in states
        state,actions=local_update(states.get(u),boundary[u],adj[u],(kind,source or None,p))
        if state is None: continue
        states[u]=state
        if len(canonical(state).encode())>CAP['local_development_bytes']: raise BudgetError('growth local state')
        if born: trace.append(dict(t=tick,site=u,event='birth',parent=source or None,boundary=boundary[u]))
        if kind=='register': trace.append(dict(t=tick,site=u,event='register',tag=p,from_site=source))
        for _,v,k,payload in actions:
            sent+=1; heapq.heappush(queue,(tick+dict(adj[u])[v],v,u,seq,k,payload)); seq+=1
        peak=max(peak,len(queue))
        if sent>CAP['development_messages'] or sent*16>CAP['development_bytes'] or peak>CAP['development_queue']: raise BudgetError('growth messages')
        if time.perf_counter()-started>CAP['construction_seconds']: raise BudgetError('growth seconds')
    cost=dict(occupied=len(states),development_ticks=tick,development_messages=sent,development_bytes=16*sent,peak_development_queue=peak,
              peak_local_state_bytes=max(len(canonical(s).encode()) for s in states.values()),peak_total_development_state_bytes=sum(len(canonical(s).encode()) for s in states.values()),
              rule_applications=applications,global_builder_work=0,construction_wall_seconds=time.perf_counter()-started,construction_cpu_seconds=time.process_time()-cpu)
    return dict(states=states,cost=cost,trace=trace,tree_hash=digest({u:s['parent'] for u,s in states.items()}))
def place(h,tree,workload,theta,analytic=False,trace=False):
    start=time.perf_counter(); cpu=time.process_time(); adj=neighbors(h); boundary=cues(h)
    for b in boundary.values():
        for cue in b:
            if cue['kind']=='query': cue['q']=workload['q']
            elif cue['kind']=='write': cue['w']=workload['w'][cue['a']]
    states={}
    for u,s in tree['states'].items():
        if not (s['root'] or s['down']): continue
        routes={}
        if s['parent'] is not None: routes['V']=s['parent']
        if '8' in s['down'] and s['down']['8'] is not None: routes['A']=s['down']['8']
        states[u]=dict(id=u,parent=s['parent'],down=copy.deepcopy(s['down']),routes=routes,latch=None)
    root=next(t['site'] for t in h['terminals'] if t['kind']=='query')
    queue=[(tree['cost']['development_ticks'],root,'',0,'start',None)]; seq=1; sent=0; peak=0; proposals=0; applications=0; tick=0; audit=[]
    while queue:
        tick,u,source,_,kind,p=heapq.heappop(queue); applications+=1
        if tick>PLACEMENT_CAP['ticks']: raise BudgetError('placement ticks')
        if trace: audit.append(dict(t=tick,site=u,event=kind,from_site=source,payload=p))
        try: state,actions=update(states[u],boundary[u],adj[u],(kind,source or None,p),theta,analytic)
        except ValueError as e: raise IntegrityError(str(e)) from e
        states[u]=state
        if len(canonical(state).encode())>PLACEMENT_CAP['local_bytes']: raise BudgetError('placement local state')
        for action in actions:
            if action[0]=='send':
                _,v,k,payload=action; sent+=1
                if v not in dict(adj[u]): raise IntegrityError('nonlocal action')
                heapq.heappush(queue,(tick+dict(adj[u])[v],v,u,seq,k,payload)); seq+=1
            elif action[0]=='self': heapq.heappush(queue,(tick,u,u,seq,action[1],action[2]));seq+=1
            elif action[0]=='proposal':
                proposals+=1
                if trace: audit.append(dict(t=tick,event='proposal',**action[1]))
            elif action[0]=='differentiate' and trace: audit.append(dict(t=tick,event='differentiate',**action[1]))
        peak=max(peak,len(queue))
        if sent+tree['cost']['development_messages']>PLACEMENT_CAP['messages'] or sent*96+tree['cost']['development_bytes']>PLACEMENT_CAP['bytes'] or peak>PLACEMENT_CAP['queue'] or proposals>PLACEMENT_CAP['proposals']: raise BudgetError('placement resources')
        if time.perf_counter()-start>CAP['construction_seconds']: raise BudgetError('placement seconds')
    cells={}
    for u,s in states.items():
        _,actions=update(s,boundary[u],adj[u],('compile',None,None),theta,analytic); applications+=1
        cells[u]=next(a[1] for a in actions if a[0]=='compile')
    c=dict(tree['cost']); c.update(development_ticks=tick,development_messages=c['development_messages']+sent,
              development_bytes=c['development_bytes']+96*sent,placement_messages=sent,placement_bytes=96*sent,proposals=proposals,retries=0,
              peak_development_queue=max(c['peak_development_queue'],peak),peak_local_state_bytes=max(c['peak_local_state_bytes'],max(len(canonical(s).encode()) for s in states.values())),
              peak_total_development_state_bytes=max(c['peak_total_development_state_bytes'],sum(len(canonical(s).encode()) for s in states.values())),
              rule_applications=c['rule_applications']+applications,placement_cpu_seconds=time.process_time()-cpu,placement_wall_seconds=time.perf_counter()-start,
              construction_cpu_seconds=c['construction_cpu_seconds']+time.process_time()-cpu,construction_wall_seconds=c['construction_wall_seconds']+time.perf_counter()-start,
              cached_tree_hash=tree['tree_hash'])
    return package(h,cells,c,audit)
