"""
IMAP sensor support.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.imap/
"""
import asyncio
import logging

import async_timeout
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
    EVENT_HOMEASSISTANT_STOP)
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(__name__)

REQUIREMENTS = ['aioimaplib==0.7.13']

CONF_SERVER = 'server'
CONF_FOLDER = 'folder'
CONF_SEARCH = 'search'

DEFAULT_PORT = 993

ICON = 'mdi:email-outline'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_NAME): cv.string,
    vol.Required(CONF_USERNAME): cv.string,
    vol.Required(CONF_PASSWORD): cv.string,
    vol.Required(CONF_SERVER): cv.string,
    vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
    vol.Optional(CONF_FOLDER, default='INBOX'): cv.string,
    vol.Optional(CONF_SEARCH, default='UnSeen UnDeleted'): cv.string,
})


async def async_setup_platform(hass,
                               config,
                               async_add_entities,
                               discovery_info=None):
    """Set up the IMAP platform."""
    sensor = ImapSensor(config.get(CONF_NAME),
                        config.get(CONF_USERNAME),
                        config.get(CONF_PASSWORD),
                        config.get(CONF_SERVER),
                        config.get(CONF_PORT),
                        config.get(CONF_FOLDER),
                        config.get(CONF_SEARCH))
    if not await sensor.connection():
        raise PlatformNotReady

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.shutdown())
    async_add_entities([sensor], True)


class ImapSensor(Entity):
    """Representation of an IMAP sensor."""

    def __init__(self, name, user, password, server, port, folder, search):
        """Initialize the sensor."""
        self._name = name or user
        self._user = user
        self._password = password
        self._server = server
        self._port = port
        self._folder = folder
        self._email_count = None
        self._search = search
        self._connection = None
        self._does_push = None
        self._idle_loop_task = None

    async def async_added_to_hass(self):
        """Handle when an entity is about to be added to Home Assistant."""
        if not self.should_poll:
            self._idle_loop_task = self.hass.loop.create_task(self.idle_loop())

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """Return the icon to use in the frontend."""
        return ICON

    @property
    def state(self):
        """Return the number of emails found."""
        return self._email_count

    @property
    def available(self):
        """Return the availability of the device."""
        return self._connection is not None

    @property
    def should_poll(self):
        """Return if polling is needed."""
        return not self._does_push

    async def connection(self):
        """Return a connection to the server, establishing it if necessary."""
        import aioimaplib

        if self._connection is None:
            try:
                self._connection = aioimaplib.IMAP4_SSL(
                    self._server, self._port)
                await self._connection.wait_hello_from_server()
                await self._connection.login(self._user, self._password)
                await self._connection.select(self._folder)
                self._does_push = self._connection.has_capability('IDLE')
            except (aioimaplib.AioImapException, asyncio.TimeoutError):
                self._connection = None

        return self._connection

    async def idle_loop(self):
        """Wait for data pushed from server."""
        import aioimaplib

        while True:
            try:
                if await self.connection():
                    await self.refresh_email_count()
                    await self.async_update_ha_state()

                    idle = await self._connection.idle_start()
                    await self._connection.wait_server_push()
                    self._connection.idle_done()
                    with async_timeout.timeout(10):
                        await idle
                else:
                    await self.async_update_ha_state()
            except (aioimaplib.AioImapException, asyncio.TimeoutError):
                self.disconnected()

    async def async_update(self):
        """Periodic polling of state."""
        import aioimaplib

        try:
            if await self.connection():
                await self.refresh_email_count()
        except (aioimaplib.AioImapException, asyncio.TimeoutError):
            self.disconnected()

    async def refresh_email_count(self):
        """Check the number of found emails."""
        if self._connection:
            await self._connection.noop()
            result, lines = await self._connection.search(self._search)

            if result == 'OK':
                self._email_count = len(lines[0].split())
            else:
                _LOGGER.error("Can't parse IMAP server response to search "
                              "'%s':  %s / %s",
                              self._search, result, lines[0])

    def disconnected(self):
        """Forget the connection after it was lost."""
        _LOGGER.warning("Lost %s (will attempt to reconnect)", self._server)
        self._connection = None

    async def shutdown(self):
        """Close resources."""
        if self._connection:
            if self._connection.has_pending_idle():
                self._connection.idle_done()
            await self._connection.logout()
        if self._idle_loop_task:
            self._idle_loop_task.cancel()
