import threading
import time

import hid
import pythoncom
import win32com.client
import win32com.client


class DeviceMonitor:
    def __init__(self):
        self.on_new_device = None  # callback(device_dict, hid_list_index, hid_device_list, prev_device_paths) return list of processed devices' paths
        self.previous_devices = []

    def refresh_hid_tree(self):
        #print("[DeviceMonitor] refresh_hid_tree()")
        new_devs = reversed(hid.enumerate()) # enumerate() populates hubs in opposite order of tree viewer, hub children sporadically
        # print out previous vs current for debugging
        #print(f"  → previous_devices ({len(self.previous_devices)}):")
        #for d in self.previous_devices:
            #print(f"     • {d['vendor_id']:04X}:{d['product_id']:04X} @ {d['path']}")
        #print(f"  → current_devices ({len(new_devs)}):")
        # for nd in new_devs:
        #     print("     • {}--{}:{} @ {}".format(nd['product_string'], hex(nd['vendor_id']), hex(nd['product_id']), nd['serial_number']))
        for hid_index, nd in enumerate(new_devs):
            if nd['path'] not in self.previous_devices and callable(self.on_new_device):
                #print(f"[DeviceMonitor]  + New device: VID=0x{nd['vendor_id']:04X} "
                #      f"PID=0x{nd['product_id']:04X} path={nd['path']}")
                processed_devices = self.on_new_device(nd, hid_index, new_devs, self.previous_devices)
                self.previous_devices.append(nd['path'])
                if isinstance(processed_devices, list) and len(processed_devices) > 0:
                    # if the callback returns a list of processed devices, log them to prevent duplicates
                    for processed_dev_path in processed_devices:
                        if processed_dev_path not in self.previous_devices:
                            self.previous_devices.append(processed_dev_path)
                    # print('!!!! ADDED PROCESSED DEVICE !!!! -- ', processed_devices, self.previous_devices)  # debug

    def register_on_new_hid_device(self, callback):
        #print("[DeviceMonitor] register_on_new_hid_device()")
        self.on_new_device = callback

    def reset_history(self):
        #print("[DeviceMonitor] reset_history()")
        self.previous_devices = []

def on_new_headset(dev, hid_index, new_devs, prev_devs):
    # filter to your VID/PID
    if dev['vendor_id'] == 0x35bd and dev['product_id'] == 0x0101:
        print("Found headset: {}".format(dev.get('serial_number')))
        device = hid.device()
        device.open_path(dev['path'])
        device.send_feature_report(bytes([0, ord('L'), 255, 255, 0]))

instance = DeviceMonitor()
instance.register_on_new_hid_device(on_new_headset)
instance.refresh_hid_tree()
instance.reset_history()

def watch_usb_events():
    # Initialize COM for this thread
    pythoncom.CoInitialize()
    try:
        wmi = win32com.client.Dispatch("WbemScripting.SWbemLocator") \
                           .ConnectServer(".", "root\\CIMV2")
        query = (
            "SELECT * FROM __InstanceCreationEvent "
            "WITHIN 2 WHERE TargetInstance ISA 'Win32_USBControllerDevice'"
        )
        watcher = wmi.ExecNotificationQuery(query)

        while True:
            event = watcher.NextEvent()  # blocks until a new device appears
            pnp = event.TargetInstance.Dependent  # Win32_PnPEntity
            #print(f"[WMI] USB arrival: {pnp}")
            instance.refresh_hid_tree()
    finally:
        # Clean up COM on thread exit
        pythoncom.CoUninitialize()

# start the watcher in a daemon thread
t = threading.Thread(target=watch_usb_events, daemon=True)
t.start()

# keep main alive
if __name__ == "__main__":
    # start the watcher in a daemon thread
    t = threading.Thread(target=watch_usb_events, daemon=True)
    t.start()

    # keep main alive
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Exiting.")
# # Unregister device change notifications
# windll.user32.UnregisterDeviceNotification(hdev_notify)
