#!/usr/bin/env python3
"""
sender.py — Send HPGL file to XY4160 pen plotter via RS-232
Usage: python sender.py <port> <file.hpgl>
Example: python sender.py COM3 output.hpgl
"""

import sys
import time
import serial

BAUD      = 4800
BYTESIZE  = serial.EIGHTBITS
PARITY    = serial.PARITY_NONE
STOPBITS  = serial.STOPBITS_TWO
CHUNK       = 64          # bytes per write — stays well inside 512-byte buffer
CTS_TIMEOUT = 30.0        # seconds to wait for CTS before giving up


def log(msg):
    ts = time.strftime("%H:%M:%S")
    print(f"[{ts}] {msg}", flush=True)


def send_hpgl(port: str, path: str):
    with open(path, "rb") as f:
        data = f.read()

    total = len(data)
    log(f"Loaded '{path}' — {total} bytes")

    ser = serial.Serial(
        port=port,
        baudrate=BAUD,
        bytesize=BYTESIZE,
        parity=PARITY,
        stopbits=STOPBITS,
        rtscts=True,          # hardware handshake
        timeout=1,
    )
    try:
        log(f"Serial port {port} opened  ({BAUD} bps, 8N2, RTS/CTS)")

        # PC (DTE) asserts RTS — tells plotter "I exist and am ready"
        ser.rts = True
        time.sleep(0.2)

        # CTS is driven by the PLOTTER (DCE):
        #   CTS HIGH  → plotter says "send data, my buffer has room"
        #   CTS LOW   → plotter says "stop! my buffer is full"
        cts_state = ser.cts
        log(f"PC asserted RTS  →  plotter CTS is {'HIGH (plotter ready to receive)' if cts_state else 'LOW (plotter busy — will wait)'}")

        sent = 0

        while sent < total:
            # Block here until CTS is HIGH (plotter buffer has room)
            if not ser.cts:
                if cts_state:
                    log(f"Plotter pulled CTS LOW at byte {sent} — buffer full, pausing...")
                    cts_state = False
                deadline = time.monotonic() + CTS_TIMEOUT
                while not ser.cts:
                    if time.monotonic() > deadline:
                        raise RuntimeError(
                            f"CTS did not recover within {CTS_TIMEOUT:.0f} s — "
                            "plotter hung or disconnected"
                        )
                    time.sleep(0.05)
                log(f"Plotter raised CTS HIGH at byte {sent} — resuming send")
                cts_state = True

            # CTS is HIGH — send a full chunk as fast as the UART allows
            chunk = data[sent:sent + CHUNK]
            n = ser.write(chunk)
            sent += n
            pct = sent / total * 100
            log(f"{sent}/{total} bytes sent ({pct:.1f}%)")

        log(f"Transfer complete — {sent} bytes sent")

        # Give plotter time to finish the last moves
        log("Waiting 2 s for plotter to finish...")
        time.sleep(2)

    except KeyboardInterrupt:
        log("Interrupted by user — aborting transfer")
    except RuntimeError as e:
        log(f"ERROR: {e}")
        sys.exit(1)
    finally:
        ser.rts = False          # politely drop RTS
        ser.close()
        log("Serial port closed.")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: python {sys.argv[0]} <port> <file.hpgl>")
        print(f"  e.g. python {sys.argv[0]} COM3 output.hpgl")
        sys.exit(1)

    port_arg = sys.argv[1]
    file_arg = sys.argv[2]

    try:
        send_hpgl(port_arg, file_arg)
    except serial.SerialException as e:
        print(f"Serial error: {e}", file=sys.stderr)
        sys.exit(1)
    except FileNotFoundError:
        print(f"File not found: {file_arg}", file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nInterrupted by user.")
        sys.exit(0)
