import tkinter as tk
from tkinter import ttk, messagebox
from ctypes import * 
from ICPDAS_USBIO import *

USBIO_DID = [
    USB2026,
    USB2045,
    USB2055,
    USB2064,
    USB2045_32,
    USB2055_32,
    USB2064_16,
    USB2068_18,
    USB2255_32, 
    USB2255_64,
    USB2261_16,
    USB2261_32,
    USB2260_48,
    USB2260B_48,
    USB2260C_48,
    USB2260_24,
    USB2260B_24,
    USB2260C_24    
]

# Add this Tooltip class at the top of your file
class Tooltip:
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tooltip = None
        self.widget.bind("<Enter>", self.show_tooltip)
        self.widget.bind("<Leave>", self.hide_tooltip)

    def show_tooltip(self, event=None):
        x, y, _, _ = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 25

        self.tooltip = tk.Toplevel(self.widget)
        self.tooltip.wm_overrideredirect(True)
        self.tooltip.wm_geometry(f"+{x}+{y}")

        label = tk.Label(self.tooltip, text=self.text, background="#ffffe0", relief="solid", borderwidth=1)
        label.pack()

    def hide_tooltip(self, event=None):
        if self.tooltip:
            self.tooltip.destroy()

class DOAPITestApp:
    def __init__(self, master):
        self.master = master
        self.master.title("DO_API_Test Program")
        self.master.geometry("1000x540")

        self.usbio = USBIO()
        self.i_do_total=0
        self.power_on_var = tk.StringVar(value="disable")  # 默认值设为 "disable"
        self.create_widgets()

    def create_widgets(self):
        # Device ID
        ttk.Label(self.master, text="Device ID").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.cbx_did = ttk.Combobox(self.master, values=[
            "USB-2026", "USB-2045", "USB-2055", "USB-2064",
            "USB-2045-32", "USB-2055-32", "USB-2064-16", "USB-2068-18",
            "USB-2255-32","USB-2255-64","USB-2261-16","USB-2261-32",
            "USB-2260-48","USB-2260B-48","USB-2260C-48","USB-2260-24",
            "USB-2260B-24","USB-2260C-24",
            ])
        self.cbx_did.grid(row=0, column=1, padx=5, pady=5)
        self.cbx_did.current(0)

        # Board ID
        ttk.Label(self.master, text="Board ID").grid(row=1, column=0, padx=5, pady=5, sticky="w")
        self.txt_bid = ttk.Entry(self.master)
        self.txt_bid.grid(row=1, column=1, padx=5, pady=5)
        self.txt_bid.insert(0, "1")

        # Open/Close buttons
        ttk.Button(self.master, text="Open", command=self.on_open_device).grid(row=2, column=0, padx=5, pady=5)
        ttk.Button(self.master, text="Close", command=self.on_close_device).grid(row=2, column=1, padx=5, pady=5)

        # Default/Refresh buttons
        ttk.Button(self.master, text="Default", command=self.on_load_default).grid(row=3, column=0, padx=5, pady=5)
        ttk.Button(self.master, text="Refresh", command=self.on_refresh_info).grid(row=3, column=1, padx=5, pady=5)

        # Nickname
        ttk.Label(self.master, text="Nickname").grid(row=4, column=0, padx=5, pady=5, sticky="w")
        self.txt_nickname = ttk.Entry(self.master)
        self.txt_nickname.grid(row=4, column=1, padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_nickname).grid(row=4, column=2, padx=5, pady=5)

        # WDT Timeout
        ttk.Label(self.master, text="WDT (ms)").grid(row=5, column=0, padx=5, pady=5, sticky="w")
        self.txt_timeout = ttk.Entry(self.master)
        self.txt_timeout.grid(row=5, column=1, padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_wdt_timeout).grid(row=5, column=2, padx=5, pady=5)

        # Board ID
        ttk.Label(self.master, text="Board ID").grid(row=6, column=0, padx=5, pady=5, sticky="w")
        self.txt_user_defined_bid = ttk.Entry(self.master)
        self.txt_user_defined_bid.grid(row=6, column=1, padx=5, pady=5)
        # Modify the button creation and add a tooltip
        set_button = ttk.Button(self.master, text="Set", command=self.on_set_board_id)
        set_button.grid(row=6, column=2, padx=5, pady=5)
        Tooltip(set_button, "Before setting the user ID, turn the Rotary switch to 0 position.")

        # Module Info
        self.lb_module_info = tk.Listbox(self.master, width=40, height=10)
        self.lb_module_info.grid(row=7, column=0, columnspan=4, padx=5, pady=5)

        # Getting functions
        ttk.Label(self.master, text="Getting functions").grid(row=0, column=4, padx=5, pady=5, sticky="w")
        ttk.Button(self.master, text="Get Power-On Enable", command=self.on_get_pwron_enable).grid(row=1, column=4, padx=5, pady=5)
        ttk.Button(self.master, text="Get Safety Enable", command=self.on_get_safety_enable).grid(row=2, column=4, padx=5, pady=5)
        ttk.Button(self.master, text="Get Safety Value", command=self.on_get_safety_val).grid(row=3, column=4, padx=5, pady=5)

        # Setting functions
        ttk.Label(self.master, text="Setting functions").grid(row=4, column=4, padx=5, pady=5, sticky="w")

        # Power-On enable/disable
        power_on_frame = ttk.LabelFrame(self.master, text="Power-On enable/disable")
        power_on_frame.grid(row=5, column=4, columnspan=4, pady=5, padx=5, sticky=(tk.W, tk.E))

        ttk.Label(power_on_frame, text="Channel").grid(row=0, column=0, sticky=tk.W)
        self.power_on_enable_ch = ttk.Entry(power_on_frame, width=10)
        self.power_on_enable_ch.grid(row=0, column=1, sticky=tk.W)

        self.power_on_var = tk.StringVar(value="enable")
        ttk.Radiobutton(power_on_frame, text="Enable", variable=self.power_on_var, value="enable").grid(row=0, column=2, sticky=tk.W)
        ttk.Radiobutton(power_on_frame, text="Disable", variable=self.power_on_var, value="disable").grid(row=0, column=3, sticky=tk.W)

        ttk.Button(power_on_frame, text="Set", command=self.on_set_pwron_enable).grid(row=0, column=4, sticky=tk.W)

        # Safety Enable
        safety_enable_frame = ttk.LabelFrame(self.master, text="Safety Enable (txtchannel can be empty for all channel)")
        safety_enable_frame.grid(row=6, column=4, columnspan=4, pady=5, padx=5, sticky=(tk.W, tk.E))

        ttk.Label(safety_enable_frame, text="Mask 0x").grid(row=0, column=0, sticky=tk.W)
        self.safety_enable_ch = ttk.Entry(safety_enable_frame, width=10)
        self.safety_enable_ch.grid(row=0, column=1, sticky=tk.W)

        ttk.Button(safety_enable_frame, text="Set", command=self.on_set_safety_enable).grid(row=0, column=2, sticky=tk.W)

        # Safety Value
        safety_value_frame = ttk.LabelFrame(self.master, text="Safety Value (txtchannel can be empty for all channel)")
        safety_value_frame.grid(row=7, column=4, columnspan=4, pady=5, padx=5, sticky=(tk.W, tk.E))

        ttk.Label(safety_value_frame, text="Value 0x").grid(row=0, column=0, sticky=tk.W)
        self.safety_value = ttk.Entry(safety_value_frame, width=10)
        self.safety_value.grid(row=0, column=1, sticky=tk.W)

        ttk.Button(safety_value_frame, text="Set", command=self.on_set_safety_val).grid(row=0, column=4, sticky=tk.W)

    def on_open_device(self):
        bid = int(self.txt_bid.get())
        did = self.cbx_did.current()

        self.usbio.SetCommTimeout(500)
        err_code = self.usbio.OpenDevice(USBIO_DID[did], bid)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            self.update_module_info()

    def on_close_device(self):
        self.lb_module_info.delete(0, tk.END)
        err_code = self.usbio.CloseDevice()
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")

    def on_load_default(self):
        err_code = self.usbio.LoadDefault()
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")

    def on_refresh_info(self):
        err_code = self.usbio.RefreshDeviceInfo()
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            self.update_module_info()

    def on_set_nickname(self):
        nickname = self.txt_nickname.get()
        err_code = self.usbio.SetDeviceNickName(nickname.encode())        
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")

    def on_set_wdt_timeout(self):
        timeout = int(self.txt_timeout.get())
        err_code = self.usbio.SetSoftWDTTimeout(timeout)        
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")

    def on_get_pwron_enable(self):
        power_on_enable = (c_char * self.i_do_total)()         
        err_code = self.usbio.DO_GetPowerOnEnable(power_on_enable)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            message = "Module power-on enable:\n"
            for idx in range(self.i_do_total):
                    message += f"Ch{idx}: {ord(power_on_enable[idx])}\n"  
            messagebox.showinfo("Information", message)

    def on_get_safety_enable(self):
        do_byte_count  = (self.i_do_total + 7) // 8
        i_byChenable = (c_char * do_byte_count )()   
        err_code =self.usbio.DO_GetSafetyEnable(i_byChenable)       

        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            message = "Module safety enable:\n0x"
            ubyte_array = cast(i_byChenable, POINTER(c_ubyte))
            for idx in range(do_byte_count):
                enabled = ubyte_array[idx]
                for bit in range(8):
                    channel = idx * 8 + bit
                    if channel >= self.i_do_total:
                        break
                    is_enabled = (enabled & (1 << bit)) != 0
                    message += f"Channel {channel} is {'enabled' if is_enabled else 'disabled'}\n"                   
            messagebox.showinfo("Safety Enable", message)

    def on_get_safety_val(self):
        #safety_val = bytearray((USBIO_DO_MAX_CHANNEL + 7) // 8)
        safety_count_arr = (c_char * ((self.i_do_total + 7) // 8))()  
        err_code = self.usbio.DO_GetSafetyValue(safety_count_arr)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            info = "Module safety value: \n0x"
            little_endian_value = int.from_bytes(safety_count_arr, byteorder='little')
            info+=f"{little_endian_value:08X}"
            #for idx in range((self.i_do_total + 7) // 8):
            #    info += f"{ord(safety_count_arr[idx]):02X}"
            messagebox.showinfo("Information", info)

    def on_set_pwron_enable(self):
        channel = self.power_on_enable_ch.get()
        is_enable = self.power_on_var.get() == "enable"            
        ch_enable = (c_char * self.i_do_total)() 

        if channel:     
            i_channel = int(channel)                             
            if is_enable:
                val =0x1;
            else:
                val =0x0;
            err_code = self.usbio.DO_SetPowerOnEnable(i_channel,val)
        else:
            if is_enable:
                for iIdx in range(self.i_do_total):
                    ch_enable[iIdx] = 0x1
            else:
                for iIdx in range(self.i_do_total):
                    ch_enable[iIdx] =  0x00            
            err_code = self.usbio.DO_SetPowerOnEnables(ch_enable)

        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")

    def on_set_safety_enable(self):
        enable_mask = int(self.safety_enable_ch.get(), 16)
        do_byte_count =(USBIO_DO_MAX_CHANNEL + 7) // 8
        ch_enable = (c_char * do_byte_count )()  
        for iIdx in range(do_byte_count):
            ch_enable[iIdx] = (enable_mask >> (iIdx * 8)) & 0xFF

        err_code = self.usbio.DO_SetSafetyEnable(ch_enable)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")

    def on_set_safety_val(self):
        value = int(self.safety_value.get(), 16)  # Convert hex string to integer
            
        do_byte_count =(USBIO_DO_MAX_CHANNEL + 7) // 8
        ch_enable = (c_char * do_byte_count )()             
        for iIdx in range(do_byte_count):
            ch_enable[iIdx] = (value >> (iIdx * 8)) & 0xFF
        err_code = self.usbio.DO_SetSafetyValue(ch_enable)

        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")

    def on_set_board_id(self):
        board_id = int(self.txt_user_defined_bid.get())
        err_code = self.usbio.SetUserDefinedBoardID(board_id)        
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Board ID set successfully.")

    def update_module_info(self):
        self.lb_module_info.delete(0, tk.END)

        soft_wdt_timeout = ctypes.c_ulong()
        self.usbio.GetSoftWDTTimeout(ctypes.byref(soft_wdt_timeout))
        self.lb_module_info.insert(tk.END, f"SoftWDT: {soft_wdt_timeout.value}")

        fw_ver = ctypes.c_int()
        self.usbio.GetFwVer(ctypes.byref(fw_ver))
        little_endian_value = int.from_bytes(fw_ver, byteorder='little')
        self.lb_module_info.insert(tk.END, f"Version: {little_endian_value}")

        device_nickname = (ctypes.c_char * USBIO_NICKNAME_LENGTH)()
        self.usbio.GetDeviceNickName(device_nickname)
        nickname = ''.join([chr(i) for i in device_nickname.value]).rstrip('\x00')
        self.lb_module_info.insert(tk.END, f"Description: {nickname}")

        device_sn = (ctypes.c_char * USBIO_SN_LENGTH)()
        self.usbio.GetDeviceSN(device_sn)
        sn = ''.join([chr(i) for i in device_sn.value]).rstrip('\x00')
        self.lb_module_info.insert(tk.END, f"SN: {sn}")

        bySupIOMask = c_char()
        self.usbio.GetSupportIOMask(byref(bySupIOMask))
        self.lb_module_info.insert(tk.END, f"Support IO Mask: 0x{ord(bySupIOMask.value):02X}")   

        intvl=ord(bySupIOMask.value)  
        if SUPPORT_DO(intvl):
            do_total =  c_char()
            self.usbio.GetDOTotal(byref(do_total))
            self.i_do_total=ord(do_total.value)
            self.lb_module_info.insert(tk.END, f"DO channels: {self.i_do_total}")             

if __name__ == "__main__":
    root = tk.Tk()
    app = DOAPITestApp(root)
    root.mainloop()
