import asyncio
import logging
from homeassistant.const import EVENT_HOMEASSISTANT_STOP


_LOGGER = logging.getLogger(__name__)


class ReconnectingClient:

    def __init__(self, hass, host, port, connection_name, receive_line_callback, connection_status_changed_callback):
        self.connected = False
        self.reconnect_time_start = 1
        self.reconnect_time_max = 60
        self.reconnect_time = self.reconnect_time_start
        self.hass = hass
        self._host = host
        self._port = port
        self._receive_line_callback = receive_line_callback
        self._connection_status_changed_callback = connection_status_changed_callback
        self._run = False
        self._writer = None
        self._connection_last_state = 'UNKNOWN'
        self._connection_name = connection_name

    async def start(self):
        self._run = True
        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
        self.hass.loop.create_task(self._connection())

    async def stop(self):
        self._run = False
        self.connected = False

    def write_line(self, line):
        if self._writer:
            line += '\n'
            self._writer.write(line.encode())

    async def _connection(self):
        try:
            reader, writer = await asyncio.open_connection(self._host, self._port)
            _LOGGER.info("Connected to {} {}:{}".format(self._connection_name, self._host, self._port))
            self._connection_last_state = 'CONNECTED'

            self._writer = writer
            self.connected = True
            await self._connection_status_changed_callback('connected')

            self.reconnect_time = self.reconnect_time_start
            while self._run:
                line = await reader.readline()
                if not line:
                    raise OSError("Disconnect")
                line = line.decode()
                _LOGGER.debug("{} received line: {}".format(self._connection_name, line))
                await self._receive_line_callback(line)
        except OSError:
            if self._connection_last_state != 'FAILED':
                notification_text = "{} connection to {}:{} failed".format(self._connection_name,self._host, self._port)
                self.hass.components.persistent_notification.async_create(notification_text, title="No connection")
                _LOGGER.error("Connection to {} failed {}:{}".format(self._connection_name, self._host, self._port))
                await self._connection_status_changed_callback('disconnected')
            self._connection_last_state = 'FAILED'

            self.connected = False
            await asyncio.sleep(self.reconnect_time)
            self.reconnect_time = min(2 * self.reconnect_time, self.reconnect_time_max)
            self.hass.loop.create_task(self._connection())
