"""Support for KNX/IP climate devices."""
from typing import List, Optional

from xknx.devices import Climate as XknxClimate
from xknx.dpt import HVACControllerMode, HVACOperationMode

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
    HVAC_MODE_HEAT,
    HVAC_MODE_OFF,
    PRESET_AWAY,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS

from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES
from .knx_entity import KnxEntity

CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()}
PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()}


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up climate(s) for KNX platform."""
    entities = []
    for device in hass.data[DOMAIN].xknx.devices:
        if isinstance(device, XknxClimate):
            entities.append(KNXClimate(device))
    async_add_entities(entities)


class KNXClimate(KnxEntity, ClimateEntity):
    """Representation of a KNX climate device."""

    def __init__(self, device: XknxClimate):
        """Initialize of a KNX climate device."""
        super().__init__(device)

        self._unit_of_measurement = TEMP_CELSIUS

    @property
    def supported_features(self) -> int:
        """Return the list of supported features."""
        return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

    async def async_update(self):
        """Request a state update from KNX bus."""
        await self._device.sync()
        await self._device.mode.sync()

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

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self._device.temperature.value

    @property
    def target_temperature_step(self):
        """Return the supported step of target temperature."""
        return self._device.temperature_step

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self._device.target_temperature.value

    @property
    def min_temp(self):
        """Return the minimum temperature."""
        return self._device.target_temperature_min

    @property
    def max_temp(self):
        """Return the maximum temperature."""
        return self._device.target_temperature_max

    async def async_set_temperature(self, **kwargs) -> None:
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if temperature is None:
            return
        await self._device.set_target_temperature(temperature)
        self.async_write_ha_state()

    @property
    def hvac_mode(self) -> Optional[str]:
        """Return current operation ie. heat, cool, idle."""
        if self._device.supports_on_off and not self._device.is_on:
            return HVAC_MODE_OFF
        if self._device.mode.supports_controller_mode:
            return CONTROLLER_MODES.get(
                self._device.mode.controller_mode.value, HVAC_MODE_HEAT
            )
        # default to "heat"
        return HVAC_MODE_HEAT

    @property
    def hvac_modes(self) -> Optional[List[str]]:
        """Return the list of available operation/controller modes."""
        _controller_modes = [
            CONTROLLER_MODES.get(controller_mode.value)
            for controller_mode in self._device.mode.controller_modes
        ]

        if self._device.supports_on_off:
            if not _controller_modes:
                _controller_modes.append(HVAC_MODE_HEAT)
            _controller_modes.append(HVAC_MODE_OFF)

        _modes = list(set(filter(None, _controller_modes)))
        # default to ["heat"]
        return _modes if _modes else [HVAC_MODE_HEAT]

    async def async_set_hvac_mode(self, hvac_mode: str) -> None:
        """Set operation mode."""
        if self._device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
            await self._device.turn_off()
        else:
            if self._device.supports_on_off and not self._device.is_on:
                await self._device.turn_on()
            if self._device.mode.supports_controller_mode:
                knx_controller_mode = HVACControllerMode(
                    CONTROLLER_MODES_INV.get(hvac_mode)
                )
                await self._device.mode.set_controller_mode(knx_controller_mode)
        self.async_write_ha_state()

    @property
    def preset_mode(self) -> Optional[str]:
        """Return the current preset mode, e.g., home, away, temp.

        Requires SUPPORT_PRESET_MODE.
        """
        if self._device.mode.supports_operation_mode:
            return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY)
        return None

    @property
    def preset_modes(self) -> Optional[List[str]]:
        """Return a list of available preset modes.

        Requires SUPPORT_PRESET_MODE.
        """
        _presets = [
            PRESET_MODES.get(operation_mode.value)
            for operation_mode in self._device.mode.operation_modes
        ]

        return list(filter(None, _presets))

    async def async_set_preset_mode(self, preset_mode: str) -> None:
        """Set new preset mode."""
        if self._device.mode.supports_operation_mode:
            knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode))
            await self._device.mode.set_operation_mode(knx_operation_mode)
            self.async_write_ha_state()
