#!/usr/bin/env python3
"""PN5180 NFC reader diagnostic script.

Connects to a PN5180 over SPI on a Raspberry Pi and reads
hardware status, version info, and register state.

Wiring (from spoolbuddy/README.md):
    PN5180 VCC  -> Pi Pin 1  (3.3V)
    PN5180 GND  -> Pi Pin 20 (GND)
    PN5180 SCK  -> Pi Pin 23 (GPIO11)
    PN5180 MISO -> Pi Pin 21 (GPIO9)
    PN5180 MOSI -> Pi Pin 19 (GPIO10)
    PN5180 NSS  -> Pi Pin 16 (GPIO23, manual CS)
    PN5180 BUSY -> Pi Pin 22 (GPIO25)
    PN5180 RST  -> Pi Pin 18 (GPIO24)
"""

import os
import sys
import time

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "daemon")))


from pn5180 import (  # noqa: E402
    NSS_PIN as DRIVER_NSS_PIN,
    PN5180,
    RST_PIN as DRIVER_RST_PIN,
    SPI_BUS as DRIVER_SPI_BUS,
    SPI_DEVICE as DRIVER_SPI_DEVICE,
)

REG_SYSTEM_CONFIG = 0x00
REG_IRQ_ENABLE = 0x01
REG_IRQ_STATUS = 0x02
REG_IRQ_CLEAR = 0x03
REG_TRANSCEIVE_CONTROL = 0x04
REG_TIMER1_RELOAD = 0x0C
REG_TIMER1_CONFIG = 0x0F
REG_RX_WAIT_CONFIG = 0x11
REG_CRC_RX_CONFIG = 0x12
REG_RX_STATUS = 0x13
REG_CRC_TX_CONFIG = 0x19
REG_RF_STATUS = 0x1D
REG_SYSTEM_STATUS = 0x24
REG_SIGPRO_CONFIG = 0x1A  # Signal Processing Configuration
REG_TEMP_CONTROL = 0x25

# ---------------------------------------------------------------------------
# EEPROM addresses
# ---------------------------------------------------------------------------
EEPROM_DIE_IDENTIFIER = 0x00  # 16 bytes
EEPROM_PRODUCT_VERSION = 0x10  # 2 bytes
EEPROM_FIRMWARE_VERSION = 0x12  # 2 bytes
EEPROM_EEPROM_VERSION = 0x14  # 2 bytes
EEPROM_IRQ_PIN_CONFIG = 0x1A  # 1 byte


def _check_spi_device_access() -> str:
    """Check that the configured spidev exists and can be opened."""
    spi_path = f"/dev/spidev{DRIVER_SPI_BUS}.{DRIVER_SPI_DEVICE}"
    if not os.path.exists(spi_path):
        raise FileNotFoundError(f"SPI device not found: {spi_path}")

    fd = os.open(spi_path, os.O_RDWR)
    os.close(fd)
    return spi_path


def _self_test_control_pins(nfc: PN5180):
    """Toggle NSS and RST pins and print observed line state.
    Uses public set_pin/get_pin methods to avoid direct access to driver internals.
    """
    for pin_name, pin_num in (("NSS", DRIVER_NSS_PIN), ("RST", DRIVER_RST_PIN)):
        nfc.set_pin(pin_num, True)
        time.sleep(0.005)
        active_state = nfc.get_pin(pin_num)

        nfc.set_pin(pin_num, False)
        time.sleep(0.005)
        inactive_state = nfc.get_pin(pin_num)

        # Restore idle-high level used by this driver.
        nfc.set_pin(pin_num, True)

        print(
            f"    {pin_name} pin {pin_num}: "
            f"ACTIVE->{'ACTIVE' if active_state else 'INACTIVE'}, "
            f"INACTIVE->{'ACTIVE' if inactive_state else 'INACTIVE'}"
        )


