from imgui_bundle import imgui, immapp
import hid
import struct
import time

MAX_I2C_LENGTH = 32

def open_beyond() -> hid.device | None:
    try:
        bg=hid.device()
        bg.open(0x35bd, 0x0101)
        return bg
    except:
        imgui.open_popup("Beyond connection error")
        return None

def fpga_reset():
    bg = open_beyond()
    if bg is not None:
        bg.send_feature_report(b'\x00eR')
        bg.close()

def fpga_reconfig():
    bg = open_beyond()
    if bg is not None:
        bg.send_feature_report(b'\x00eB')
        bg.close()

def fpga_dvt_1_dfu_mode():
    bg = open_beyond()
    if bg is not None:
        bg.send_feature_report(b'\x00eI\x02\x30\x00')
        bg.close()

def fpga_restart_modules(mod_resets):
    bg = open_beyond()
    if bg is not None:
        bg.send_feature_report(b'\x00eI\x02\x20' + bytes([mod_resets]))
        time.sleep(.1)
        bg.send_feature_report(b'\x00eI\x02\x20\x00')
        bg.close()

def write_i2c(reg_addr, reg_bytes):
    bg = open_beyond()
    if bg is not None:
        feat_report = bytearray(b'\x00eI')
        feat_report.append(len(reg_bytes) + 1) # +1 for the register address itself
        feat_report.append(reg_addr)
        feat_report.extend(reg_bytes)

        bg.send_feature_report(feat_report)
        bg.close()

def read_i2c(reg_addr, num_bytes):
    bg = open_beyond()
    if bg is not None:
        bg.send_feature_report(b'\x00ei'+bytes([num_bytes, reg_addr]))

        # 200ms timeout
        start_time = time.time()
        while (time.time() - start_time) < 0.2:
            retbytes = bg.read(65, timeout_ms = 10)
            if len(retbytes) > 0 and retbytes[0] == ord('e'): # fpga response
                if len(retbytes) > 1:
                    if retbytes[1] == num_bytes:
                        return retbytes[2:2+num_bytes]
                    else:
                        return []            
            if len(retbytes) > 0 and retbytes[0] == ord('E'): # any error occurred
                return []
        
        return [] # timeout
    else: # bg is None
        return []


class MyGui:
    def __init__(self):
        self.i2c_reg = "00"
        self.i2c_reg_int = 0
        self.i2c_len = 1
        self.i2c_data = ["00"]*MAX_I2C_LENGTH
        self.i2c_data_int = [0]*MAX_I2C_LENGTH
        self.i2c_read_error = False
    def reg_input_callback(self, data: imgui.InputTextCallbackData):
        if data.event_flag == imgui.InputTextFlags_.callback_edit:
            if data.buf_text_len > 2:
                data.delete_chars(2, (data.buf_text_len - 2))
                data.buf_dirty = True

        return 0
    
    def hex_int_edit(self, hexstr: str, intval: int, widg_name: str):
        imgui.push_item_width(100)
        (text_changed, hexstr) = imgui.input_text("##"+widg_name+"_str", hexstr, 
            imgui.InputTextFlags_.chars_hexadecimal | imgui.InputTextFlags_.chars_uppercase | imgui.InputTextFlags_.callback_edit,
            callback = self.reg_input_callback)
        if text_changed:
            try:
                intval = int(hexstr, 16)
            except ValueError:
                intval = 0

        imgui.same_line()
        (int_changed, intval) = imgui.input_int(widg_name, intval, 1)
        if int_changed:
            if intval > 255:
                intval = 255
            if intval < 0:
                intval = 0
            hexstr = f"{intval:02X}"
            imgui.get_current_context().input_text_state.reload_user_buf_and_move_to_end()

        imgui.pop_item_width()

        return (hexstr, intval)

    def gui(self):
        imgui.set_window_font_scale(1.5)
        imgui.text("Easily control Eyetracking FPGA!!")
        imgui.set_window_font_scale(1.0)
        imgui.separator_text("Main Controls")

        if imgui.button('Reset'):
            fpga_reset()
        if imgui.button('Reconfig'):
            fpga_reconfig()

        imgui.separator_text("I2C Control")
        self.i2c_reg, self.i2c_reg_int = self.hex_int_edit(self.i2c_reg, self.i2c_reg_int, "I2C Reg")


        imgui.set_next_item_width(100)
        (_, self.i2c_len) = imgui.input_int("Number to read/write", self.i2c_len, 1)
        # limit to 1 to MAX_I2C_LENGTH bytes
        if self.i2c_len > MAX_I2C_LENGTH:
            self.i2c_len = MAX_I2C_LENGTH
        if self.i2c_len < 1:
            self.i2c_len = 1

        imgui.set_next_window_size_constraints(imgui.ImVec2(0.0, imgui.get_text_line_height_with_spacing() * 1.0), imgui.ImVec2(imgui.FLT_MAX, imgui.get_text_line_height_with_spacing() * 10))

#        ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 1), ImVec2(FLT_MAX, ImGui::GetTextLineHeightWithSpacing() * max_height_in_lines));
        imgui.begin_child("i2c_regs", None, imgui.ChildFlags_.auto_resize_y | imgui.ChildFlags_.borders)

        imgui.begin_table("regs_table",2)
        # self.i2c_data[0], self.i2c_data_int[0] = self.hex_int_edit(self.i2c_data[0], self.i2c_data_int[0], "I2C Data 0")
        for i in range(self.i2c_len ):
            imgui.table_next_row()
            imgui.table_next_column()
            # imgui.set_next_item_width(40)
            imgui.text(f"{i+self.i2c_reg_int:02X}")
            # imgui.same_line()
            imgui.table_next_column()
            self.i2c_data[i], self.i2c_data_int[i] = self.hex_int_edit(self.i2c_data[i], self.i2c_data_int[i], f"I2C Data {i}")

        imgui.end_table()
        imgui.end_child()

        if imgui.button("Write I2C"):
            write_i2c(self.i2c_reg_int, bytes(self.i2c_data_int[0:self.i2c_len]))
        if imgui.button("Read I2C"):
            new_data = read_i2c(self.i2c_reg_int, self.i2c_len)
            if len(new_data) == 0: 
                # error result
                imgui.open_popup("i2c read error")
                self.i2c_read_error = True
            else:
                # load the new data into the inputs
                for j, dat in enumerate(new_data):
                    self.i2c_data[j] = f"{dat:02X}"
                    self.i2c_data_int[j] = dat
                # reload the text edits
                imgui.get_current_context().input_text_state.reload_user_buf_and_move_to_end()

        modal_ret = imgui.begin_popup_modal("i2c read error", self.i2c_read_error)
        if modal_ret[0]:
            imgui.separator_text("I2C Read Error")
            imgui.text("Could not read I2C from FPGA")
            if imgui.button("close"):
                imgui.close_current_popup()
                self.i2c_read_error = False
            imgui.end_popup()

        modal_ret = imgui.begin_popup_modal("Beyond connection error")
        if modal_ret[0]:
            imgui.separator_text("Unable to connect to Beyond")
            imgui.text("HID could not open. Check USB connection.")
            if imgui.button("close"):
                imgui.close_current_popup()
            imgui.end_popup()

mygui = MyGui()

immapp.run(
    gui_function=mygui.gui,  # The Gui function to run
    window_title="Eyetracking FPGA",  # the window title
    # window_size_auto=True,  # Auto size the application window given its widgets
    window_size=(400,800)
    # Uncomment the next line to restore window position and size from previous run
    # window_restore_previous_geometry==True
)