"""
homeassistant.components.sensor.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors sensors.

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

from homeassistant.helpers.entity import Entity

from homeassistant.const import (
    ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP,
    TEMP_CELCIUS, TEMP_FAHRENHEIT,
    STATE_ON, STATE_OFF)

CONF_PORT = "port"
CONF_DEBUG = "debug"
CONF_PERSISTENCE = "persistence"
CONF_PERSISTENCE_FILE = "persistence_file"
CONF_VERSION = "version"

ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"

_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
                'd4b809c2167650691058d1e29bfd2c4b1792b4b0.zip'
                '#pymysensors==0.3']


def setup_platform(hass, config, add_devices, discovery_info=None):
    """ Setup the mysensors platform. """

    import mysensors.mysensors as mysensors
    import mysensors.const_14 as const

    devices = {}    # keep track of devices added to HA
    # Just assume celcius means that the user wants metric for now.
    # It may make more sense to make this a global config option in the future.
    is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)

    def sensor_update(update_type, nid):
        """ Callback for sensor updates from the MySensors gateway. """
        _LOGGER.info("sensor_update %s: node %s", update_type, nid)
        sensor = gateway.sensors[nid]
        if sensor.sketch_name is None:
            return
        if nid not in devices:
            devices[nid] = {}

        node = devices[nid]
        new_devices = []
        for child_id, child in sensor.children.items():
            if child_id not in node:
                node[child_id] = {}
            for value_type, value in child.values.items():
                if value_type not in node[child_id]:
                    name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
                    node[child_id][value_type] = \
                        MySensorsNodeValue(
                            nid, child_id, name, value_type, is_metric, const)
                    new_devices.append(node[child_id][value_type])
                else:
                    node[child_id][value_type].update_sensor(
                        value, sensor.battery_level)

        if new_devices:
            _LOGGER.info("adding new devices: %s", new_devices)
            add_devices(new_devices)

    port = config.get(CONF_PORT)
    if port is None:
        _LOGGER.error("Missing required key 'port'")
        return False

    persistence = config.get(CONF_PERSISTENCE, True)
    persistence_file = config.get(CONF_PERSISTENCE_FILE,
                                  hass.config.path('mysensors.pickle'))
    version = config.get(CONF_VERSION, '1.4')

    gateway = mysensors.SerialGateway(port, sensor_update,
                                      persistence=persistence,
                                      persistence_file=persistence_file,
                                      protocol_version=version)
    gateway.metric = is_metric
    gateway.debug = config.get(CONF_DEBUG, False)
    gateway.start()

    if persistence:
        for nid in gateway.sensors:
            sensor_update('sensor_update', nid)

    hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
                         lambda event: gateway.stop())


class MySensorsNodeValue(Entity):
    """ Represents the value of a MySensors child node. """
    # pylint: disable=too-many-arguments, too-many-instance-attributes
    def __init__(self, node_id, child_id, name, value_type, metric, const):
        self._name = name
        self.node_id = node_id
        self.child_id = child_id
        self.battery_level = 0
        self.value_type = value_type
        self.metric = metric
        self._value = ''
        self.const = const

    @property
    def should_poll(self):
        """ MySensor gateway pushes its state to HA.  """
        return False

    @property
    def name(self):
        """ The name of this sensor. """
        return self._name

    @property
    def state(self):
        """ Returns the state of the device. """
        return self._value

    @property
    def unit_of_measurement(self):
        """ Unit of measurement of this entity. """
        if self.value_type == self.const.SetReq.V_TEMP:
            return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
        elif self.value_type == self.const.SetReq.V_HUM or \
                self.value_type == self.const.SetReq.V_DIMMER or \
                self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
            return '%'
        return None

    @property
    def state_attributes(self):
        """ Returns the state attributes. """
        return {
            ATTR_NODE_ID: self.node_id,
            ATTR_CHILD_ID: self.child_id,
            ATTR_BATTERY_LEVEL: self.battery_level,
        }

    def update_sensor(self, value, battery_level):
        """ Update a sensor with the latest value from the controller. """
        _LOGGER.info("%s value = %s", self._name, value)
        if self.value_type == self.const.SetReq.V_TRIPPED or \
           self.value_type == self.const.SetReq.V_ARMED:
            self._value = STATE_ON if int(value) == 1 else STATE_OFF
        else:
            self._value = value
        self.battery_level = battery_level
        self.update_ha_state()
