from logging import getLogger
from typing import Optional

from nperipherals_core.model.device import ReaderDevice, DeviceServiceThread
from nperipherals_core.model.custom_exceptions import DeviceNotConnected
from nperipherals_core.model.repository import NotificationRepository

logger = getLogger(__name__)


class ReaderDeviceServiceThread(DeviceServiceThread):
    def __init__(self, device, notification_repository):
        # type: (ReaderDevice, NotificationRepository) -> None

        super(ReaderDeviceServiceThread, self).__init__()

        self.device = device
        self.notification_repository = notification_repository

        self.SLEEP_IN_SECONDS = 1

    def run(self):
        # type: () -> None

        reader_device_name = self.device.get_name()
        logger.info("Starting device [{}]".format(reader_device_name))

        while self.running:
            self._initialize(reader_device_name)
            self._connect(reader_device_name)
            self._read(reader_device_name)
            self._disconnect(reader_device_name)

    def _initialize(self, reader_device_name):
        # type: (unicode) -> None

        while self.running:
            try:
                logger.info("Initializing device [{}]".format(reader_device_name))
                self.device.initialize()
                break
            except Exception as _:
                logger.exception("Error initializing device [{}]".format(reader_device_name))
                self._wait()
                continue

    def _connect(self, reader_device_name):
        # type: (unicode) -> None

        while self.running:
            try:
                logger.info("Connecting device [{}]".format(reader_device_name))
                self.device.connect()
                logger.info("Connected to device [{}]".format(reader_device_name))
                break
            except Exception as _:
                logger.exception("Error connecting to device [{}]".format(reader_device_name))
                self._wait()
                continue

    def _read(self, reader_device_name):
        # type: (unicode) -> None

        while self.running:
            try:
                logger.debug("Reading from device [{}]".format(reader_device_name))
                data = self.device.read()

                logger.debug("Notifying data from device [{}]".format(reader_device_name))
                self.notification_repository.notify(reader_device_name, data)

                self._wait(0.1)
            except DeviceNotConnected as _:
                logger.warning("Error connecting to [{}]".format(reader_device_name))
                break
            except Exception as _:
                logger.warning("Error reading data from [{}]".format(reader_device_name))
                self._wait()
                continue

    def _disconnect(self, reader_device_name):
        # type: (unicode) -> None

        try:
            logger.info("Disconnecting device [{}]".format(reader_device_name))
            self.device.disconnect()
            logger.info("Disconnecting to device [{}]".format(reader_device_name))
        except Exception as _:
            logger.warning("Error Disconnecting to device [{}]".format(reader_device_name))

    def _wait(self, seconds=None):
        # type: (Optional[float]) -> None

        self.sleep(seconds or self.SLEEP_IN_SECONDS)
