#!/usr/bin/env python3
"""
IF-50 send test. Every run is logged to if50_run_log.csv next to this file with the exact bytes
sent, the pacing, and what the box echoed. Add the printed result afterwards with:  --result "TEXT"

Examples
  python3 if50_test.py --nudge --pace 2.0 "ABC"                  # DC3, DC1, then A B C two seconds apart
  python3 if50_test.py --burst "THE QUICK BROWN FOX\r\n"          # one write, bell first (the very first test)
  python3 if50_test.py --pace 0.5 "THE QUICK BROWN FOX"           # paced, no nudge
  python3 if50_test.py --result "QIC OWFOX"                       # attach the paper result to the last run
"""
import argparse, csv, datetime, os, sys, time
import serial

PORT = "/dev/cu.usbserial-DU0DTPSH"
LOG  = os.path.join(os.path.dirname(os.path.abspath(__file__)), "if50_run_log.csv")
FIELDS = ["time", "mode", "baud", "nudge", "pace_s", "bell", "text_sent", "bytes_hex",
          "dc3_echo", "dc1_echo", "dsr_before", "dsr_after", "rx_hex", "printed"]

def log_row(row):
    new = not os.path.exists(LOG)
    with open(LOG, "a", newline="") as f:
        w = csv.DictWriter(f, fieldnames=FIELDS)
        if new: w.writeheader()
        w.writerow(row)

def set_result(text):
    rows = list(csv.DictReader(open(LOG)))
    rows[-1]["printed"] = text
    with open(LOG, "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=FIELDS); w.writeheader(); w.writerows(rows)
    print(f"logged result for run at {rows[-1]['time']}: {text!r}")

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("text", nargs="?", default="")
    ap.add_argument("--baud", type=int, default=9600)
    ap.add_argument("--nudge", action="store_true", help="send DC3, wait 1 s, DC1, wait 1 s first")
    ap.add_argument("--burst", action="store_true", help="one write; bell (0x07) 0.5 s before the text")
    ap.add_argument("--pace", type=float, default=None, help="seconds between characters")
    ap.add_argument("--no-bell", action="store_true")
    ap.add_argument("--result", help="attach printed result to the last logged run and exit")
    a = ap.parse_args()
    if a.result is not None:
        set_result(a.result); return
    text = a.text.encode().decode("unicode_escape")  # allow \r\n on the command line
    data = text.encode("ascii", "replace")
    s = serial.Serial(PORT, a.baud, bytesize=8, parity="N", stopbits=1, xonxoff=False, timeout=0.3)
    s.dtr = True; s.rts = True; time.sleep(0.3)
    row = {"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "baud": a.baud,
           "nudge": a.nudge, "pace_s": a.pace if a.pace is not None else "burst",
           "bell": a.burst and not a.no_bell, "text_sent": text, "bytes_hex": data.hex(" "),
           "dsr_before": s.dsr, "printed": ""}
    dc3 = dc1 = b""
    if a.nudge:
        s.write(b"\x13"); s.flush(); time.sleep(1.0); dc3 = s.read(8)
        s.write(b"\x11"); s.flush(); time.sleep(1.0); dc1 = s.read(8)
    row["dc3_echo"] = dc3.hex(); row["dc1_echo"] = dc1.hex()
    rx = b""
    if a.pace is None:
        row["mode"] = "burst"
        if not a.no_bell: s.write(b"\x07"); s.flush(); time.sleep(0.5)
        s.write(data); s.flush(); time.sleep(1.5); rx = s.read(64)
    else:
        row["mode"] = "paced"
        for b in data:
            s.write(bytes([b])); s.flush(); time.sleep(a.pace); rx += s.read(8)
        time.sleep(1.0); rx += s.read(64)
    row["dsr_after"] = s.dsr; row["rx_hex"] = rx.hex(" ")
    s.close(); log_row(row)
    hung = a.nudge and (dc3 == b"" or dc1 == b"")
    print(f"sent {len(data)} bytes [{data.hex(' ')}] mode={row['mode']} pace={row['pace_s']} nudge={a.nudge}")
    print(f"dc3_echo={dc3.hex() or '-'} dc1_echo={dc1.hex() or '-'} rx={rx.hex(' ') or '-'} DSR={row['dsr_after']}")
    if hung: print("WARNING: box did not echo DC3/DC1 -> it is hung; power-cycle the typewriter before the next run")

if __name__ == "__main__":
    main()
