"""Parse hvin.dat based on TYPE DBASE from DBHV.BAS / TESTHV3.BAS.

Record layout (199 bytes each):
  MODNAME     STRING * 13
  INTYPE      STRING * 3
  MININ       SINGLE
  MAXIN       SINGLE
  OUTSIGTYPE  STRING * 7
  MINOUT      SINGLE
  MAXOUT      SINGLE
  WAVESHPCAL  STRING * 8
  ...42 SINGLEs total...
"""
import struct, sys

FIELDS = [
    ('MODNAME', 's', 13),
    ('INTYPE', 's', 3),
    ('MININ', 'f', 4),
    ('MAXIN', 'f', 4),
    ('OUTSIGTYPE', 's', 7),
    ('MINOUT', 'f', 4),
    ('MAXOUT', 'f', 4),
    ('WAVESHPCAL', 's', 8),
    ('FINCAL', 'f', 4),
    ('FINMIN', 'f', 4),
    ('FINMAX', 'f', 4),
    ('FINEXTMIN', 'f', 4),
    ('FINEXTMAX', 'f', 4),
    ('INPROTECT', 'f', 4),
    ('IOUTLIM', 'f', 4),
    ('VOUTLIM', 'f', 4),
    ('OUTRES', 'f', 4),
    ('OUTNOISE', 'f', 4),
    ('OSCALIN', 'f', 4),
    ('GNCALIN', 'f', 4),
    ('OSCALPT', 'f', 4),
    ('GNCALPT', 'f', 4),
    ('CALTOL', 'f', 4),
    ('ADJ', 'f', 4),
    ('LINEAR', 'f', 4),
    ('ACCSINCAL', 'f', 4),
    ('ACCSINSTD', 'f', 4),
    ('ACCSINEXT', 'f', 4),
    ('ACCCF12', 'f', 4),
    ('ACCCF23', 'f', 4),
    ('ACCCF34', 'f', 4),
    ('ACCCF45', 'f', 4),
    ('CMR', 'f', 4),
    ('STEPTIME', 'f', 4),
    ('STEPPERC', 'f', 4),
    ('STEPTOL', 'f', 4),
    ('LOOPVMIN', 'f', 4),
    ('LOOPVNOM', 'f', 4),
    ('LOOPVMAX', 'f', 4),
    ('MAXLOADR', 'f', 4),
    ('MINVS', 'f', 4),
    ('NOMVS', 'f', 4),
    ('MAXVS', 'f', 4),
    ('ISMIN', 'f', 4),
    ('ISMAX', 'f', 4),
    ('PSS', 'f', 4),
]

RECORD_SIZE = sum(sz for _, _, sz in FIELDS)
print(f'Computed record size: {RECORD_SIZE} bytes')

def parse_record(buf, off):
    rec = {}
    pos = off
    for name, typ, sz in FIELDS:
        chunk = buf[pos:pos+sz]
        if typ == 's':
            rec[name] = chunk.rstrip(b'\x00 ').decode('latin-1', errors='replace').strip()
        else:
            rec[name] = struct.unpack('<f', chunk)[0]
        pos += sz
    return rec

def main():
    with open(sys.argv[1], 'rb') as f:
        buf = f.read()
    print(f'File size: {len(buf)} bytes, {len(buf)/RECORD_SIZE} records')
    n = len(buf) // RECORD_SIZE
    for i in range(n):
        r = parse_record(buf, i * RECORD_SIZE)
        if not r['MODNAME'] or not any(c.isalnum() for c in r['MODNAME']):
            continue
        print(f"#{i+1:02d} {r['MODNAME']!r:20s} IN={r['INTYPE']!r} OUT={r['OUTSIGTYPE']!r} {r['MININ']:+.3f} to {r['MAXIN']:+.3f} -> {r['MINOUT']:+.3f} to {r['MAXOUT']:+.3f} Vs={r['NOMVS']:.1f} Is={r['ISMAX']:.1f}mA")

if __name__ == '__main__':
    main()
