"""
Select a scanner if none has been selected yet or if the currently selected one
doesn't exist anymore.

IMPORTANT: This plugin must list the devices only if required. Otherwise
it tends to mess up some Sane backend like the Brother one for instance.
"""
import logging

import Levenshtein

import openpaperwork_core


LOGGER = logging.getLogger(__name__)


class Plugin(openpaperwork_core.PluginBase):
    PRIORITY = 1000

    def get_interfaces(self):
        return []

    def get_deps(self):
        return [
            {
                'interface': 'config',
                'defaults': ['openpaperwork_core.config'],
            },
            {
                'interface': 'scan',
                'defaults': ['paperwork_backend.docscan.libinsane'],
            },
        ]

    def init(self, core):
        super().init(core)

    def scan_get_scanner_promise(self, scanner_dev_id=None, autoselect=True):
        """
        Overload the method 'scan_get_scanner_promise' to check whether
        the device actually exists, and if not find the closest one.
        """
        if not autoselect:
            return None

        def get_scanner(scanner_dev_id=None):
            scanner = None
            if scanner_dev_id is None:
                scanner_dev_id = self.core.call_success(
                    "config_get", "scanner_dev_id"
                )
            try:
                if scanner_dev_id is not None:
                    scanner = self.core.call_success(
                        "scan_get_scanner", scanner_dev_id
                    )
            except Exception as exc:
                # may happen if ID has changed (those containing the USB
                # bus + ID for instance)
                LOGGER.warning(
                    "Failed to get scanner '%s'."
                    " Will try to find the closest one",
                    scanner_dev_id, exc_info=exc
                )
            if scanner is not None:
                return scanner

            devs = self.core.call_success("scan_list_scanners")
            scanner_dev_id = self._find_dev_id(devs, scanner_dev_id)
            if scanner_dev_id is None:
                return None
            return self.core.call_success("scan_get_scanner", scanner_dev_id)

        return openpaperwork_core.promise.ThreadedPromise(
            self.core, get_scanner, args=(scanner_dev_id,)
        )

    def _find_dev_id(self, devs, current_dev_id=None):
        devs = [dev[0] for dev in devs]
        LOGGER.info("Available scanners: %s", devs)

        if len(devs) <= 0:
            LOGGER.error("No scanner found !")
            return None
        elif current_dev_id is None:
            # pick a scanner at random.
            selected = devs[0]
        else:
            LOGGER.info("Previously selected scanner: %s", current_dev_id)
            # look for the closest scanner ID
            devs = [
                (Levenshtein.distance(dev, current_dev_id), dev)
                for dev in devs
            ]
            devs.sort()
            selected = devs[0][1]

        LOGGER.info("Selected scanner: %s", selected)
        # ASSUMPTION(Jflesch): we assume here the requested scanner was
        # the one selected in the configuration --> we update the configuration
        # to avoid listing devices every time.
        self.core.call_success("config_put", "scanner_dev_id", selected)
        return selected
