import ctypes
import os
from pathlib import Path

DLL_DIR = Path(__file__).resolve().parent   # 本目录内放置 PynectDirect.dll 与 ftd2xx.dll

def load_sdk():
    """加载 DLL，并把 dll 目录加入依赖搜索路径（ftd2xx.dll 同目录）"""
    if os.name == "nt" and hasattr(os, "add_dll_directory"):
        os.add_dll_directory(str(DLL_DIR))
    return ctypes.CDLL(str(DLL_DIR / "PynectDirect.dll"))

# ---------- 数据结构 ----------
class PynectDirectDeviceInfo(ctypes.Structure):
    _fields_ = [
        ("protocol", ctypes.c_int),
        ("capabilities", ctypes.c_uint32),
        ("protocol_name", ctypes.c_char * 32),
        ("model", ctypes.c_char * 32),
        ("serial_number", ctypes.c_char * 32),
        ("hardware_version", ctypes.c_char * 64),
    ]

class PynectDirectSelectedWavelengthRange(ctypes.Structure):
    _fields_ = [
        ("request_start_nm", ctypes.c_double),
        ("request_end_nm", ctypes.c_double),
        ("actual_start_nm", ctypes.c_double),
        ("actual_end_nm", ctypes.c_double),
        ("start_pixel", ctypes.c_uint16),
        ("end_pixel", ctypes.c_uint16),
        ("count", ctypes.c_uint16),
    ]

P_U16 = ctypes.POINTER(ctypes.c_uint16)
P_UINT = ctypes.POINTER(ctypes.c_uint)
P_DOUBLE = ctypes.POINTER(ctypes.c_double)

# ---------- 类型绑定（本次例程用到的函数） ----------
def bind_api(sdk):
    sdk.pynect_direct_scan_devices.restype = ctypes.c_int
    sdk.pynect_direct_open_device.argtypes = [ctypes.c_int]
    sdk.pynect_direct_open_device.restype = ctypes.c_int
    sdk.pynect_direct_close_device.restype = None
    sdk.pynect_direct_get_device_info.argtypes = [ctypes.POINTER(PynectDirectDeviceInfo)]
    sdk.pynect_direct_get_device_info.restype = ctypes.c_int
    sdk.pynect_direct_get_wavelength_count.argtypes = [P_U16]
    sdk.pynect_direct_get_wavelength_count.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_range.argtypes = [P_DOUBLE, P_DOUBLE]
    sdk.pynect_direct_get_full_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_data.argtypes = [P_DOUBLE, ctypes.c_int]
    sdk.pynect_direct_get_full_wavelength_data.restype = ctypes.c_int
    sdk.pynect_direct_get_full_spectrum_data.argtypes = [P_U16, ctypes.c_int]
    sdk.pynect_direct_get_full_spectrum_data.restype = ctypes.c_int
    sdk.pynect_direct_get_integration_time.argtypes = [P_UINT]
    sdk.pynect_direct_get_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_integration_time.argtypes = [ctypes.c_uint]
    sdk.pynect_direct_set_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_selected_wavelength_range.argtypes = [ctypes.c_double, ctypes.c_double]
    sdk.pynect_direct_set_selected_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_selected_wavelength_range.argtypes = [ctypes.POINTER(PynectDirectSelectedWavelengthRange)]
    sdk.pynect_direct_get_selected_wavelength_range.restype = ctypes.c_int

def require(code, what):
    if code != 0:
        raise RuntimeError(f"{what} failed, return code={code}")

def main():
    sdk = load_sdk()
    bind_api(sdk)

    n = sdk.pynect_direct_scan_devices()
    if n <= 0:
        print("No device found.")
        return
    print(f"Found {n} device(s).")

    require(sdk.pynect_direct_open_device(0), "open_device")
    try:
        info = PynectDirectDeviceInfo()
        require(sdk.pynect_direct_get_device_info(ctypes.byref(info)), "get_device_info")
        print(f"Model:    {info.model.decode().strip(chr(0))}")
        print(f"Serial:   {info.serial_number.decode().strip(chr(0))}")
        print(f"Hardware: {info.hardware_version.decode().strip(chr(0))}")

        count = ctypes.c_uint16()
        require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "get_wavelength_count")
        n = count.value
        print(f"Wavelength count: {n}")

        start, end = ctypes.c_double(), ctypes.c_double()
        require(sdk.pynect_direct_get_full_wavelength_range(
            ctypes.byref(start), ctypes.byref(end)), "get_full_wavelength_range")
        print(f"Wavelength range: {start.value:.2f} ~ {end.value:.2f} nm")

        wl = (ctypes.c_double * n)()
        require(sdk.pynect_direct_get_full_wavelength_data(wl, n), "get_full_wavelength_data")

        integration = ctypes.c_uint()
        require(sdk.pynect_direct_get_integration_time(ctypes.byref(integration)),
                "get_integration_time")
        print(f"Current integration time: {integration.value} us")
        sdk.pynect_direct_set_integration_time(50000)

        spec = (ctypes.c_uint16 * n)()
        require(sdk.pynect_direct_get_full_spectrum_data(spec, n), "get_full_spectrum_data")

        print(f"{'Index':<6}{'Wavelength(nm)':>15}{'Intensity':>10}")
        print(f"{'-----':<6}{'--------------':>15}{'---------':>10}")
        for i in range(min(5, n)):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")
        if n > 10:
            print(f"...  (total {n} points)")
        for i in range(max(0, n - 5), n):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")

        if start.value < 400.0 and end.value > 800.0:
            require(sdk.pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range")
            sel = PynectDirectSelectedWavelengthRange()
            require(sdk.pynect_direct_get_selected_wavelength_range(ctypes.byref(sel)),
                    "get_selected_range")
            print(f"Selected: pixels {sel.start_pixel}..{sel.end_pixel}, count={sel.count}")
    finally:
        sdk.pynect_direct_close_device()
        print("Done.")

if __name__ == "__main__":
    main()
