import subprocess
import time
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
import sys
import os
import logging
from threading import Thread

# Detect import error for HID - usually if hidapi.dll is not found
try:
    import hid
    from hid import HIDException
except ImportError as err:
    showerror(title="Failed to start", message="Could not import \"hid\" Python library. Is hidapi.dll installed?\n\n"
              f"Error message:\n{err}")


os.environ['DM_VENDOR_ID'] = "0x2709" # set Bigscreen vendor ID for direct mode utility

class tkgui:
    def __init__(self):
        self.switcher_thread = None

        self.tkroot = tk.Tk()
        self.tkroot.title("Beyond video format selection")
        self.tkroot.geometry("400x150")

        self.frame_fatp = ttk.Frame(self.tkroot, relief=tk.GROOVE, borderwidth=6)
        self.frame_normal = ttk.Frame(self.tkroot, relief=tk.GROOVE, borderwidth=6)

        label_fatp = ttk.Label(self.tkroot, text = "FATP Modes")
        label_normal = ttk.Label(self.tkroot, text = "Normal Modes")

        self.btn_60_1920 = ttk.Button(self.frame_fatp, text="1920x1920 60Hz", command=self.vid_60_1920)
        self.btn_60_2544 = ttk.Button(self.frame_fatp, text="2544x2544 60Hz", command=self.vid_60_2544)
        self.btn_75_2544 = ttk.Button(self.frame_normal, text="2544x2544 75Hz", command=self.vid_75_2544)
        self.btn_90_1920 = ttk.Button(self.frame_normal, text="1920x1920 90Hz", command=self.vid_90_1920)
        
        label_fatp.grid(row=0, column=0)
        self.frame_fatp.grid(row=1, column=0, sticky="ew", padx=5)
        label_normal.grid(row=0, column=1)
        self.frame_normal.grid(row=1, column=1, sticky="ew", padx=5)

        self.btn_60_1920.pack(padx=10, pady=10)
        self.btn_60_2544.pack(padx=10, pady=10)
        self.btn_75_2544.pack(padx=10, pady=10)
        self.btn_90_1920.pack(padx=10, pady=10)

        self.tkroot.columnconfigure(0,weight=1)
        self.tkroot.columnconfigure(1,weight=1)
        self.tkroot.mainloop()

    def disable_ui(self):
        self.btn_60_1920["state"]=tk.DISABLED
        self.btn_60_2544["state"]=tk.DISABLED
        self.btn_75_2544["state"]=tk.DISABLED
        self.btn_90_1920["state"]=tk.DISABLED

    def enable_ui(self):
        self.btn_60_1920["state"]=tk.NORMAL
        self.btn_60_2544["state"]=tk.NORMAL
        self.btn_75_2544["state"]=tk.NORMAL
        self.btn_90_1920["state"]=tk.NORMAL


    def vid_60_1920(self):
        if(self.switcher_thread is not None):
            if(self.switcher_thread.is_alive()):
                self.switcher_thread.join()      
        self.disable_ui()
        self.switcher_thread = Thread(target=self.internal_vid_60_1920)
        self.switcher_thread.run()
    def internal_vid_60_1920(self):
        bigs = self.open_beyond()
        if(bigs is not None):
            bigs.send_feature_report(bytes([0, ord('@'), 1])) # FATP mode 1 - just 1920x1920 60Hz, no DSC
            self.disable_direct_mode()
        self.enable_ui()

    def vid_60_2544(self):
        if(self.switcher_thread is not None):
            if(self.switcher_thread.is_alive()):
                self.switcher_thread.join()      
        self.disable_ui()
        self.switcher_thread = Thread(target=self.internal_vid_60_2544)
        self.switcher_thread.run()
    def internal_vid_60_2544(self):
        bigs = self.open_beyond()
        if(bigs is not None):
            bigs.send_feature_report(bytes([0, ord('@'), 2])) # FATP mode 2 - just 2544x2544 60Hz, DSC required
            time.sleep(4)
            self.fix_dsc()
        self.enable_ui()

    def vid_75_2544(self):
        if(self.switcher_thread is not None):
            if(self.switcher_thread.is_alive()):
                self.switcher_thread.join()      
        self.disable_ui()
        self.switcher_thread = Thread(target=self.internal_vid_75_2544)
        self.switcher_thread.run()
    def internal_vid_75_2544(self):
        bigs = self.open_beyond()
        if(bigs is not None):
            bigs.send_feature_report(bytes([0, ord('d'), 2])) # EDID switch 2 - just 2544x2544 75Hz
            time.sleep(4)
            self.fix_dsc()
        self.enable_ui()
    
    def vid_90_1920(self):
        if(self.switcher_thread is not None):
            if(self.switcher_thread.is_alive()):
                self.switcher_thread.join()      
        self.disable_ui()
        self.switcher_thread = Thread(target=self.internal_vid_90_1920)
        self.switcher_thread.run()
    def internal_vid_90_1920(self):
        bigs = self.open_beyond()
        if(bigs is not None):
            bigs.send_feature_report(bytes([0, ord('d'), 1])) # EDID switch 1 - just 1920x1920 90Hz
            time.sleep(4)
            self.fix_dsc()
        self.enable_ui()

    def open_beyond(self):
        try:
            bigs = hid.Device(vid=0x35bd, pid=0x0101)
        except HIDException as hiderr:
            showerror("Could not connect", "Could not connect to Beyond through USB.\nPlease check all connections.\n\n"
                      f"Error message:\n{hiderr}")
            logging.error(f"Could not connect to Beyond through USB. Error message: {hiderr}")
            return None
            
        return bigs

    def fix_dsc(self):
        self.enable_direct_mode()
        self.start_direct_mode()
        self.disable_direct_mode()

    def enable_direct_mode(self):
        cmd = "./direct_mode_dx12.exe"
        args = ['-enable']

        logging.info(f"Running {cmd}")
        try:
            r = subprocess.run([cmd] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            logging.info(r.stdout.decode())
            if(len(r.stderr) > 0):
                #showerror("Direct Mode problem", f"Error from direct_mode_dx12.exe: {r.stderr.decode()}")
                logging.warning(r.stderr.decode())
            time.sleep(2)
        except FileNotFoundError as err:
            showerror("Could not enable Direct Mode", "Could not find direct_mode_dx12.exe.\nIs it in the program directory?")
            logging.error("Could not find direct_mode_dx12.exe.")

    def disable_direct_mode(self):
        cmd = "./direct_mode_dx12.exe"
        args = ['-disable']

        logging.info(f"Running {cmd}")
        try:
            r = subprocess.run([cmd] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            logging.info(r.stdout.decode())
            if(len(r.stderr) > 0):
                #showerror("Direct Mode problem", f"Error from direct_mode_dx12.exe: {r.stderr.decode()}")
                logging.warning(r.stderr.decode())
            time.sleep(2)
        except FileNotFoundError as err:
            showerror("Could not disable Direct Mode", "Could not find direct_mode_dx12.exe.\nIs it in the program directory?")
            logging.error("Could not find direct_mode_dx12.exe.")

    def start_direct_mode(self):
        cmd = "./direct_mode_dx12.exe"
        args = '-dsc_present_test 0 1 1 0 0x11 4 128'

        logging.info(f"Running {[cmd] + args.split(' ')}")
        try:
            r = subprocess.run([cmd] + args.split(' '), input='q\r\n'.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            logging.info(r.stdout.decode())
            if(len(r.stderr) > 0):
                #showerror("Direct Mode problem", f"Error from direct_mode_dx12.exe: {r.stderr.decode()}")
                logging.warning(r.stderr.decode())
            time.sleep(2)
        except FileNotFoundError as err:
            showerror("Could not start Direct Mode", "Could not find direct_mode_dx12.exe.\nIs it in the program directory?")
            logging.error("Could not find direct_mode_dx12.exe.")

    time.sleep(2)

if __name__ == "__main__":
    logging.basicConfig(filename='fatp_switcher.log', format='%(levelname)s:%(message)s', encoding='utf-8', level=logging.DEBUG)
    logging.info("Fatp Switcher loaded at {}".format(time.strftime('%m/%d/%Y %I:%M:%S %p',time.localtime())))
    # if hid was not loaded, then don't load the gui
    if("hid" in sys.modules):
        gui_instance = tkgui()
    else:
        logging.error("Could not load HID library.")


    
