"""Support for WeMo device discovery."""
import logging

import requests
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.discovery import SERVICE_WEMO
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery

from homeassistant.const import (
    EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)

DOMAIN = 'wemo'

# Mapping from Wemo model_name to component.
WEMO_MODEL_DISPATCH = {
    'Bridge':  'light',
    'CoffeeMaker': 'switch',
    'Dimmer': 'light',
    'Humidifier': 'fan',
    'Insight': 'switch',
    'LightSwitch': 'switch',
    'Maker':   'switch',
    'Motion': 'binary_sensor',
    'Sensor':  'binary_sensor',
    'Socket':  'switch',
}

SUBSCRIPTION_REGISTRY = None
KNOWN_DEVICES = []

_LOGGER = logging.getLogger(__name__)


def coerce_host_port(value):
    """Validate that provided value is either just host or host:port.

    Returns (host, None) or (host, port) respectively.
    """
    host, _, port = value.partition(':')

    if not host:
        raise vol.Invalid('host cannot be empty')

    if port:
        port = cv.port(port)
    else:
        port = None

    return host, port


CONF_STATIC = 'static'
CONF_DISCOVERY = 'discovery'

DEFAULT_DISCOVERY = True

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_STATIC, default=[]): vol.Schema([
            vol.All(cv.string, coerce_host_port)
        ]),
        vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean,
    }),
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
    """Set up for WeMo devices."""
    hass.data[DOMAIN] = config

    if DOMAIN in config:
        hass.async_create_task(hass.config_entries.flow.async_init(
            DOMAIN, context={'source': config_entries.SOURCE_IMPORT}))

    return True


async def async_setup_entry(hass, entry):
    """Set up a wemo config entry."""
    import pywemo

    config = hass.data[DOMAIN]

    # Keep track of WeMo devices
    devices = []

    # Keep track of WeMo device subscriptions for push updates
    global SUBSCRIPTION_REGISTRY
    SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry()
    await hass.async_add_executor_job(SUBSCRIPTION_REGISTRY.start)

    def stop_wemo(event):
        """Shutdown Wemo subscriptions and subscription thread on exit."""
        _LOGGER.debug("Shutting down WeMo event subscriptions")
        SUBSCRIPTION_REGISTRY.stop()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)

    def setup_url_for_device(device):
        """Determine setup.xml url for given device."""
        return 'http://{}:{}/setup.xml'.format(device.host, device.port)

    def setup_url_for_address(host, port):
        """Determine setup.xml url for given host and port pair."""
        if not port:
            port = pywemo.ouimeaux_device.probe_wemo(host)

        if not port:
            return None

        return 'http://{}:{}/setup.xml'.format(host, port)

    def discovery_dispatch(service, discovery_info):
        """Dispatcher for incoming WeMo discovery events."""
        # name, model, location, mac
        model_name = discovery_info.get('model_name')
        serial = discovery_info.get('serial')

        # Only register a device once
        if serial in KNOWN_DEVICES:
            _LOGGER.debug(
                "Ignoring known device %s %s", service, discovery_info)
            return

        _LOGGER.debug("Discovered unique WeMo device: %s", serial)
        KNOWN_DEVICES.append(serial)

        component = WEMO_MODEL_DISPATCH.get(model_name, 'switch')

        discovery.load_platform(
            hass, component, DOMAIN, discovery_info, config)

    discovery.async_listen(hass, SERVICE_WEMO, discovery_dispatch)

    def discover_wemo_devices(now):
        """Run discovery for WeMo devices."""
        _LOGGER.debug("Beginning WeMo device discovery...")
        _LOGGER.debug("Adding statically configured WeMo devices...")
        for host, port in config.get(DOMAIN, {}).get(CONF_STATIC, []):
            url = setup_url_for_address(host, port)

            if not url:
                _LOGGER.error(
                    'Unable to get description url for WeMo at: %s',
                    '{}:{}'.format(host, port) if port else host)
                continue

            try:
                device = pywemo.discovery.device_from_description(url, None)
            except (requests.exceptions.ConnectionError,
                    requests.exceptions.Timeout) as err:
                _LOGGER.error("Unable to access WeMo at %s (%s)", url, err)
                continue

            if not [d[1] for d in devices
                    if d[1].serialnumber == device.serialnumber]:
                devices.append((url, device))

        if config.get(DOMAIN, {}).get(CONF_DISCOVERY, DEFAULT_DISCOVERY):
            _LOGGER.debug("Scanning network for WeMo devices...")
            for device in pywemo.discover_devices():
                if not [d[1] for d in devices
                        if d[1].serialnumber == device.serialnumber]:
                    devices.append((setup_url_for_device(device),
                                    device))

        for url, device in devices:
            _LOGGER.debug(
                "Adding WeMo device at %s:%i", device.host, device.port)

            discovery_info = {
                'model_name': device.model_name,
                'serial': device.serialnumber,
                'mac_address': device.mac,
                'ssdp_description': url,
            }

            discovery_dispatch(SERVICE_WEMO, discovery_info)

        _LOGGER.debug("WeMo device discovery has finished")

    hass.bus.async_listen_once(
        EVENT_HOMEASSISTANT_START, discover_wemo_devices)

    return True
