"""
A collection of Printers.

It has methods to discover printers, and provides an interface for
the methods expected from printers.
"""

from collections.abc import Set
import usb.core
import usb.util
from printer import Printer, EscPosPrinter, BrotherPrinter


class Printers:
    """
    Finds and creates a set of Printer that can be used by the Workers to print.
    """

    def __init__(self, app):
        """
        Discover printers connected to the computer and return a Collection of Printer()
        """
        self.app = app
        self.printers = self._discover_printers()

    def _discover_printers(self) -> Set[Printer]:
        """
        Gets connected USB printer devices using the pyusb library.

        We analyse the USB devices, get the ones that match
        the printer class ( 7 ) and for Brother and EPSON printers,
        try to create a Printer object that can be used by the Worker class
        to execute prints.

        Returns a set of Printer
        """

        self.app.logger.debug("Discovering USB Devices connected to this system")

        printers = set()

        # Find all connected USB devices
        devices = usb.core.find(find_all=True, custom_match=_FindClass(7))
        if not devices:
            self.app.logger.warning(
                "No USB devices of class 7 ( printers ) found or pyusb could not access the bus."
            )
            raise RuntimeError(
                "No USB devices of class 7 ( printers ) found or pyusb could not access the bus."
            )

        for dev in devices:
            # Attempt to get the manufacturer and product strings
            try:
                manufacturer = usb.util.get_string(dev, dev.iManufacturer)
            except Exception:
                manufacturer = "Unknown"

            try:
                product = usb.util.get_string(dev, dev.iProduct)
            except Exception:
                product = "Unknown"
            self.app.logger.debug(
                "Looking at %s %s (%s:%s)",
                manufacturer,
                product,
                hex(dev.idVendor),
                hex(dev.idProduct),
            )

            if manufacturer == "EPSON":
                try:
                    # We create a new EscPosPrinter()
                    self.app.logger.debug("Trying to creat a new EPSON printer")
                    prid = dev.idProduct
                    vendir = dev.idVendor
                    escpos_printer = EscPosPrinter(
                        self.app, vendor_id=vendir, device_id=prid
                    )
                except Exception as e:
                    raise e

                # If the object creation is successfull, we add it to the list of Printers
                printers.add(escpos_printer)
                self.app.logger.debug("Found a %s printer", manufacturer)

                # We already found the type of printer,
                # we don't need an extra comparaison.
                continue

            # or a Brother Printer
            if manufacturer == "Brother":
                try:
                    # We create a new BrotherPrinter()
                    self.app.logger.debug("Trying to creat a new BROTHER printer")
                    prid = dev.idProduct
                    vendir = dev.idVendor
                    brother_printer = BrotherPrinter(
                        self.app, vendor_id=vendir, device_id=prid
                    )
                except Exception as e:
                    self.app.logger.error(
                        "Could not create a %s printer class with %s:%s" , product,
                        dev.idVendor,
                        dev.idProduct,
                    )
                    raise e

                # If the object creation is successfull, we add it to the list of Printers
                printers.add(brother_printer)
                self.app.logger.debug("Found a %s printer" , manufacturer)

        self.app.logger.debug("Found %s printers" , len(printers))
        return printers

    def any(self) -> Printer:
        """
        Return a dict key: UUID, value: Printer, with any connected printer.
        """
        if len(self.printers) > 0:
            for i in self.printers:
                return i
        else:
            raise RuntimeError("No printers available")

    # def get_printer(self, printer_type):
    #     """
    #     Return a specific printer

    #     printer_type -- a printer type
    #     """
    #     return NotImplementedError()


class _FindClass:
    """
    Use by usb.core to modify the way USB devices are found
    Taken from pyUSB documentation on Github
    """
    def __init__(self, class_):
        self._class = class_

    def __call__(self, device):
        # first, let's check the device
        if device.bDeviceClass == self._class:
            return True
        # ok, transverse all devices to find an
        # interface that matches our class
        for cfg in device:
            # find_descriptor: what's it?
            intf = usb.util.find_descriptor(cfg, bInterfaceClass=self._class)
            if intf is not None:
                return True

        return False
