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

USBIO_DID = [
    USB2019, 
    USB2026
]
class AI_API_TestApp:
    def __init__(self, master):
        self.master = master
        self.master.title("AI_API_Test Program")
        self.master.geometry("1200x680")

        self.usbio = USBIO() 
        self.i_ai_total= 0

        self.create_widgets()

    def create_widgets(self):
        # Device ID
        ttk.Label(self.master, text="Device ID").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.cbx_did = ttk.Combobox(self.master, values=["USB-2019", "USB-2026"], width=10)
        self.cbx_did.grid(row=0, column=1, sticky="w", padx=5, pady=5)
        self.cbx_did.current(0)

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

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

        # Default and 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)

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

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

        # WDT setting
        ttk.Label(self.master, text="WDT Timeout").grid(row=7, column=0, sticky="w", padx=5, pady=5)
        self.txt_wdt_timeout = ttk.Entry(self.master, width=10)
        self.txt_wdt_timeout.grid(row=7, column=1, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_wdt_timeout).grid(row=8, column=1, padx=5, pady=5)

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

        # Type code setting
        ttk.Label(self.master, text="Type").grid(row=1, column=2, sticky="w", padx=5, pady=5)
        self.txt_type_code = ttk.Entry(self.master, width=6)  # Reduced width
        self.txt_type_code.grid(row=1, column=3, sticky="w", padx=5, pady=5)
        ttk.Label(self.master, text="Channel").grid(row=1, column=3, sticky="e", padx=5, pady=5)
        self.txt_type_code_channel = ttk.Entry(self.master, width=3)  # Reduced width
        self.txt_type_code_channel.grid(row=1, column=4, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_type_code).grid(row=1, column=5, padx=5, pady=5)

        # Channel CJC offset setting
        ttk.Label(self.master, text="Offset").grid(row=2, column=2, sticky="w", padx=5, pady=5)
        self.txt_ch_cjc_offset = ttk.Entry(self.master, width=8)
        self.txt_ch_cjc_offset.grid(row=2, column=3, sticky="w", padx=5, pady=5)
        ttk.Label(self.master, text="Channel").grid(row=2, column=4, sticky="w", padx=5, pady=5)
        self.txt_ch_cjc_offset_channel = ttk.Entry(self.master, width=5)
        self.txt_ch_cjc_offset_channel.grid(row=2, column=5, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_ch_cjc_offset).grid(row=2, column=6, padx=5, pady=5)

        # Channel enable/disable setting
        ttk.Label(self.master, text="Mask  0x").grid(row=3, column=2, sticky="w", padx=5, pady=5)
        self.txt_ch_enable = ttk.Entry(self.master, width=10)
        self.txt_ch_enable.grid(row=3, column=3, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_ch_enable).grid(row=3, column=4, padx=5, pady=5)

        # Filter rejection setting
        ttk.Label(self.master, text="Filter rejection 0:60Hz,1:50Hz").grid(row=4, column=2, sticky="w", padx=5, pady=5)
        self.txt_filter_rejection = ttk.Entry(self.master, width=10)
        self.txt_filter_rejection.grid(row=4, column=3, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_filter_rejection).grid(row=4, column=4, padx=5, pady=5)

        # CJC setting
        self.chk_enable_cjc = tk.BooleanVar()
        ttk.Checkbutton(self.master, text="Enable CJC", variable=self.chk_enable_cjc).grid(row=5, column=2, sticky="w", padx=5, pady=5)
        ttk.Label(self.master, text="Offset").grid(row=5, column=3, sticky="w", padx=5, pady=5)
        self.txt_cjc_offset = ttk.Entry(self.master, width=8)
        self.txt_cjc_offset.grid(row=5, column=4, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_cjc_offset).grid(row=5, column=5, padx=5, pady=5)

        # Wire detection setting
        self.chk_enable_wire_detection = tk.BooleanVar()
        ttk.Checkbutton(self.master, text="Enable Wire Detection", variable=self.chk_enable_wire_detection).grid(row=6, column=2, columnspan=2, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Set", command=self.on_set_wire_detection).grid(row=6, column=4, padx=5, pady=5)

        # Getting functions
        ttk.Label(self.master, text="Getting functions").grid(row=7, column=2, columnspan=2, sticky="w", padx=5, pady=5)
        ttk.Button(self.master, text="Get Sup Type Code", command=self.on_get_sup_type_code).grid(row=8, column=2, padx=5, pady=5)
        ttk.Button(self.master, text="Get Type Code", command=self.on_get_ch_type_code).grid(row=8, column=3, padx=5, pady=5)
        ttk.Button(self.master, text="Get Ch Offset", command=self.on_get_ch_offset).grid(row=8, column=4, padx=5, pady=5)
        ttk.Button(self.master, text="Get Ch Enable", command=self.on_get_ch_enable).grid(row=9, column=2, padx=5, pady=5)
        ttk.Button(self.master, text="Get Filter Rejection", command=self.on_get_filter_rejection).grid(row=9, column=3, padx=5, pady=5)
        ttk.Button(self.master, text="Get CJC Setting", command=self.on_get_cjc_setting).grid(row=9, column=4, padx=5, pady=5)
        ttk.Button(self.master, text="Get Wire Detection", command=self.on_get_wire_detection).grid(row=10, column=2, padx=5, pady=5)
        ttk.Button(self.master, text="Get Resolution", command=self.on_get_resolution).grid(row=10, column=3, padx=5, pady=5)
    
    def on_open_device(self):
        board_id = int(self.txt_bid.get()) 
        device_id =USBIO_DID[self.cbx_did.current()]
        
        self.usbio.SetCommTimeout(500)
        err_code = self.usbio.OpenDevice(device_id, board_id)
        
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            self.refresh_module_info()

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

    def refresh_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}")  #.decode()}")    
           
         
        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):
        ai_total =  c_char()
        self.usbio.GetAITotal(byref(ai_total))
        self.i_ai_total=ord(ai_total.value)
        self.lb_module_info.insert(tk.END, f"AI channels: {self.i_ai_total}")             
        
    def on_set_type_code(self):
        channel = int(self.txt_type_code_channel.get())
        type_code = int(self.txt_type_code.get(), 16)
        err_code = self.usbio.AI_SetTypeCode(channel, type_code)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Type code set successfully")

    def on_set_ch_cjc_offset(self):
        channel = int(self.txt_ch_cjc_offset_channel.get())
        offset = float(self.txt_ch_cjc_offset.get())
        err_code = self.usbio.AI_SetChCJCOffset(channel, offset)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Channel CJC offset set successfully")

    def on_set_ch_enable(self):
        ch_enable = int(self.txt_ch_enable.get(), 16)
        err_code = self.usbio.AI_SetChEnable(ch_enable)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Channel enable/disable set successfully")

    def on_set_filter_rejection(self):
        filter_rejection = int(self.txt_filter_rejection.get())
        err_code = self.usbio.AI_SetFilterRejection(filter_rejection)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Filter rejection set successfully")

    def on_set_cjc_offset(self):
        cjc_enable = self.chk_enable_cjc.get()
        cjc_offset = float(self.txt_cjc_offset.get())
        err_code = self.usbio.AI_SetCJCEnable(cjc_enable)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            err_code = self.usbio.AI_SetCJCOffset(cjc_offset)
            if err_code != 0:
                messagebox.showerror("Error", f"[{err_code}]")
            else:
                messagebox.showinfo("Success", "CJC settings set successfully")

    def on_set_wire_detection(self):
        wire_detect_enable = self.chk_enable_wire_detection.get()
        err_code = self.usbio.AI_SetWireDetectEnable(wire_detect_enable)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Wire detection setting set successfully")

    def on_get_sup_type_code(self):
        total_sup_type = c_char()
        self.usbio.AI_GetTotalSupportType(byref(total_sup_type))        
        if total_sup_type is None:
            messagebox.showerror("Error", "Failed to get supported type codes")
        else:
            sup_type = (c_char*USBIO_MAX_SUPPORT_TYPE)()
            self.usbio.AI_GetSupportTypeCode(sup_type)

            message = f"{total_sup_type} supported types for this module.\n"
            if ord(total_sup_type.value) > 0:
                message += "Support types are:\n"
                for i in range(ord(total_sup_type.value)):
                 message += f"0x{ord(sup_type[i]):02X}\n"
            messagebox.showinfo("Information", message)

    def on_get_ch_type_code(self):        
        ch_type_code = (c_char*self.i_ai_total)()
        self.usbio.AI_GetTypeCode(ch_type_code)
        if ch_type_code is None:
            messagebox.showerror("Error", "Failed to get channel type codes")
        else:
            message = "Module current channel type:\n"
            #for idx, code in enumerate(ch_type_code[:self.ai_total]):
            for idx in range(self.i_ai_total):
                message += f"Ch{idx}: 0x{ord(ch_type_code[idx]):02X}\n"
            messagebox.showinfo("Information", message)
            

    def on_get_ch_offset(self):
        ch_cjc_offset = (ctypes.c_float * USBIO_AI_MAX_CHANNEL)()
        self.usbio.AI_GetChCJCOffset(ch_cjc_offset)
        if ch_cjc_offset is None:
            messagebox.showerror("Error", "Failed to get channel CJC offsets")
        else:
            message = "Module channel CJC offset:\n"
            for idx in range(self.i_ai_total):
                message += f"Ch{idx}: {ch_cjc_offset[idx]}\n"
            messagebox.showinfo("Information", message)

    def on_get_ch_enable(self):
        ai_byte_count  = (self.i_ai_total + 7) // 8
        i_byChenable = (c_char * ai_byte_count)()   
        self.usbio.AI_GetChEnable(i_byChenable)                       
        if i_byChenable is None:
            messagebox.showerror("Error", "Failed to get channel enable status")
        else:
            message = "Channel Enable Status:\n"
            # transfer c_char to c_ubyte
            ubyte_array = cast(i_byChenable, POINTER(c_ubyte))

            for idx in range(ai_byte_count):
                enabled = ubyte_array[idx]
                for bit in range(8):
                    channel = idx * 8 + bit
                    if channel >= self.i_ao_total:
                        break
                    is_enabled = (enabled & (1 << bit)) != 0
                    message += f"Channel {channel} is {'enabled' if is_enabled else 'disabled'}\n"  
            messagebox.showinfo("Information", message)

    def on_get_filter_rejection(self):
        filter_rejection=c_char()
        self.usbio.AI_GetFilterRejection(byref(filter_rejection))
        if filter_rejection is None:
            messagebox.showerror("Error", "Failed to get filter rejection")
        else:
            message = f"Module filter rejection: 0x{ord(filter_rejection.value):02X}"
            messagebox.showinfo("Information", message)

    def on_get_cjc_setting(self):
        cjc_enable=c_char()
        self.usbio.AI_GetCJCEnable(byref(cjc_enable))
        cjc_offset=c_float()
        self.usbio.AI_GetCJCOffset(byref(cjc_offset))
        cjc_value=c_float()
        self.usbio.AI_ReadCJCValue(byref(cjc_value))
        
        if cjc_enable is None or cjc_offset is None or cjc_value is None:
            messagebox.showerror("Error", "Failed to get CJC settings")
        else:
            message = "Enable CJC offset." if cjc_enable else f"Disable CJC offset. CJC offset is {cjc_offset}"
            messagebox.showinfo("Information", message)
            messagebox.showinfo("Information", f"CJC value is {cjc_value}")

    def on_get_wire_detection(self):
        wire_detect_enable=c_char()
        self.usbio.AI_GetWireDetectEnable(byref(wire_detect_enable))
        if wire_detect_enable is None:
            messagebox.showerror("Error", "Failed to get wire detection status")
        else:
            message = "Module wire detection enable" if wire_detect_enable else "Module wire detection disable"
            messagebox.showinfo("Information", message)

    def on_get_resolution(self):
        resolution = (c_char * self.i_ai_total)()           
        self.usbio.AI_GetResolution(resolution)
        if resolution is None:
            messagebox.showerror("Error", "Failed to get resolution")
        else:
            message = "Module current channel resolution:\n"
            for idx in range(self.i_ai_total):
                message += f"Ch{idx}: {ord(resolution[idx])}\n"
            messagebox.showinfo("Information", message)

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

    def on_load_default(self):
        err_code = self.usbio.LoadDefault()
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Default settings loaded successfully")
    def on_set_nickname(self):
        nickname = self.txt_nickname.get()
        err_code = self.usbio.SetDeviceNickName(nickname.encode())
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Nickname set successfully")

    def on_set_wdt_timeout(self):
        timeout = int(self.txt_wdt_timeout.get())
        err_code = self.usbio.SetSoftWDTTimeout(timeout)
        if err_code != 0:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "WDT timeout set successfully")

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