#!/usr/bin/env python3
import argparse, csv, glob, os, sys
PCS = [
    '0x9001DF69','0x9001DF75','0x9001D05B','0x9001D086','0x9001D090',
    '0x900396FD','0x9003971A','0x90039767','0x90039778','0x900397B4',
    '0x9001DF7A','0x9001DF7C','0x9001E301','0x9001E303','0x9001E55C','0x9001E55E',
    '0x9001E614','0x9001E65F','0x9001E696','0x9001E72A','0x9001E779','0x9001E84D',
    '0x9001E883','0x9001F02E','0x9001F030','0x9001F216',
]
WATCH_ADDRS = {'0x00000729','0x0000071E','0x0003E7BA','0x0003E7BB','0x0003E7BC','0x0003E7BD','0x0003E7BE','0x0003E7BF','0x8003E7BA','0x8003E7BB','0x8003E7BC','0x8003E7BD','0x8003E7BE','0x8003E7BF'}

def read_rows(path):
    with open(path, newline='') as f:
        return list(csv.DictReader(f))

def pc_counts(rows):
    out={pc:0 for pc in PCS}
    for r in rows:
        if r.get('event')=='break':
            pc=(r.get('pc') or '').upper().replace('X','x')
            for want in out:
                if pc.lower()==want.lower(): out[want]+=1
    return out

def last_events(rows):
    last={}
    for r in rows:
        a=(r.get('addr') or '').upper().replace('X','x')
        p=(r.get('addr_physical') or '').upper().replace('X','x')
        for key in (a,p):
            if key in WATCH_ADDRS:
                last[key]=r
    return last

def packet_cdb(rows):
    # find watch/read or write of 0x7e8..0x7ef in packet phase if present, otherwise derive from notes not available.
    vals={}
    for r in rows:
        try: a=int((r.get('addr_physical') or r.get('addr') or ''),16)
        except Exception: continue
        if 0x7e8 <= a <= 0x7ef:
            vals[a]=r.get('bytes_hex') or ''
    if vals:
        return ' '.join(vals.get(a,'??') for a in range(0x7e8,0x7f0))
    return ''

def summarize_one(path):
    rows=read_rows(path)
    counts=pc_counts(rows)
    last=last_events(rows)
    base=os.path.basename(path)
    lines=[]
    lines.append(f'## {base}')
    lines.append('')
    lines.append(f'rows={len(rows)}')
    ev_counts={}
    for _r in rows:
        ev_counts[_r.get('event','')]=ev_counts.get(_r.get('event',''),0)+1
    for _ev in ('gdr_data_in_ready','gdr_data_in_submit','gdr_data_in_side_effect'):
        if ev_counts.get(_ev):
            lines.append(f'{_ev}={ev_counts[_ev]}')
    lines.append('')
    lines.append('| PC | hits |')
    lines.append('|---|---:|')
    for pc,h in counts.items():
        if h:
            lines.append(f'| {pc} | {h} |')
    if not any(counts.values()):
        lines.append('| none | 0 |')
    lines.append('')
    lines.append('| addr | event | pc | bytes | value | cycle |')
    lines.append('|---|---|---|---|---|---:|')
    for a in sorted(WATCH_ADDRS, key=lambda x:int(x,16)):
        r=last.get(a)
        if r:
            lines.append(f"| {a} | {r.get('event','')} | {r.get('pc','')} | {r.get('bytes_hex','')} | {r.get('value','')} | {r.get('cycle','')} |")
    gdr_rows=[r for r in rows if (r.get('event') or '').startswith('gdr_data_in')]
    if gdr_rows:
        lines.append('')
        lines.append('| gdr event | pc | addr | bytes | cycle | notes |')
        lines.append('|---|---|---|---|---:|---|')
        for r in (gdr_rows[:3] + ([] if len(gdr_rows)<=6 else [{'event':'...','pc':'...','addr':'...','bytes_hex':'...','cycle':'','notes':f'{len(gdr_rows)-6} omitted'}]) + (gdr_rows[-3:] if len(gdr_rows)>3 else [])):
            lines.append(f"| {r.get('event','')} | {r.get('pc','')} | {r.get('addr','')} | {r.get('bytes_hex','')} | {r.get('cycle','')} | {r.get('notes','')} |")
    return '\n'.join(lines), counts

def main():
    ap=argparse.ArgumentParser()
    ap.add_argument('trace', nargs='+')
    ap.add_argument('--out', default='gdr8050l_v109_readcap_route_summary.md')
    ns=ap.parse_args()
    paths=[]
    for pat in ns.trace:
        got=glob.glob(pat)
        paths.extend(got or [pat])
    lines=['# GDR-8050L READ CAPACITY route analysis','']
    lines.append('READ CAPACITY must be passed as `0x25`, not bare `25`, because bare `25` is parsed as decimal and becomes `0x19`. Newer traces may also include `gdr_data_in_*` semantic events from the packet data-in model.')
    lines.append('')
    important=[]
    for p in paths:
        if not os.path.exists(p):
            lines.append(f'## missing: {p}\n')
            continue
        sec, counts = summarize_one(p)
        lines.append(sec)
        if any(counts.get(pc,0) for pc in ['0x900396FD','0x9003971A','0x90039767','0x90039778','0x900397B4']):
            important.append(os.path.basename(p))
    lines.append('')
    if important:
        lines.append('## Native capacity mapper hit')
        for x in important: lines.append(f'- {x}')
    else:
        lines.append('## Native capacity mapper hit')
        lines.append('No 0x900397xx high-block consumer hit in the analyzed trace(s).')
    with open(ns.out,'w',newline='') as f: f.write('\n'.join(lines)+'\n')
    print(f'[OK] wrote {ns.out}')
if __name__=='__main__': main()