def run_diagnostics():
    print("=" * 60)
    print("PN5180 NFC Reader Diagnostics")
    print("=" * 60)

    nfc = None
    try:
        print("\n[1] SPI device check...")
        spi_path = _check_spi_device_access()
        print(f"    SPI device OK: {spi_path}")

        nfc = PN5180()

        print("\n[2] Control pin self-test (NSS/RST)...")
        _self_test_control_pins(nfc)

        # Reset
        print("\n[3] Hardware reset...")
        nfc.reset()
        print("    Reset OK")

        # Version info
        print("\n[4] Version info (EEPROM)")
        product = nfc.read_eeprom(EEPROM_PRODUCT_VERSION, 2)
        firmware = nfc.read_eeprom(EEPROM_FIRMWARE_VERSION, 2)
        eeprom = nfc.read_eeprom(EEPROM_EEPROM_VERSION, 2)
        die_id = nfc.read_eeprom(EEPROM_DIE_IDENTIFIER, 16)

        print(f"    Product version  : {product[1]}.{product[0]}")
        print(f"    Firmware version : {firmware[1]}.{firmware[0]}")
        print(f"    EEPROM version   : {eeprom[1]}.{eeprom[0]}")
        print(f"    Die identifier   : {die_id.hex()}")

        # Register dump
        print("\n[5] Register dump")
        # Use register names from the script (not in pn5180.py)
        REGISTER_NAMES_DUMP = {
            0x00: "SYSTEM_CONFIG",
            0x01: "IRQ_ENABLE",
            0x02: "IRQ_STATUS",
            0x03: "IRQ_CLEAR",
            0x04: "TRANSCEIVE_CONTROL",
            0x0C: "TIMER1_RELOAD",
            0x0F: "TIMER1_CONFIG",
            0x11: "RX_WAIT_CONFIG",
            0x12: "CRC_RX_CONFIG",
            0x13: "RX_STATUS",
            0x19: "CRC_TX_CONFIG",
            0x1A: "SIGPRO_CONFIG",
            0x1D: "RF_STATUS",
            0x24: "SYSTEM_STATUS",
            0x25: "TEMP_CONTROL",
        }
        for addr, name in sorted(REGISTER_NAMES_DUMP.items()):
            val = nfc.read_reg(addr)
            print(f"    0x{addr:02X} {name:<24s} = 0x{val:08X}")

        # SIGPRO_CONFIG ISO/IEC14443 mode check
        sigpro_val = nfc.read_reg(REG_SIGPRO_CONFIG)
        sigpro_mode = (sigpro_val >> 0) & 0b111
        baudrate_map = {
            0b100: "106 kBd (ISO/IEC14443 type A/B)",
            0b101: "212 kBd (FeliCa 212 kBd)",
            0b110: "424 kBd (FeliCa 424 kBd)",
            0b111: "848 kBd",
        }
        baudrate_str = baudrate_map.get(sigpro_mode, "Unknown or reserved")
        print(f"\n[5b] SIGPRO_CONFIG (0x1A) bits 2:0 = 0b{sigpro_mode:03b} ({baudrate_str})")

        # IRQ status breakdown
        irq = nfc.read_reg(REG_IRQ_STATUS)
        print(f"\n[6] IRQ status flags (0x{irq:08X})")
        irq_flags = [
            (0, "RX_IRQ"),
            (1, "TX_IRQ"),
            (2, "IDLE_IRQ"),
            (3, "MODE_DETECTED_IRQ"),
            (4, "CARD_ACTIVATED_IRQ"),
            (5, "STATE_CHANGE_IRQ"),
            (6, "RFOFF_DET_IRQ"),
            (7, "RFON_DET_IRQ"),
            (8, "TX_RFOFF_IRQ"),
            (9, "TX_RFON_IRQ"),
            (10, "RF_ACTIVE_ERROR_IRQ"),
            (14, "LPCD_IRQ"),
        ]
        for bit, name in irq_flags:
            state = "SET" if irq & (1 << bit) else "---"
            print(f"    bit {bit:2d}: {name:<28s} [{state}]")

        # RF status
        rf = nfc.read_reg(REG_RF_STATUS)
        print(f"\n[7] RF status (0x{rf:08X})")
        tx_rf_on = bool(rf & (1 << 0))
        rx_en = bool(rf & (1 << 1))
        print(f"    TX RF active : {tx_rf_on}")
        print(f"    RX enabled   : {rx_en}")

        # System status
        sys_stat = nfc.read_reg(REG_SYSTEM_STATUS)
        print(f"\n[8] System status (0x{sys_stat:08X})")

        # System status bit breakdown
        sys_stat_bits = [
            (9, "LDO_TVDD_OK"),
            (8, "PARAMETER_ERROR"),
            (7, "SYNTAX_ERROR"),
            (6, "SEMANTIC_ERROR"),
            (5, "STBY_PREVENT_RFLD"),
            (4, "BOOT_TEMP"),
            (3, "BOOT_SOFT_RESET"),
            (2, "BOOT_WUC"),
            (1, "BOOT_RFLD"),
            (0, "BOOT_POR"),
        ]
        for bit, symbol in sys_stat_bits:
            state = "SET" if sys_stat & (1 << bit) else "---"
            print(f"    bit {bit:2d}: {symbol:<18s} [{state}]")

        # Temperature
        temp_ctrl = nfc.read_reg(REG_TEMP_CONTROL)
        print(f"\n[9] Temp control register (0x{temp_ctrl:08X})")

        # TEMP_DELTA bits 1:0
        temp_delta = (temp_ctrl >> 0) & 0b11
        temp_delta_map = {
            0b00: "85°C",
            0b01: "115°C",
            0b10: "125°C",
            0b11: "135°C",
        }
        temp_delta_str = temp_delta_map.get(temp_delta, "Unknown")
        print(f"    bits 1:0 TEMP_DELTA = 0b{temp_delta:02b} ({temp_delta_str})")

        print("\n" + "=" * 60)
        print("Diagnostics complete - PN5180 is responding over SPI.")
        print("=" * 60)

    except TimeoutError as e:
        print(f"\nERROR: {e}")
        print("Check wiring and ensure SPI is enabled (dtparam=spi=on in /boot/firmware/config.txt)")
        sys.exit(1)
    except Exception as e:
        print(f"\nERROR: {e}")
        sys.exit(1)
    finally:
        if nfc is not None:
            nfc.close()


if __name__ == "__main__":
    run_diagnostics()
