"""
Support for Envisalink devices.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/
"""
import logging
import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform

REQUIREMENTS = ['pyenvisalink==1.9', 'pydispatcher==2.0.5']

_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'

EVL_CONTROLLER = None

CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port'
CONF_PANEL_TYPE = 'panel_type'
CONF_EVL_VERSION = 'evl_version'
CONF_CODE = 'code'
CONF_USERNAME = 'user_name'
CONF_PASS = 'password'
CONF_EVL_KEEPALIVE = 'keepalive_interval'
CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
CONF_ZONES = 'zones'
CONF_PARTITIONS = 'partitions'

CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name'
CONF_PANIC = 'panic_type'

DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police'

SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated'

ZONE_SCHEMA = vol.Schema({
    vol.Required(CONF_ZONENAME): cv.string,
    vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})

PARTITION_SCHEMA = vol.Schema({
    vol.Required(CONF_PARTITIONNAME): cv.string})

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_EVL_HOST): cv.string,
        vol.Required(CONF_PANEL_TYPE):
            vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
        vol.Required(CONF_USERNAME): cv.string,
        vol.Required(CONF_PASS): cv.string,
        vol.Required(CONF_CODE): cv.string,
        vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
        vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
        vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
        vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
            vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
        vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
            vol.All(vol.Coerce(int), vol.Range(min=15)),
        vol.Optional(CONF_ZONEDUMP_INTERVAL,
                     default=DEFAULT_ZONEDUMP_INTERVAL):
            vol.All(vol.Coerce(int), vol.Range(min=15)),
    }),
}, extra=vol.ALLOW_EXTRA)


# pylint: disable=unused-argument
def setup(hass, base_config):
    """Common setup for Envisalink devices."""
    from pyenvisalink import EnvisalinkAlarmPanel
    from pydispatch import dispatcher

    global EVL_CONTROLLER

    config = base_config.get(DOMAIN)

    _host = config.get(CONF_EVL_HOST)
    _port = config.get(CONF_EVL_PORT)
    _code = config.get(CONF_CODE)
    _panel_type = config.get(CONF_PANEL_TYPE)
    _panic_type = config.get(CONF_PANIC)
    _version = config.get(CONF_EVL_VERSION)
    _user = config.get(CONF_USERNAME)
    _pass = config.get(CONF_PASS)
    _keep_alive = config.get(CONF_EVL_KEEPALIVE)
    _zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
    _zones = config.get(CONF_ZONES)
    _partitions = config.get(CONF_PARTITIONS)
    _connect_status = {}
    EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
                                          _port,
                                          _panel_type,
                                          _version,
                                          _user,
                                          _pass,
                                          _zone_dump,
                                          _keep_alive,
                                          hass.loop)

    def login_fail_callback(data):
        """Callback for when the evl rejects our login."""
        _LOGGER.error("The envisalink rejected your credentials.")
        _connect_status['fail'] = 1

    def connection_fail_callback(data):
        """Network failure callback."""
        _LOGGER.error("Could not establish a connection with the envisalink.")
        _connect_status['fail'] = 1

    def connection_success_callback(data):
        """Callback for a successful connection."""
        _LOGGER.info("Established a connection with the envisalink.")
        _connect_status['success'] = 1

    def zones_updated_callback(data):
        """Handle zone timer updates."""
        _LOGGER.info("Envisalink sent a zone update event.  Updating zones...")
        dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
                        sender=None,
                        zone=data)

    def alarm_data_updated_callback(data):
        """Handle non-alarm based info updates."""
        _LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
        dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
                        sender=None,
                        partition=data)

    def partition_updated_callback(data):
        """Handle partition changes thrown by evl (including alarms)."""
        _LOGGER.info("The envisalink sent a partition update event.")
        dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
                        sender=None,
                        partition=data)

    def stop_envisalink(event):
        """Shutdown envisalink connection and thread on exit."""
        _LOGGER.info("Shutting down envisalink.")
        EVL_CONTROLLER.stop()

    def start_envisalink(event):
        """Startup process for the Envisalink."""
        hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
        for _ in range(10):
            if 'success' in _connect_status:
                hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
                return True
            elif 'fail' in _connect_status:
                return False
            else:
                time.sleep(1)

        _LOGGER.error("Timeout occurred while establishing evl connection.")
        return False

    EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
    EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
    EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
    EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
    EVL_CONTROLLER.callback_login_failure = login_fail_callback
    EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
    EVL_CONTROLLER.callback_login_success = connection_success_callback

    _result = start_envisalink(None)
    if not _result:
        return False

    # Load sub-components for Envisalink
    if _partitions:
        load_platform(hass, 'alarm_control_panel', 'envisalink',
                      {CONF_PARTITIONS: _partitions,
                       CONF_CODE: _code,
                       CONF_PANIC: _panic_type}, base_config)
        load_platform(hass, 'sensor', 'envisalink',
                      {CONF_PARTITIONS: _partitions,
                       CONF_CODE: _code}, base_config)
    if _zones:
        load_platform(hass, 'binary_sensor', 'envisalink',
                      {CONF_ZONES: _zones}, base_config)

    return True


class EnvisalinkDevice(Entity):
    """Representation of an Envisalink device."""

    def __init__(self, name, info, controller):
        """Initialize the device."""
        self._controller = controller
        self._info = info
        self._name = name

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

    @property
    def should_poll(self):
        """No polling needed."""
        return False
