"""Support for Ecobee Thermostats."""
import logging

import voluptuous as vol

from homeassistant.components import ecobee
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
    DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE,
    ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
    SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
    SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
    SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
    SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.const import (
    ATTR_ENTITY_ID, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv

_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)

ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'

DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
AWAY_MODE = 'awayMode'

DEPENDENCIES = ['ecobee']

SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'

SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int),
})

RESUME_PROGRAM_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
})

SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
                 SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
                 SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
                 SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
                 SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Ecobee Thermostat Platform."""
    if discovery_info is None:
        return
    data = ecobee.NETWORK
    hold_temp = discovery_info['hold_temp']
    _LOGGER.info(
        "Loading ecobee thermostat component with hold_temp set to %s",
        hold_temp)
    devices = [Thermostat(data, index, hold_temp)
               for index in range(len(data.ecobee.thermostats))]
    add_entities(devices)

    def fan_min_on_time_set_service(service):
        """Set the minimum fan on time on the target thermostats."""
        entity_id = service.data.get(ATTR_ENTITY_ID)
        fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME]

        if entity_id:
            target_thermostats = [device for device in devices
                                  if device.entity_id in entity_id]
        else:
            target_thermostats = devices

        for thermostat in target_thermostats:
            thermostat.set_fan_min_on_time(str(fan_min_on_time))

            thermostat.schedule_update_ha_state(True)

    def resume_program_set_service(service):
        """Resume the program on the target thermostats."""
        entity_id = service.data.get(ATTR_ENTITY_ID)
        resume_all = service.data.get(ATTR_RESUME_ALL)

        if entity_id:
            target_thermostats = [device for device in devices
                                  if device.entity_id in entity_id]
        else:
            target_thermostats = devices

        for thermostat in target_thermostats:
            thermostat.resume_program(resume_all)

            thermostat.schedule_update_ha_state(True)

    hass.services.register(
        DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, fan_min_on_time_set_service,
        schema=SET_FAN_MIN_ON_TIME_SCHEMA)

    hass.services.register(
        DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
        schema=RESUME_PROGRAM_SCHEMA)


class Thermostat(ClimateDevice):
    """A thermostat class for Ecobee."""

    def __init__(self, data, thermostat_index, hold_temp):
        """Initialize the thermostat."""
        self.data = data
        self.thermostat_index = thermostat_index
        self.thermostat = self.data.ecobee.get_thermostat(
            self.thermostat_index)
        self._name = self.thermostat['name']
        self.hold_temp = hold_temp
        self.vacation = None
        self._climate_list = self.climate_list
        self._operation_list = ['auto', 'auxHeatOnly', 'cool',
                                'heat', 'off']
        self._fan_list = ['auto', 'on']
        self.update_without_throttle = False

    def update(self):
        """Get the latest state from the thermostat."""
        if self.update_without_throttle:
            self.data.update(no_throttle=True)
            self.update_without_throttle = False
        else:
            self.data.update()

        self.thermostat = self.data.ecobee.get_thermostat(
            self.thermostat_index)

    @property
    def supported_features(self):
        """Return the list of supported features."""
        return SUPPORT_FLAGS

    @property
    def name(self):
        """Return the name of the Ecobee Thermostat."""
        return self.thermostat['name']

    @property
    def temperature_unit(self):
        """Return the unit of measurement."""
        return TEMP_FAHRENHEIT

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self.thermostat['runtime']['actualTemperature'] / 10.0

    @property
    def target_temperature_low(self):
        """Return the lower bound temperature we try to reach."""
        if self.current_operation == STATE_AUTO:
            return self.thermostat['runtime']['desiredHeat'] / 10.0
        return None

    @property
    def target_temperature_high(self):
        """Return the upper bound temperature we try to reach."""
        if self.current_operation == STATE_AUTO:
            return self.thermostat['runtime']['desiredCool'] / 10.0
        return None

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        if self.current_operation == STATE_AUTO:
            return None
        if self.current_operation == STATE_HEAT:
            return self.thermostat['runtime']['desiredHeat'] / 10.0
        if self.current_operation == STATE_COOL:
            return self.thermostat['runtime']['desiredCool'] / 10.0
        return None

    @property
    def fan(self):
        """Return the current fan status."""
        if 'fan' in self.thermostat['equipmentStatus']:
            return STATE_ON
        return STATE_OFF

    @property
    def current_fan_mode(self):
        """Return the fan setting."""
        return self.thermostat['runtime']['desiredFanMode']

    @property
    def current_hold_mode(self):
        """Return current hold mode."""
        mode = self._current_hold_mode
        return None if mode == AWAY_MODE else mode

    @property
    def fan_list(self):
        """Return the available fan modes."""
        return self._fan_list

    @property
    def _current_hold_mode(self):
        events = self.thermostat['events']
        for event in events:
            if event['running']:
                if event['type'] == 'hold':
                    if event['holdClimateRef'] == 'away':
                        if int(event['endDate'][0:4]) - \
                                int(event['startDate'][0:4]) <= 1:
                            # A temporary hold from away climate is a hold
                            return 'away'
                        # A permanent hold from away climate
                        return AWAY_MODE
                    if event['holdClimateRef'] != "":
                        # Any other hold based on climate
                        return event['holdClimateRef']
                    # Any hold not based on a climate is a temp hold
                    return TEMPERATURE_HOLD
                if event['type'].startswith('auto'):
                    # All auto modes are treated as holds
                    return event['type'][4:].lower()
                if event['type'] == 'vacation':
                    self.vacation = event['name']
                    return VACATION_HOLD
        return None

    @property
    def current_operation(self):
        """Return current operation."""
        if self.operation_mode == 'auxHeatOnly' or \
                self.operation_mode == 'heatPump':
            return STATE_HEAT
        return self.operation_mode

    @property
    def operation_list(self):
        """Return the operation modes list."""
        return self._operation_list

    @property
    def operation_mode(self):
        """Return current operation ie. heat, cool, idle."""
        return self.thermostat['settings']['hvacMode']

    @property
    def mode(self):
        """Return current mode, as the user-visible name."""
        cur = self.thermostat['program']['currentClimateRef']
        climates = self.thermostat['program']['climates']
        current = list(filter(lambda x: x['climateRef'] == cur, climates))
        return current[0]['name']

    @property
    def fan_min_on_time(self):
        """Return current fan minimum on time."""
        return self.thermostat['settings']['fanMinOnTime']

    @property
    def device_state_attributes(self):
        """Return device specific state attributes."""
        # Move these to Thermostat Device and make them global
        status = self.thermostat['equipmentStatus']
        operation = None
        if status == '':
            operation = STATE_IDLE
        elif 'Cool' in status:
            operation = STATE_COOL
        elif 'auxHeat' in status:
            operation = STATE_HEAT
        elif 'heatPump' in status:
            operation = STATE_HEAT
        else:
            operation = status

        return {
            "actual_humidity": self.thermostat['runtime']['actualHumidity'],
            "fan": self.fan,
            "climate_mode": self.mode,
            "operation": operation,
            "climate_list": self.climate_list,
            "fan_min_on_time": self.fan_min_on_time
        }

    @property
    def is_away_mode_on(self):
        """Return true if away mode is on."""
        return self._current_hold_mode == AWAY_MODE

    @property
    def is_aux_heat_on(self):
        """Return true if aux heater."""
        return 'auxHeat' in self.thermostat['equipmentStatus']

    def turn_away_mode_on(self):
        """Turn away mode on by setting it on away hold indefinitely."""
        if self._current_hold_mode != AWAY_MODE:
            self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
                                              'indefinite')
            self.update_without_throttle = True

    def turn_away_mode_off(self):
        """Turn away off."""
        if self._current_hold_mode == AWAY_MODE:
            self.data.ecobee.resume_program(self.thermostat_index)
            self.update_without_throttle = True

    def set_hold_mode(self, hold_mode):
        """Set hold mode (away, home, temp, sleep, etc.)."""
        hold = self.current_hold_mode

        if hold == hold_mode:
            # no change, so no action required
            return
        if hold_mode == 'None' or hold_mode is None:
            if hold == VACATION_HOLD:
                self.data.ecobee.delete_vacation(
                    self.thermostat_index, self.vacation)
            else:
                self.data.ecobee.resume_program(self.thermostat_index)
        else:
            if hold_mode == TEMPERATURE_HOLD:
                self.set_temp_hold(self.current_temperature)
            else:
                self.data.ecobee.set_climate_hold(
                    self.thermostat_index, hold_mode, self.hold_preference())
        self.update_without_throttle = True

    def set_auto_temp_hold(self, heat_temp, cool_temp):
        """Set temperature hold in auto mode."""
        if cool_temp is not None:
            cool_temp_setpoint = cool_temp
        else:
            cool_temp_setpoint = (
                self.thermostat['runtime']['desiredCool'] / 10.0)

        if heat_temp is not None:
            heat_temp_setpoint = heat_temp
        else:
            heat_temp_setpoint = (
                self.thermostat['runtime']['desiredCool'] / 10.0)

        self.data.ecobee.set_hold_temp(self.thermostat_index,
                                       cool_temp_setpoint, heat_temp_setpoint,
                                       self.hold_preference())
        _LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, "
                      "cool=%s, is=%s", heat_temp,
                      isinstance(heat_temp, (int, float)), cool_temp,
                      isinstance(cool_temp, (int, float)))

        self.update_without_throttle = True

    def set_fan_mode(self, fan_mode):
        """Set the fan mode.  Valid values are "on" or "auto"."""
        if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
            error = "Invalid fan_mode value:  Valid values are 'on' or 'auto'"
            _LOGGER.error(error)
            return

        cool_temp = self.thermostat['runtime']['desiredCool'] / 10.0
        heat_temp = self.thermostat['runtime']['desiredHeat'] / 10.0
        self.data.ecobee.set_fan_mode(self.thermostat_index, fan_mode,
                                      cool_temp, heat_temp,
                                      self.hold_preference())

        _LOGGER.info("Setting fan mode to: %s", fan_mode)

    def set_temp_hold(self, temp):
        """Set temperature hold in modes other than auto.

        Ecobee API: It is good practice to set the heat and cool hold
        temperatures to be the same, if the thermostat is in either heat, cool,
        auxHeatOnly, or off mode. If the thermostat is in auto mode, an
        additional rule is required. The cool hold temperature must be greater
        than the heat hold temperature by at least the amount in the
        heatCoolMinDelta property.
        https://www.ecobee.com/home/developer/api/examples/ex5.shtml
        """
        if self.current_operation == STATE_HEAT or self.current_operation == \
                STATE_COOL:
            heat_temp = temp
            cool_temp = temp
        else:
            delta = self.thermostat['settings']['heatCoolMinDelta'] / 10
            heat_temp = temp - delta
            cool_temp = temp + delta
        self.set_auto_temp_hold(heat_temp, cool_temp)

    def set_temperature(self, **kwargs):
        """Set new target temperature."""
        low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
        high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
        temp = kwargs.get(ATTR_TEMPERATURE)

        if self.current_operation == STATE_AUTO and \
                (low_temp is not None or high_temp is not None):
            self.set_auto_temp_hold(low_temp, high_temp)
        elif temp is not None:
            self.set_temp_hold(temp)
        else:
            _LOGGER.error(
                "Missing valid arguments for set_temperature in %s", kwargs)

    def set_humidity(self, humidity):
        """Set the humidity level."""
        self.data.ecobee.set_humidity(self.thermostat_index, humidity)

    def set_operation_mode(self, operation_mode):
        """Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
        self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
        self.update_without_throttle = True

    def set_fan_min_on_time(self, fan_min_on_time):
        """Set the minimum fan on time."""
        self.data.ecobee.set_fan_min_on_time(
            self.thermostat_index, fan_min_on_time)
        self.update_without_throttle = True

    def resume_program(self, resume_all):
        """Resume the thermostat schedule program."""
        self.data.ecobee.resume_program(
            self.thermostat_index, 'true' if resume_all else 'false')
        self.update_without_throttle = True

    def hold_preference(self):
        """Return user preference setting for hold time."""
        # Values returned from thermostat are 'useEndTime4hour',
        # 'useEndTime2hour', 'nextTransition', 'indefinite', 'askMe'
        default = self.thermostat['settings']['holdAction']
        if default == 'nextTransition':
            return default
        # add further conditions if other hold durations should be
        # supported; note that this should not include 'indefinite'
        # as an indefinite away hold is interpreted as away_mode
        return 'nextTransition'

    @property
    def climate_list(self):
        """Return the list of climates currently available."""
        climates = self.thermostat['program']['climates']
        return list(map((lambda x: x['name']), climates))
