"""
homeassistant.components.isy994
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connects to an ISY-994 controller and loads relevant components to control its
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.

For configuration details please visit the documentation for this component at
https://home-assistant.io/components/isy994/
"""
import logging
from urllib.parse import urlparse

from homeassistant import bootstrap
from homeassistant.const import (
    ATTR_DISCOVERED, ATTR_SERVICE, CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
    EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers import validate_config
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component

DOMAIN = "isy994"
REQUIREMENTS = ['PyISY==1.0.5']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"
DISCOVER_SENSORS = "isy994.sensors"
ISY = None
SENSOR_STRING = 'Sensor'
HIDDEN_STRING = '{HIDE ME}'
CONF_TLS_VER = 'tls'

_LOGGER = logging.getLogger(__name__)


def setup(hass, config):
    """
    Setup ISY994 component.
    This will automatically import associated lights, switches, and sensors.
    """
    import PyISY

    # pylint: disable=global-statement
    # check for required values in configuration file
    if not validate_config(config,
                           {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
                           _LOGGER):
        return False

    # pull and parse standard configuration
    user = config[DOMAIN][CONF_USERNAME]
    password = config[DOMAIN][CONF_PASSWORD]
    host = urlparse(config[DOMAIN][CONF_HOST])
    addr = host.geturl()
    if host.scheme == 'http':
        addr = addr.replace('http://', '')
        https = False
    elif host.scheme == 'https':
        addr = addr.replace('https://', '')
        https = True
    else:
        _LOGGER.error('isy994 host value in configuration file is invalid.')
        return False
    port = host.port
    addr = addr.replace(':{}'.format(port), '')

    # pull and parse optional configuration
    global SENSOR_STRING
    global HIDDEN_STRING
    SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING))
    HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING))
    tls_version = config[DOMAIN].get(CONF_TLS_VER, None)

    # connect to ISY controller
    global ISY
    ISY = PyISY.ISY(addr, port, user, password, use_https=https,
                    tls_ver=tls_version, log=_LOGGER)
    if not ISY.connected:
        return False

    # listen for HA stop to disconnect
    hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)

    # Load components for the devices in the ISY controller that we support
    for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
                                  ('light', DISCOVER_LIGHTS),
                                  ('switch', DISCOVER_SWITCHES))):
        component = get_component(comp_name)
        bootstrap.setup_component(hass, component.DOMAIN, config)
        hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
                      {ATTR_SERVICE: discovery,
                       ATTR_DISCOVERED: {}})

    ISY.auto_update = True
    return True


def stop(event):
    """ Cleanup the ISY subscription. """
    ISY.auto_update = False


class ISYDeviceABC(ToggleEntity):
    """ Abstract Class for an ISY device. """

    _attrs = {}
    _onattrs = []
    _states = []
    _dtype = None
    _domain = None
    _name = None

    def __init__(self, node):
        # setup properties
        self.node = node

        # track changes
        self._change_handler = self.node.status. \
            subscribe('changed', self.on_update)

    def __del__(self):
        """ cleanup subscriptions because it is the right thing to do. """
        self._change_handler.unsubscribe()

    @property
    def domain(self):
        """ Returns the domain of the entity. """
        return self._domain

    @property
    def dtype(self):
        """ Returns the data type of the entity (binary or analog). """
        if self._dtype in ['analog', 'binary']:
            return self._dtype
        return 'binary' if self.unit_of_measurement is None else 'analog'

    @property
    def should_poll(self):
        """ Tells Home Assistant not to poll this entity. """
        return False

    @property
    def value(self):
        """ Returns the unclean value from the controller. """
        # pylint: disable=protected-access
        return self.node.status._val

    @property
    def state_attributes(self):
        """ Returns the state attributes for the node. """
        attr = {}
        for name, prop in self._attrs.items():
            attr[name] = getattr(self, prop)
            attr = self._attr_filter(attr)
        return attr

    def _attr_filter(self, attr):
        """ Placeholder for attribute filters. """
        # pylint: disable=no-self-use
        return attr

    @property
    def unique_id(self):
        """ Returns the id of this ISY sensor. """
        # pylint: disable=protected-access
        return self.node._id

    @property
    def raw_name(self):
        """ Returns the unclean node name. """
        return str(self._name) \
            if self._name is not None else str(self.node.name)

    @property
    def name(self):
        """ Returns the cleaned name of the node. """
        return self.raw_name.replace(HIDDEN_STRING, '').strip() \
            .replace('_', ' ')

    @property
    def hidden(self):
        """ Suggestion if the entity should be hidden from UIs. """
        return HIDDEN_STRING in self.raw_name

    def update(self):
        """ Update state of the sensor. """
        # ISY objects are automatically updated by the ISY's event stream
        pass

    def on_update(self, event):
        """ Handles the update received event. """
        self.update_ha_state()

    @property
    def is_on(self):
        """ Returns boolean response if the node is on. """
        return bool(self.value)

    @property
    def is_open(self):
        """ Returns boolean respons if the node is open. On = Open. """
        return self.is_on

    @property
    def state(self):
        """ Returns the state of the node. """
        if len(self._states) > 0:
            return self._states[0] if self.is_on else self._states[1]
        return self.value

    def turn_on(self, **kwargs):
        """ Turns the device on. """
        if self.domain is not 'sensor':
            attrs = [kwargs.get(name) for name in self._onattrs]
            self.node.on(*attrs)
        else:
            _LOGGER.error('ISY cannot turn on sensors.')

    def turn_off(self, **kwargs):
        """ Turns the device off. """
        if self.domain is not 'sensor':
            self.node.off()
        else:
            _LOGGER.error('ISY cannot turn off sensors.')

    @property
    def unit_of_measurement(self):
        """ Returns the defined units of measurement or None. """
        try:
            return self.node.units
        except AttributeError:
            return None
