#!/usr/bin/env python3
"""Convert older BEANalyzer CSV logs for safer replay.

Older logs may store DATA as DATA+CRC+EOM+Post-EOM. This script rewrites DATA to
real BEAN DATA only, using the physical frame stored in the raw column.
"""
import csv, re, sys
from pathlib import Path

def parse_hex(s):
    out=[]
    for part in str(s or '').replace(',',' ').split():
        part=part.strip().replace('0x','').replace('0X','')
        if re.fullmatch(r'[0-9A-Fa-f]{1,2}', part): out.append(int(part,16))
    return out

def hx(vals): return ' '.join(f'{v&0xFF:02X}' for v in vals)

def crc8(core):
    crc=0
    for byte in core:
        crc ^= byte & 0xFF
        for _ in range(8):
            crc = (((crc << 1) ^ 0x13) & 0xFF) if (crc & 0x80) else ((crc << 1) & 0xFF)
    return crc

def meta(frame):
    if len(frame)>=6 and frame[0]==1:
        header=frame[1]; ml=header&0x0F; crc_idx=2+ml
        if 3<=ml<=13 and len(frame)>crc_idx:
            core=frame[1:crc_idx]
            return {'header':header,'priority':header>>4,'ml':ml,'dst':frame[2],'mid':frame[3],'data':frame[4:crc_idx],'crc':frame[crc_idx],'crc_ok':frame[crc_idx]==crc8(core),'eom':frame[crc_idx+1] if len(frame)>crc_idx+1 else None,'post':frame[crc_idx+2] if len(frame)>crc_idx+2 else None}
    return None

def convert(inp, out):
    with open(inp,newline='',encoding='utf-8') as f:
        r=csv.DictReader(f); fields=list(r.fieldnames or [])
        extra=['capture_marker','bean_header','priority','ml','data_len','dst_id','mes_id','crc','crc_ok','eom','post_eom','rsp_candidate','eof_candidate']
        outfields=list(fields)
        for col in extra:
            if col not in outfields: outfields.append(col)
        rows=[]; changed=0; parsed=0
        for row in r:
            m=meta(parse_hex(row.get('raw','')))
            if m:
                parsed+=1
                if parse_hex(row.get('data','')) != m['data']: changed+=1
                row['data']=hx(m['data']); row['context']=f"{m['header']:02X}"; row['did']=f"{m['dst']:02X}"; row['mid']=f"{m['mid']:02X}"
                row['capture_marker']='01'; row['bean_header']=f"{m['header']:02X}"; row['priority']=str(m['priority']); row['ml']=str(m['ml']); row['data_len']=str(max(0,m['ml']-2)); row['dst_id']=f"{m['dst']:02X}"; row['mes_id']=f"{m['mid']:02X}"; row['crc']=f"{m['crc']:02X}"; row['crc_ok']='1' if m['crc_ok'] else '0'; row['eom']='' if m['eom'] is None else f"{m['eom']:02X}"; row['post_eom']='' if m['post'] is None else f"{m['post']:02X}"
                if m['post'] is not None:
                    row['rsp_candidate']=format((m['post']>>6)&3,'02b'); row['eof_candidate']=format(m['post']&0x3F,'06b')
            rows.append(row)
    with open(out,'w',newline='',encoding='utf-8') as f:
        w=csv.DictWriter(f,fieldnames=outfields,extrasaction='ignore'); w.writeheader(); w.writerows(rows)
    print(f'Parsed physical frames: {parsed}')
    print(f'Rows cleaned: {changed}')
    print(f'Output: {out}')

if __name__ == '__main__':
    if len(sys.argv)<2:
        print('Usage: convert_old_beanalyzer_csv_for_replay.py input.csv [output.csv]'); sys.exit(1)
    inp=Path(sys.argv[1])
    out=Path(sys.argv[2]) if len(sys.argv)>2 else inp.with_name(inp.stem + '_replay_clean.csv')
    convert(inp,out)
