"""
Component to monitor plants.

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

import voluptuous as vol

from homeassistant.const import (
    STATE_OK, STATE_PROBLEM, STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE,
    CONF_SENSORS, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON)
from homeassistant.components import group
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = 'plant'

READING_BATTERY = 'battery'
READING_TEMPERATURE = ATTR_TEMPERATURE
READING_MOISTURE = 'moisture'
READING_CONDUCTIVITY = 'conductivity'
READING_BRIGHTNESS = 'brightness'

ATTR_PROBLEM = 'problem'
PROBLEM_NONE = 'none'

CONF_MIN_BATTERY_LEVEL = 'min_' + READING_BATTERY
CONF_MIN_TEMPERATURE = 'min_' + READING_TEMPERATURE
CONF_MAX_TEMPERATURE = 'max_' + READING_TEMPERATURE
CONF_MIN_MOISTURE = 'min_' + READING_MOISTURE
CONF_MAX_MOISTURE = 'max_' + READING_MOISTURE
CONF_MIN_CONDUCTIVITY = 'min_' + READING_CONDUCTIVITY
CONF_MAX_CONDUCTIVITY = 'max_' + READING_CONDUCTIVITY
CONF_MIN_BRIGHTNESS = 'min_' + READING_BRIGHTNESS
CONF_MAX_BRIGHTNESS = 'max_' + READING_BRIGHTNESS

CONF_SENSOR_BATTERY_LEVEL = READING_BATTERY
CONF_SENSOR_MOISTURE = READING_MOISTURE
CONF_SENSOR_CONDUCTIVITY = READING_CONDUCTIVITY
CONF_SENSOR_TEMPERATURE = READING_TEMPERATURE
CONF_SENSOR_BRIGHTNESS = READING_BRIGHTNESS

SCHEMA_SENSORS = vol.Schema({
    vol.Optional(CONF_SENSOR_BATTERY_LEVEL): cv.entity_id,
    vol.Optional(CONF_SENSOR_MOISTURE): cv.entity_id,
    vol.Optional(CONF_SENSOR_CONDUCTIVITY): cv.entity_id,
    vol.Optional(CONF_SENSOR_TEMPERATURE): cv.entity_id,
    vol.Optional(CONF_SENSOR_BRIGHTNESS): cv.entity_id,
})

PLANT_SCHEMA = vol.Schema({
    vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS),
    vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int,
    vol.Optional(CONF_MIN_TEMPERATURE): vol.Coerce(float),
    vol.Optional(CONF_MAX_TEMPERATURE): vol.Coerce(float),
    vol.Optional(CONF_MIN_MOISTURE): cv.positive_int,
    vol.Optional(CONF_MAX_MOISTURE): cv.positive_int,
    vol.Optional(CONF_MIN_CONDUCTIVITY): cv.positive_int,
    vol.Optional(CONF_MAX_CONDUCTIVITY): cv.positive_int,
    vol.Optional(CONF_MIN_BRIGHTNESS): cv.positive_int,
    vol.Optional(CONF_MAX_BRIGHTNESS): cv.positive_int,
})

DOMAIN = 'plant'
DEPENDENCIES = ['zone', 'group']

GROUP_NAME_ALL_PLANTS = 'all plants'
ENTITY_ID_ALL_PLANTS = group.ENTITY_ID_FORMAT.format('all_plants')

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: {
        cv.string: PLANT_SCHEMA
    },
}, extra=vol.ALLOW_EXTRA)


@asyncio.coroutine
def async_setup(hass, config):
    """Set up the Plant component."""
    component = EntityComponent(_LOGGER, DOMAIN, hass,
                                group_name=GROUP_NAME_ALL_PLANTS)

    entities = []
    for plant_name, plant_config in config[DOMAIN].items():
        _LOGGER.info("Added plant %s", plant_name)
        entity = Plant(plant_name, plant_config)
        sensor_entity_ids = list(plant_config[CONF_SENSORS].values())
        _LOGGER.debug("Subscribing to entity_ids %s", sensor_entity_ids)
        async_track_state_change(hass, sensor_entity_ids, entity.state_changed)
        entities.append(entity)

    yield from component.async_add_entities(entities)

    return True


class Plant(Entity):
    """Plant monitors the well-being of a plant.

    It also checks the measurements against
    configurable min and max values.
    """

    READINGS = {
        READING_BATTERY: {
            ATTR_UNIT_OF_MEASUREMENT:  '%',
            'min': CONF_MIN_BATTERY_LEVEL,
            'icon': 'mdi:battery-outline'
        },
        READING_TEMPERATURE: {
            ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
            'min': CONF_MIN_TEMPERATURE,
            'max': CONF_MAX_TEMPERATURE,
            'icon': 'mdi:thermometer'
        },
        READING_MOISTURE: {
            ATTR_UNIT_OF_MEASUREMENT: '%',
            'min': CONF_MIN_MOISTURE,
            'max': CONF_MAX_MOISTURE,
            'icon': 'mdi:water'
        },
        READING_CONDUCTIVITY: {
            ATTR_UNIT_OF_MEASUREMENT: 'µS/cm',
            'min': CONF_MIN_CONDUCTIVITY,
            'max': CONF_MAX_CONDUCTIVITY,
            'icon': 'mdi:emoticon-poop'
        },
        READING_BRIGHTNESS: {
            ATTR_UNIT_OF_MEASUREMENT: 'lux',
            'min': CONF_MIN_BRIGHTNESS,
            'max': CONF_MAX_BRIGHTNESS,
            'icon': 'mdi:white-balance-sunny'
        }
    }

    def __init__(self, name, config):
        """Initialize the Plant component."""
        self._config = config
        self._sensormap = dict()
        for reading, entity_id in config['sensors'].items():
            self._sensormap[entity_id] = reading
        self._state = STATE_UNKNOWN
        self._name = name
        self._battery = None
        self._moisture = None
        self._conductivity = None
        self._temperature = None
        self._brightness = None
        self._icon = 'mdi:help-circle'
        self._problems = PROBLEM_NONE

    @callback
    def state_changed(self, entity_id, _, new_state):
        """Update the sensor status.

        This callback is triggered, when the sensor state changes.
        """
        value = new_state.state
        _LOGGER.debug("Received callback from %s with value %s",
                      entity_id, value)
        if value == STATE_UNKNOWN:
            return

        reading = self._sensormap[entity_id]
        if reading == READING_MOISTURE:
            self._moisture = int(value)
        elif reading == READING_BATTERY:
            self._battery = int(value)
        elif reading == READING_TEMPERATURE:
            self._temperature = float(value)
        elif reading == READING_CONDUCTIVITY:
            self._conductivity = int(value)
        elif reading == READING_BRIGHTNESS:
            self._brightness = int(value)
        else:
            raise _LOGGER.error("Unknown reading from sensor %s: %s",
                                entity_id, value)
        self._update_state()

    def _update_state(self):
        """Update the state of the class based sensor data."""
        result = []
        for sensor_name in self._sensormap.values():
            params = self.READINGS[sensor_name]
            value = getattr(self, '_{}'.format(sensor_name))
            if value is not None:
                if 'min' in params and params['min'] in self._config:
                    min_value = self._config[params['min']]
                    if value < min_value:
                        result.append('{} low'.format(sensor_name))
                        self._icon = params['icon']

                if 'max' in params and params['max'] in self._config:
                    max_value = self._config[params['max']]
                    if value > max_value:
                        result.append('{} high'.format(sensor_name))
                        self._icon = params['icon']

        if result:
            self._state = STATE_PROBLEM
            self._problems = ','.join(result)
        else:
            self._state = STATE_OK
            self._icon = 'mdi:thumb-up'
            self._problems = PROBLEM_NONE
        _LOGGER.debug("New data processed")
        self.async_schedule_update_ha_state()

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

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

    @property
    def state(self):
        """Return the state of the entity."""
        return self._state

    @property
    def state_attributes(self):
        """Return the attributes of the entity.

        Provide the individual measurements from the
        sensor in the attributes of the device.
        """
        attrib = {
            ATTR_ICON: self._icon,
            ATTR_PROBLEM: self._problems,
        }

        for reading in self._sensormap.values():
            attrib[reading] = getattr(self, '_{}'.format(reading))

        return attrib
