import usb.core
import usb.util
from ..helpers import error_print, debug_print
import threading
from functools import wraps
from kivy.clock import Clock

def parse_response(r):
    return ''.join([chr(x) for x in r]).strip('\x00')

class USBLock():
    def __call__(self, func):
        @wraps(func)
        def inner(selfp, *args, **kwargs):
            if selfp.lock is not None:
                with selfp.lock:
                    return func(selfp, *args, **kwargs)
            else:
                error_print("Cannot run function with lock: device not initialized")
        return inner

class Generic():
    def __init__(self, name, id="0000:0000",
                 detach_device=False, configuration_number=None, input_endpoint=None, output_endpoint=None,
                 healthcheck=None, status=None,
                 **kwargs):
        self.name = name
        [self.vendor_id, self.product_id] = [int(f"0x{f}", 0) for f in id.split(":")]
        self.detach_device = detach_device
        self.configuration_number = configuration_number
        self.input_endpoint = input_endpoint
        self.output_endpoint = output_endpoint
        self.healthcheck = healthcheck
        self.status = status

        self.device = None
        self.input = None
        self.output = None
        self.lock = None

    def initialize(self, *args):
        self.device = usb.core.find(idVendor=self.vendor_id, idProduct=self.product_id)
        if self.device is None:
            error_print(f"Could not find configured USB device {hex(self.vendor_id).removeprefix("0x")}:{hex(self.product_id).removeprefix("0x")}, rescheduling in 10s")
            Clock.schedule_once(self.initialize, 10)
            return

        if self.detach_device:
            try:
                self.device.detach_kernel_driver(0)
            except:
                pass

        self.device.set_configuration(self.configuration_number)

        if self.input_endpoint is not None:
            [intf, alt, endpoint] = [int(f) for f in self.input_endpoint.split(".")]
            self.input = self.device.get_active_configuration()[(intf, alt)][endpoint]
        if self.output_endpoint is not None:
            [intf, alt, endpoint] = [int(f) for f in self.output_endpoint.split(".")]
            self.output = self.device.get_active_configuration()[(intf, alt)][endpoint]

        self.lock = threading.Lock()

    @USBLock()
    def is_alive(self):
        if self.device is None:
            return False
        elif self.healthcheck is not None:
            response = self._send_and_get(self.healthcheck.get("command", ""))
            return response is not None and self.healthcheck.get("response", "") in response.splitlines()
        else:
            return "???"

    def _send_and_get(self, usb_command, **kwargs):
        debug_print(f"USB Generic ({self.name}): Sending {usb_command}")

        try:
            self.output.write(usb_command)
            responseb = usb.util.create_buffer(1024)
            self.input.read(responseb)
            response = parse_response(responseb)
        except usb.core.USBTimeoutError:
            error_print(f"USB Generic ({self.name}): Failed to read response from command {usb_command}")
            response = None
        except usb.core.USBError as e:
            error_print(f"USB Generic ({self.name}): Failed to send command {usb_command}: {e}")
            response = None

        debug_print(f"USB Generic ({self.name}): {response}")
        return response

    @USBLock()
    def run_command(self, usb_command, **kwargs):
        return self._send_and_get(self, usb_command, **kwargs)

    @USBLock()
    def get_status(self):
        if self.status is not None:
            response = self._send_and_get(self.status.get("command", ""))
            if response is not None:
                return (response.splitlines(), self.status.get("repeat_delay", 30))

