"""PC-side quick test for TMP117 instrument.

Usage (example):
    python pc_test_tmp117.py [serial_port] [--timestamp]

Options:
    --timestamp, -t    Show timestamp in output (default: hidden)
"""

from __future__ import annotations

import sys
import time

from tools.myserial.scpi_serial import SCPISerial

# Optional: Specify port explicitly, or leave as None for auto-detection
PORT = None  # Auto-detect
# PORT = "/dev/cu.usbmodem2101"  # macOS
# PORT = "/dev/ttyACM0"  # Linux
# PORT = "COM3"  # Windows

# Display options
SHOW_TIMESTAMP = False  # Set via --timestamp or -t flag


def main() -> int:
    global SHOW_TIMESTAMP
    
    # Parse command line arguments
    args = sys.argv[1:]
    port = PORT
    
    for arg in args:
        if arg in ("--timestamp", "-t"):
            SHOW_TIMESTAMP = True
        elif not arg.startswith("-"):
            port = arg
    
    try:
        # ANSI color codes for pretty output
        CYAN = "\033[96m"
        YELLOW = "\033[93m"
        GREEN = "\033[92m"
        MAGENTA = "\033[95m"
        RED = "\033[91m"
        RESET = "\033[0m"
        BOLD = "\033[1m"
        DEG = "\u00b0"
        THERM = "🌡️"
        DELTA = "Δ"
        CHECK = "✓"
        WARN = "⚠️"

        def color_label(label, color):
            return f"{BOLD}{color}{label}{RESET}"

        def color_val(val, color):
            return f"{color}{val}{RESET}"

        print(f"{BOLD}{CYAN}Connecting to TMP117...{RESET}")
        with SCPISerial(port=port) as instr:
            print(f"{GREEN}{CHECK} Connected to: {BOLD}{instr.port_name}{RESET}\n")

            # Flush any startup messages and sync
            time.sleep(0.5)
            try:
                instr._ser.reset_input_buffer() # type: ignore
            except Exception:
                pass

            instr.write("*CLS")
            time.sleep(0.1)

            def q(label, cmd, color=YELLOW):
                resp = instr.query(cmd)
                print(f"{color_label(label, CYAN)} {color_val('->', MAGENTA)} {color_val(resp, color)}")
                return resp

            q("*IDN?", "*IDN?")
            count_resp = q("SENS:COUN?", ":SENS:COUN?")
            try:
                count = int(count_resp)
            except Exception:
                count = 1

            # Query addresses and offsets
            for idx in range(count):
                n = idx + 1
                q(f"ADDR{n}:", f":CONF{n}:ADDR?")
                q(f"OFFS{n}:", f":CONF{n}:OFFS?")

            # Demonstrate TMP117 hardware features (if supported by the firmware/driver)
            print(f"\n{BOLD}{MAGENTA}--- TMP117 hardware feature demo ---{RESET}")
            for idx in range(count):
                n = idx + 1
                print(f"\n{BOLD}{MAGENTA}--- Sensor {n} ---{RESET}")
                try:
                    q(f"SER{n}:", f":SENS{n}:SER?")
                except Exception:
                    pass

                # Hardware temperature offset register (TMP117) - read/try-write/read
                try:
                    q(f"HW_OFFS{n} before:", f":CONF{n}:TEMP:OFFS?")
                    instr.write(f":CONF{n}:TEMP:OFFS 0.0")
                    time.sleep(0.1)
                    q(f"HW_OFFS{n} after:", f":CONF{n}:TEMP:OFFS?")
                except Exception:
                    pass

                # Set alert limits and read back
                try:
                    instr.write(f":CONF{n}:HIGH 30.0")
                    instr.write(f":CONF{n}:LOW 10.0")
                    time.sleep(0.05)
                    q(f"HIGH{n}:", f":CONF{n}:HIGH?")
                    q(f"LOW{n}:", f":CONF{n}:LOW?")
                except Exception:
                    pass

                # Query alert mode (WINDOW/HYSTERESIS)
                try:
                    q(f"ALRT{n}:", f":CONF{n}:ALRT:MODE?")
                except Exception:
                    pass

                # One-shot measurement
                try:
                    q(f"MEAS{n} (one-shot):", f":SENS{n}:MEAS?")
                except Exception:
                    pass
                # One-shot puts TMP117 into SHUTDOWN; restore continuous mode
                try:
                    instr.write(f":CONF{n}:MEAS:MODE 0")
                except Exception:
                    pass

                # Measurement configuration: set averaging/delay and query
                try:
                    instr.write(f":CONF{n}:MEAS:AVG 1")  # request 8x averaging
                    time.sleep(0.02)
                    q(f"MEAS:AVG{n}:", f":CONF{n}:MEAS:AVG?")
                    instr.write(f":CONF{n}:MEAS:DEL 0.00155")  # seconds; driver maps to code
                    # instr.write(f":CONF{n}:MEAS:DEL 0.125")  # seconds; driver maps to code
                    # instr.write(f":CONF{n}:MEAS:DEL 1")  # seconds; driver maps to code
                    time.sleep(0.02)
                    q(f"MEAS:DEL{n}:", f":CONF{n}:MEAS:DEL?")
                except Exception as error:
                    print(f"{RED}Error configuring MEAS:AVG for sensor {n}: {error}{RESET}")
                    pass
                
                for i in range(2):
                    q(f":SYST:ERR?", ":SYST:ERR?")

                q(f":CONF{n}:MEAS:MODE?", f":CONF{n}:MEAS:MODE?")
                q(f":SENS{n}:ALL?", f":SENS{n}:ALL?")
                q(f":SENS{n}:TEMP?", f":SENS{n}:TEMP?")
                
                for i in range(2):
                    q(f":SYST:ERR?", ":SYST:ERR?")

            print(f"{BOLD}{MAGENTA}--- End hardware feature demo ---{RESET}\n")

            # Demonstrate offset tweak
            if count >= 1:
                print(f"\n{BOLD}{MAGENTA}--- Setting sample offsets ---{RESET}")
                instr.write(":CONF1:OFFS 0.0")
                q("OFFS1 after set:", ":CONF1:OFFS?")
                if count >= 2:
                    instr.write(":CONF2:OFFS 0.0")
                    q("OFFS2 after set:", ":CONF2:OFFS?")
                print(f"{YELLOW}Use :SYST:CAL:SAVE to persist these offsets in prod mode.{RESET}\n")

            # Test *RST once
            print(f"{BOLD}{MAGENTA}--- Testing *RST ---{RESET}")
            q("Before RST - SENS:COUN?", ":SENS:COUN?")
            print(f"{BOLD}{YELLOW}Sending *RST...{RESET}")
            instr.write("*RST")
            time.sleep(0.2)
            q("After RST - SENS:COUN?", ":SENS:COUN?")
            print(f"{BOLD}{MAGENTA}--- End RST test ---{RESET}\n")

            # Determine measurement mode based on sensor count
            if count >= 2:
                print(f"{BOLD}{MAGENTA}--- Synchronized one-shot pair measurement ---{RESET}")
                print(f"{YELLOW}Streaming synchronized temperature readings (Ctrl+C to stop):{RESET}\n")
                print(f"{BOLD}{CYAN}T1{RESET}         {BOLD}{GREEN}T2{RESET}         {BOLD}Skew (ms){RESET}    {BOLD}{YELLOW}DIFF{RESET}")
            else:
                print(f"{BOLD}{MAGENTA}--- Single sensor temperature streaming ---{RESET}")
                print(f"{YELLOW}Streaming temperature readings (Ctrl+C to stop):{RESET}\n")
                print(f"{BOLD}{CYAN}T1{RESET}")

            try:
                while True:
                    try:
                        def pretty_temp_line(t1, t2=None, diff=None, ts=None, skew_ms=None):
                            parts = []
                            if ts is not None:
                                parts.append(f"{MAGENTA}[{ts:13.6f}s]{RESET}")
                            parts.append(f"{THERM} {BOLD}{CYAN}T1{RESET} {YELLOW}{t1:.2f}{DEG}C{RESET}")
                            if t2 is not None:
                                parts.append(f"{THERM} {BOLD}{CYAN}T2{RESET} {GREEN}{t2:.2f}{DEG}C{RESET}")
                            if skew_ms is not None:
                                parts.append(f"skew: {skew_ms:.2f}ms")
                            if diff is not None:
                                parts.append(f"{DELTA}T: {BOLD}{diff:+.2f}{DEG}C{RESET}")
                            return " | ".join(parts)

                        if count >= 2:
                            # Use :SENS:SYNC? for synchronized one-shot pair measurement
                            # Format: timestamp,T1,T2,skew_ms,DIFF
                            # Optional: :SENS:SYNC? <avg> where avg is 1, 8, 32, or 64
                            response = instr.query(":SENS:SYNC? 64")
                            values = [float(x) for x in response.split(",")]

                            if len(values) >= 5:
                                # SYNC format: ts, T1, T2, skew_ms, DIFF
                                ts, t1, t2, skew_ms, diff = values[0], values[1], values[2], values[3], values[4]
                                line = pretty_temp_line(t1, t2, diff, ts if SHOW_TIMESTAMP else None, skew_ms)
                                print(line)
                            elif len(values) == 4:
                                ts, t1, t2, diff = values[0], values[1], values[2], values[3]
                                line = pretty_temp_line(t1, t2, diff, ts if SHOW_TIMESTAMP else None)
                                print(line)
                            else:
                                print(f"{WARN} Unexpected response: {response}")
                        else:
                            # Single sensor mode - use :SENS:TEMP?
                            # Format: timestamp,temp
                            response = instr.query(":SENS:TEMP?")
                            values = [float(x) for x in response.split(",")]

                            if len(values) >= 2:
                                ts, t1 = values[0], values[1]
                                line = pretty_temp_line(t1, None, None, ts if SHOW_TIMESTAMP else None)
                                print(line)
                            else:
                                print(f"{WARN} Unexpected response: {response}")

                    except ValueError as e:
                        print(f"{RED}Error parsing response: {e}{RESET}")
                    # time.sleep(0.5)
            except KeyboardInterrupt:
                print(f"\n{BOLD}{MAGENTA}User stopped the program.{RESET}")

        return 0
        
    except ValueError as e:
        print(f"\n{RED} Connection error: {e}{RESET}")
        return 1
    except KeyboardInterrupt:
        print(f"\n\n{BOLD}{MAGENTA}Test cancelled by user{RESET}")
        return 1
    except Exception as e:  # noqa: BLE001
        print(f"\n{RED} Error: {e}{RESET}")
        import traceback
        traceback.print_exc()
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
