# import threading
# from collections.abc import Callable
# from collections import deque
import struct

# import numpy as np
from imgui_bundle import immapp, imgui #, implot, ImVec2
import hid

from hid_lib import *
from config_lib import *


class my_gui:
    def __init__(self):
        self.reset_vars()
        self.error_text = ''
        self.never_loaded = True

    def reset_vars(self):
        self.vars = {
            SigTag.Serial: [False, ''],
            SigTag.HMD_Serial: [False, ''],
            SigTag.Tracking_Serial: [False, ''],
            SigTag.Fan_Speed: [False, 0],
            SigTag.Prox_Cal: [False, 0],
            SigTag.RGB_Color: [False, [1, 0, 1]],
            SigTag.Prox_Disable: [False, False],
            SigTag.Linkbox_v1: [False, False],
            SigTag.FATP_Mode: [False, False],
            SigTag.Display_Brightness: [False, 26],
            SigTag.Prox_Threshold: [False, 1500],
            SigTag.Prox_Hysteresis: [False, 100],
            SigTag.EDID_Switch: [False, 0],
            SigTag.User_Prox_Trim: [False, 0],
            SigTag.VXR_Sleep_Enable: [False, False]
        }

    def load_config(self):
        try:
            bynd = connect_beyond()
            config_bytes = read_sig(bynd)
            bynd.close()
            if len(config_bytes) < USER_SIG_LENGTH:
                self.error_text = "Could not read config memory"
                imgui.open_popup("Error")
            config_data = parse_sig(config_bytes)
            self.reset_vars()
            for ckey, cvalue in config_data.items():
                if(ckey == SigTag.RGB_Color):
                    # 3 bytes for R, G, B
                    self.vars[ckey] = [True, [float(cvalue[0])/255.0, float(cvalue[1])/255.0, float(cvalue[2])/255.0]]
                if(ckey == SigTag.Serial or ckey == SigTag.HMD_Serial or ckey == SigTag.Tracking_Serial):
                    # variable length string
                    self.vars[ckey] = [True, cvalue.decode()]
                if(ckey == SigTag.Fan_Speed or ckey == SigTag.EDID_Switch):
                    # single byte number
                    self.vars[ckey] = [True, int(cvalue[0])]
                if(ckey == SigTag.Prox_Disable or ckey == SigTag.Linkbox_v1 or ckey == SigTag.FATP_Mode or ckey == SigTag.VXR_Sleep_Enable):
                    # one byte, boolean
                    self.vars[ckey] = [True, cvalue != b'\x00']
                if(ckey == SigTag.Prox_Cal or ckey == SigTag.Prox_Threshold or ckey == SigTag.Prox_Hysteresis):
                    # two byte number
                    self.vars[ckey] = [True, int(cvalue[0] + 256*cvalue[1])]
                if(ckey == SigTag.User_Prox_Trim):
                    # two byte SIGNED number
                    newval = struct.unpack('<h', bytes([cvalue[0], cvalue[1]]))[0]
                    self.vars[ckey] = [True, newval]
                if(ckey == SigTag.Display_Brightness):
                    # two byte number, but divide by 0x03FF (1023) to show 0-100% on the slider
                    raw_brightness = struct.unpack('<H',cvalue)[0]
                    self.vars[ckey] = [True, int(100*raw_brightness/1023)]
            self.never_loaded = False

        except OSError:
            # failed to connect
            self.error_text = "Could not connect to Beyond"
            imgui.open_popup("Error")

    def save_config(self):
        try:
            bynd = connect_beyond()
            config_data = {}
            for ckey, cvalue in self.vars.items():
                if(cvalue[0]):
                    if(ckey == SigTag.RGB_Color):
                        # 3 bytes for R, G, B
                        cR = int(min(cvalue[1][0]*255, 255))
                        cG = int(min(cvalue[1][1]*255, 255))
                        cB = int(min(cvalue[1][2]*255, 255))
                        config_data[ckey] = struct.pack('<BBB',cR, cG, cB)
                    if(ckey == SigTag.Serial or ckey == SigTag.HMD_Serial or ckey == SigTag.Tracking_Serial):
                        # variable length string
                        config_data[ckey] = cvalue[1].encode()
                    if(ckey == SigTag.Fan_Speed or ckey == SigTag.EDID_Switch):
                        # single byte number
                        if cvalue[1] > 255:
                            config_data[ckey] = bytes([255])
                        else:
                            config_data[ckey] = bytes([int(cvalue[1])])
                    if(ckey == SigTag.Prox_Disable or ckey == SigTag.Linkbox_v1 or ckey == SigTag.FATP_Mode or ckey == SigTag.VXR_Sleep_Enable):
                        # one byte, boolean
                        config_data[ckey] = b'\x01' if cvalue[1] else b'\x00'
                    if(ckey == SigTag.Prox_Cal or ckey == SigTag.Prox_Threshold or ckey == SigTag.Prox_Hysteresis):
                        # two byte number
                        if cvalue[1] > 65535:
                            config_data[ckey] = struct.pack('<H',65535)
                        else:
                            config_data[ckey] = struct.pack('<H',int(cvalue[1]))
                    if(ckey == SigTag.User_Prox_Trim):
                        # two byte SIGNED number
                        config_data[ckey] = struct.pack('<h', int(cvalue[1]))
                    if(ckey == SigTag.Display_Brightness):
                        # two byte number, but multiply by 0x03FF (1023) to convert from slider percent
                        cvalue[1] = int(cvalue[1]/100.0 * 1023)
                        if cvalue[1] > 1023:
                            config_data[ckey] = struct.pack('<H',1023)
                        else:
                            config_data[ckey] = struct.pack('<H',cvalue[1])
            try:
                config_bytes = create_sig(config_data)
                save_sig(bynd, config_bytes)
                bynd.close()

            except (ValueError, IndexError):
                self.error_text = "Could not create properly formatted config memory region"
                imgui.open_popup("Error")
            except (OSError, RuntimeError) as e:
                self.error_text = "Error while writing config memory region: "+str(e)

        except OSError:
            # failed to connect
            self.error_text = "Could not connect to Beyond"
            imgui.open_popup("Error")

    def gui(self):
        i = 0
        _,self.vars[SigTag.Serial][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Serial][0])
        imgui.same_line()
        _,self.vars[SigTag.Serial][1] = imgui.input_text("PCB Serial",self.vars[SigTag.Serial][1])
        i = i + 1

        _,self.vars[SigTag.HMD_Serial][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.HMD_Serial][0])
        imgui.same_line()
        _,self.vars[SigTag.HMD_Serial][1] = imgui.input_text("HMD Serial",self.vars[SigTag.HMD_Serial][1])
        i = i + 1
        
        _,self.vars[SigTag.Tracking_Serial][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Tracking_Serial][0])
        imgui.same_line()
        _,self.vars[SigTag.Tracking_Serial][1] = imgui.input_text("Tracking (Lighthouse) Serial",self.vars[SigTag.Tracking_Serial][1])
        i = i + 1
         
        _,self.vars[SigTag.Fan_Speed][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Fan_Speed][0])
        imgui.same_line()
        _,self.vars[SigTag.Fan_Speed][1] = imgui.drag_int("Fan Speed", self.vars[SigTag.Fan_Speed][1], 1, 0, 100, "%d", imgui.SliderFlags_.always_clamp.value)
        i = i + 1
        
        _,self.vars[SigTag.Prox_Cal][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Prox_Cal][0])
        imgui.same_line()
        _,self.vars[SigTag.Prox_Cal][1] = imgui.drag_int("Prox Calibration", self.vars[SigTag.Prox_Cal][1], 1, 0, 65535, "%d", imgui.SliderFlags_.always_clamp.value)
        i = i + 1
        
        _,self.vars[SigTag.RGB_Color][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.RGB_Color][0])
        imgui.same_line()
        _,self.vars[SigTag.RGB_Color][1] = imgui.color_edit3("RGB Color", self.vars[SigTag.RGB_Color][1])
        i = i + 1
        
        _,self.vars[SigTag.Prox_Disable][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Prox_Disable][0])
        imgui.same_line()
        _,self.vars[SigTag.Prox_Disable][1] = imgui.checkbox("Disable Proximity Sensor", self.vars[SigTag.Prox_Disable][1])
        i = i + 1
        
        _,self.vars[SigTag.Linkbox_v1][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Linkbox_v1][0])
        imgui.same_line()
        _,self.vars[SigTag.Linkbox_v1][1] = imgui.checkbox("Linkbox is v1", self.vars[SigTag.Linkbox_v1][1])
        i = i + 1
        
        _,self.vars[SigTag.FATP_Mode][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.FATP_Mode][0])
        imgui.same_line()
        _,self.vars[SigTag.FATP_Mode][1] = imgui.checkbox("FATP Mode", self.vars[SigTag.FATP_Mode][1])
        i = i + 1
        
        _,self.vars[SigTag.Display_Brightness][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Display_Brightness][0])
        imgui.same_line()
        _,self.vars[SigTag.Display_Brightness][1] = imgui.drag_int("Display Brightness", self.vars[SigTag.Display_Brightness][1], 1, 0, 100, "%d%%", imgui.SliderFlags_.always_clamp.value)
        i = i + 1
        
        _,self.vars[SigTag.Prox_Threshold][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Prox_Threshold][0])
        imgui.same_line()
        _,self.vars[SigTag.Prox_Threshold][1] = imgui.drag_int("Proximity Threshold", self.vars[SigTag.Prox_Threshold][1], 1, 0, 65535, "%d", imgui.SliderFlags_.always_clamp.value)
        i = i + 1

        _,self.vars[SigTag.Prox_Hysteresis][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.Prox_Hysteresis][0])
        imgui.same_line()
        _,self.vars[SigTag.Prox_Hysteresis][1] = imgui.drag_int("Proximity Hysteresis", self.vars[SigTag.Prox_Hysteresis][1], 1, 0, 65535, "%d", imgui.SliderFlags_.always_clamp.value)
        i = i + 1

        _,self.vars[SigTag.EDID_Switch][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.EDID_Switch][0])
        imgui.same_line()
        edid_items = ['Both 75Hz and 90Hz','90Hz Only','75Hz Only']
        _,self.vars[SigTag.EDID_Switch][1] = imgui.combo('EDID Switch',self.vars[SigTag.EDID_Switch][1], edid_items)
        i = i + 1

        _,self.vars[SigTag.User_Prox_Trim][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.User_Prox_Trim][0])
        imgui.same_line()
        _,self.vars[SigTag.User_Prox_Trim][1] = imgui.drag_int("User Proximity Trim", self.vars[SigTag.User_Prox_Trim][1], 1, -32768, 32767, "%d", imgui.SliderFlags_.always_clamp.value)
        i = i + 1

        _,self.vars[SigTag.VXR_Sleep_Enable][0] = imgui.checkbox(f"Enabled##{i}", self.vars[SigTag.VXR_Sleep_Enable][0])
        imgui.same_line()
        _,self.vars[SigTag.VXR_Sleep_Enable][1] = imgui.checkbox("VXR Sleep Enabled", self.vars[SigTag.VXR_Sleep_Enable][1])

        if imgui.button("Load from HMD"):
            self.load_config()

        if imgui.button("Save to HMD"):
            
            num_active_keys = 0
            for ckey, cvalue in self.vars.items():
                if cvalue[0]:
                    num_active_keys = num_active_keys + 1

            if self.never_loaded:
                imgui.open_popup("Never Loaded")
            elif num_active_keys == 0:
                imgui.open_popup('Config Empty')
            else:
                self.save_config()

        if imgui.begin_popup_modal("Error")[0]:
            imgui.text(self.error_text)
            imgui.separator()
            if imgui.button("OK"):
                imgui.close_current_popup()
            imgui.end_popup()

        save_config_from_modal = False
        if imgui.begin_popup_modal("Never Loaded")[0]:
            imgui.text('Never loaded config from Beyond. Are you sure you want to overwrite?')
            imgui.separator()
            if imgui.button("Yes"):
                imgui.close_current_popup()
                save_config_from_modal = True
            imgui.same_line()
            if imgui.button("No"):
                imgui.close_current_popup()
            imgui.end_popup()

        if imgui.begin_popup_modal("Config Empty")[0]:
            imgui.text('Saving an empty config page. Are you sure you want to do this?')
            imgui.separator()
            if imgui.button("Yes"):
                imgui.close_current_popup()
                save_config_from_modal = True
            imgui.same_line()
            if imgui.button("No"):
                imgui.close_current_popup()
            imgui.end_popup()

        if save_config_from_modal:
            self.save_config()


if __name__ == '__main__':
    mgui = my_gui()

    immapp.run(
        gui_function=mgui.gui,  # The Gui function to run
        window_title="Configuration Editor",  # the window title
        window_size=(900,480),
        # window_size_auto=True,  # Auto size the application window given its widgets
        # Uncomment the next line to restore window position and size from previous run
        # window_restore_previous_geometry=True

    )

