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

USBIO_DID = [
    USB2084
]
class Form_PI_API_Test:
    def __init__(self, master):
        self.master = master
        self.master.title("PI_API_Test Program")
        self.master.geometry("770x590")

        self.USBIO = USBIO()
        self.i_pi_total=0
        self.m_bThreadRunning = False

        self.create_widgets()

    def create_widgets(self):
        # Main frame
        main_frame = ttk.Frame(self.master, padding="3 3 12 12")
        main_frame.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)

        # Left frame
        left_frame = ttk.Frame(main_frame, padding="5 5 5 5")
        left_frame.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))

        # Device ID
        ttk.Label(left_frame, text="Device ID").grid(column=0, row=0, sticky=tk.W)
        self.cbx_did = ttk.Combobox(left_frame, values=["USB-2084"], width=15)
        self.cbx_did.grid(column=1, row=0, sticky=(tk.W, tk.E))
        self.cbx_did.current(0)

        # Board ID
        ttk.Label(left_frame, text="Board ID").grid(column=0, row=1, sticky=tk.W)
        self.txt_bid = ttk.Entry(left_frame, width=10)
        self.txt_bid.grid(column=1, row=1, sticky=(tk.W, tk.E))
        self.txt_bid.insert(0, "1")

        # Open/Close buttons
        ttk.Button(left_frame, text="Open", command=self.on_open_device).grid(column=0, row=2, sticky=tk.W)
        ttk.Button(left_frame, text="Close", command=self.on_close_device).grid(column=1, row=2, sticky=tk.W)

        # Default/Refresh buttons
        ttk.Button(left_frame, text="Default", command=self.on_load_default).grid(column=0, row=3, sticky=tk.W)
        ttk.Button(left_frame, text="Refresh", command=self.on_refresh_info).grid(column=1, row=3, sticky=tk.W)

        # Nickname
        ttk.Label(left_frame, text="Nickname").grid(column=0, row=4, sticky=tk.W)
        self.txt_nickname = ttk.Entry(left_frame, width=10)
        self.txt_nickname.grid(column=1, row=4, sticky=(tk.W, tk.E))
        ttk.Button(left_frame, text="Set", command=self.on_set_nickname).grid(column=1, row=5, sticky=tk.W)

        # WDT
        ttk.Label(left_frame, text="WDT").grid(column=0, row=6, sticky=tk.W)
        self.txt_timeout = ttk.Entry(left_frame, width=7)
        self.txt_timeout.grid(column=1, row=6, sticky=(tk.W, tk.E))
        ttk.Label(left_frame, text="ms").grid(column=2, row=6, sticky=tk.W)
        ttk.Button(left_frame, text="Set", command=self.on_set_wdt_timeout).grid(column=1, row=7, sticky=tk.W)

        # Board ID (User Defined)
        ttk.Label(left_frame, text="Board ID").grid(column=0, row=8, sticky=tk.W)
        self.txt_board_id = ttk.Entry(left_frame, width=10)
        self.txt_board_id.grid(column=1, row=8, sticky=(tk.W, tk.E))
        ttk.Button(left_frame, text="Set", command=self.on_set_board_id).grid(column=1, row=9, sticky=tk.W)

        # Module Info ListBox
        self.lb_module_info = tk.Listbox(left_frame, width=30, height=10)
        self.lb_module_info.grid(column=0, row=10, columnspan=3, sticky=(tk.W, tk.E))

        # Right frame
        right_frame = ttk.Frame(main_frame, padding="5 5 5 5")
        right_frame.grid(column=1, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))

        # Getting functions
        get_frame = ttk.LabelFrame(right_frame, text="Getting functions", padding="3 3 12 12")
        get_frame.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        ttk.Button(get_frame, text="Get Sup Type Code", command=self.on_get_sup_type_code).grid(column=0, row=0, sticky=tk.W)
        ttk.Button(get_frame, text="Get Ch Type Code", command=self.on_get_ch_type_code).grid(column=0, row=1, sticky=tk.W)
        ttk.Button(get_frame, text="Get Trigger Mode", command=self.on_get_trigger_mode).grid(column=0, row=2, sticky=tk.W)
        ttk.Button(get_frame, text="Get LPF Enable", command=self.on_get_lpf_enable).grid(column=0, row=3, sticky=tk.W)
        ttk.Button(get_frame, text="Get LPF Width", command=self.on_get_lpf_width).grid(column=0, row=4, sticky=tk.W)

        # Setting functions
        set_frame = ttk.LabelFrame(right_frame, text="Setting functions", padding="3 3 12 12")
        set_frame.grid(column=0, row=1, sticky=(tk.N, tk.W, tk.E, tk.S))

        # Type code setting
        ttk.Label(set_frame, text="Type").grid(column=0, row=0, sticky=tk.W)
        ttk.Label(set_frame, text="0x").grid(column=1, row=0, sticky=tk.W)
        self.txt_type_code = ttk.Entry(set_frame, width=5)
        self.txt_type_code.grid(column=2, row=0, sticky=(tk.W, tk.E))
        ttk.Label(set_frame, text="Channel").grid(column=3, row=0, sticky=tk.W)
        self.txt_ch = ttk.Entry(set_frame, width=5)
        self.txt_ch.grid(column=4, row=0, sticky=(tk.W, tk.E))
        ttk.Button(set_frame, text="Set", command=self.on_set_type_code).grid(column=5, row=0, sticky=tk.W)

        # Channel clear
        ttk.Label(set_frame, text="Mask").grid(column=0, row=1, sticky=tk.W)
        ttk.Label(set_frame, text="0x").grid(column=1, row=1, sticky=tk.W)
        self.txt_ch_clear_mask = ttk.Entry(set_frame, width=5)
        self.txt_ch_clear_mask.grid(column=2, row=1, sticky=(tk.W, tk.E))
        ttk.Button(set_frame, text="Clear", command=self.on_clear_ch).grid(column=5, row=1, sticky=tk.W)

        # Trigger mode
        ttk.Label(set_frame, text="Mode").grid(column=0, row=2, sticky=tk.W)
        self.txt_trigger_mode = ttk.Entry(set_frame, width=7)
        self.txt_trigger_mode.grid(column=1, row=2, columnspan=2, sticky=(tk.W, tk.E))
        ttk.Label(set_frame, text="Channel").grid(column=3, row=2, sticky=tk.W)
        self.txt_ch_to_set = ttk.Entry(set_frame, width=5)
        self.txt_ch_to_set.grid(column=4, row=2, sticky=(tk.W, tk.E))
        ttk.Button(set_frame, text="Set", command=self.on_set_trigger_mode).grid(column=5, row=2, sticky=tk.W)

        # LP filter enable
        ttk.Label(set_frame, text="Mask").grid(column=0, row=3, sticky=tk.W)
        ttk.Label(set_frame, text="0x").grid(column=1, row=3, sticky=tk.W)
        self.txt_lpf_enable = ttk.Entry(set_frame, width=5)
        self.txt_lpf_enable.grid(column=2, row=3, sticky=(tk.W, tk.E))
        ttk.Button(set_frame, text="Set LPF Enable", command=self.on_set_lpf_enable).grid(column=5, row=3, sticky=tk.W)

        # LP filter width
        ttk.Label(set_frame, text="Width").grid(column=0, row=4, sticky=tk.W)
        self.txt_lpf_width = ttk.Entry(set_frame, width=6)
        self.txt_lpf_width.grid(column=1, row=4, columnspan=2, sticky=(tk.W, tk.E))
        ttk.Label(set_frame, text="uS").grid(column=3, row=4, sticky=tk.W)
        ttk.Label(set_frame, text="Channel").grid(column=3, row=4, sticky=tk.E)
        self.txt_lpf_width_ch = ttk.Entry(set_frame, width=5)
        self.txt_lpf_width_ch.grid(column=4, row=4, sticky=(tk.W, tk.E))
        ttk.Button(set_frame, text="Set LPF Width", command=self.on_set_lpf_width).grid(column=5, row=4, sticky=tk.W)

        # Exit button
        ttk.Button(main_frame, text="Exit", command=self.on_exit).grid(column=1, row=1, sticky=tk.SE)

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

        self.USBIO.SetCommTimeout(500)
        err_code = self.USBIO.OpenDevice(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}]")

    def on_set_nickname(self):
        nickname = self.txt_nickname.get().encode()
        err_code = self.USBIO.SetDeviceNickName(nickname)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")

    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}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")
    def on_set_board_id(self):
        board_id = int(self.txt_board_id.get())
        err_code = self.USBIO.SetUserDefinedBoardID(board_id)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Operation completed successfully.")
    def on_get_sup_type_code(self):
        total_sup_type = c_char()
        self.USBIO.PI_GetTotalSupportType(byref(total_sup_type))
        
        if total_sup_type is None:
            messagebox.showerror("Error", "Failed to get support type code")
        else:
            sup_type = (c_char*USBIO_MAX_SUPPORT_TYPE)()
            self.USBIO.PI_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_pi_total)()
        self.USBIO.PI_GetTypeCode(ch_type_code)
        if ch_type_code is None:
            messagebox.showerror("Error", "Failed to get channel type code")
        else:
            message = "Module current channel type:\n"
            for idx in range(self.i_pi_total):
                message += f"Ch{idx}: 0x{ord(ch_type_code[idx]):02X}\n"           
            messagebox.showinfo("Information", message)

    def on_get_trigger_mode(self):
        trigger_mode = (c_char*self.i_pi_total)()
        self.USBIO.PI_GetTriggerMode(trigger_mode)
        if trigger_mode is None:
            messagebox.showerror("Error", "Failed to get trigger mode")
        else:
            message = "Module current trigger mode:\n"
            for idx in range(self.i_pi_total):
                message += f"Ch{idx}: 0x{ord(trigger_mode[idx]):02X}\n"               
            messagebox.showinfo("Information", message)

    def on_get_lpf_enable(self):
        pi_byte_count  = (self.i_pi_total + 7) // 8
        i_byChenable = (c_char * pi_byte_count )()         
        err_code = self.USBIO.PI_GetLPFilterEnable(i_byChenable)

        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            message = "Module LP filter enable\n"
            ubyte_array = cast(i_byChenable, POINTER(c_ubyte))
            for idx in range(pi_byte_count):
                enabled = ubyte_array[idx]
                for bit in range(8):
                    channel = idx * 8 + bit
                    if channel >= self.i_pi_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_lpf_width(self):
        lpf_width=(c_uint16*self.i_pi_total)()
        err_code = self.USBIO.PI_GetLPFilterWidth(lpf_width)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            message = "Module current LP filter width:"
            #for idx, width in enumerate(lpf_width[:self.i_pi_total]):
            for idx in range(self.i_pi_total):
                message += f"\nCh{idx}: {lpf_width[idx]}"
            messagebox.showinfo("Information", message)

    def on_set_type_code(self):
        ch = int(self.txt_ch.get())
        type_code = int(self.txt_type_code.get(), 16)
        err_code = self.USBIO.PI_SetTypeCode(ch, type_code)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Type code set successfully")

    def on_clear_ch(self):
        mask = int(self.txt_ch_clear_mask.get(), 16)
        err_code = self.USBIO.PI_ClearChCount(mask)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Channel cleared successfully")

    def on_set_trigger_mode(self):
        ch = int(self.txt_ch_to_set.get())
        mode = int(self.txt_trigger_mode.get())
        err_code = self.USBIO.PI_SetTriggerMode(ch, mode)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "Trigger mode set successfully")

    def on_set_lpf_enable(self):
        mask = int(self.txt_lpf_enable.get(), 16)
        pi_byte_count =(USBIO_PI_MAX_CHANNEL + 7) // 8
        ch_enable = (c_char * pi_byte_count )()             
        for iIdx in range(pi_byte_count ):
            ch_enable[iIdx] = (mask >> (iIdx * 8)) & 0xFF        
        err_code = self.USBIO.PI_SetLPFilterEnables(ch_enable)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "LP filter enable set successfully")

    def on_set_lpf_width(self):
        ch = int(self.txt_lpf_width_ch.get())
        width = int(self.txt_lpf_width.get())
        err_code = self.USBIO.PI_SetLPFilterWidth(ch, width)
        if err_code != ERR_NO_ERR:
            messagebox.showerror("Error", f"[{err_code}]")
        else:
            messagebox.showinfo("Success", "LP filter width set successfully")

    def on_exit(self):
        self.USBIO.CloseDevice()
        self.master.quit()

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

        wdt_timeout = ctypes.c_ulong()
        self.USBIO.GetSoftWDTTimeout(ctypes.byref(wdt_timeout))
        self.lb_module_info.insert(tk.END, f"SoftWDT: {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_PI(intvl):
            pi_total =  c_char()
            self.USBIO.GetPITotal(byref(pi_total))
            self.i_pi_total=ord(pi_total.value)
            self.lb_module_info.insert(tk.END, f"PI channels: {self.i_pi_total}")             

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