"""Support for Dyson Pure Cool link fan.

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

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (
    SUPPORT_OSCILLATE,
    SUPPORT_SET_SPEED,
    FanEntity,
    SPEED_LOW,
    SPEED_MEDIUM,
    SPEED_HIGH,
)
from homeassistant.const import ATTR_ENTITY_ID
from . import DYSON_DEVICES

_LOGGER = logging.getLogger(__name__)

ATTR_NIGHT_MODE = "night_mode"
ATTR_AUTO_MODE = "auto_mode"
ATTR_ANGLE_LOW = "angle_low"
ATTR_ANGLE_HIGH = "angle_high"
ATTR_FLOW_DIRECTION_FRONT = "flow_direction_front"
ATTR_TIMER = "timer"
ATTR_HEPA_FILTER = "hepa_filter"
ATTR_CARBON_FILTER = "carbon_filter"
ATTR_DYSON_SPEED = "dyson_speed"
ATTR_DYSON_SPEED_LIST = "dyson_speed_list"

DYSON_DOMAIN = "dyson"
DYSON_FAN_DEVICES = "dyson_fan_devices"

SERVICE_SET_NIGHT_MODE = "set_night_mode"
SERVICE_SET_AUTO_MODE = "set_auto_mode"
SERVICE_SET_ANGLE = "set_angle"
SERVICE_SET_FLOW_DIRECTION_FRONT = "set_flow_direction_front"
SERVICE_SET_TIMER = "set_timer"
SERVICE_SET_DYSON_SPEED = "set_speed"

DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_NIGHT_MODE): cv.boolean,
    }
)

SET_AUTO_MODE_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_AUTO_MODE): cv.boolean,
    }
)

SET_ANGLE_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_ANGLE_LOW): cv.positive_int,
        vol.Required(ATTR_ANGLE_HIGH): cv.positive_int,
    }
)

SET_FLOW_DIRECTION_FRONT_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_FLOW_DIRECTION_FRONT): cv.boolean,
    }
)

SET_TIMER_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_TIMER): cv.positive_int,
    }
)

SET_DYSON_SPEED_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
        vol.Required(ATTR_DYSON_SPEED): cv.positive_int,
    }
)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Dyson fan components."""
    from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
    from libpurecool.dyson_pure_cool import DysonPureCool

    if discovery_info is None:
        return

    _LOGGER.debug("Creating new Dyson fans")
    if DYSON_FAN_DEVICES not in hass.data:
        hass.data[DYSON_FAN_DEVICES] = []

    # Get Dyson Devices from parent component
    has_purecool_devices = False
    device_serials = [device.serial for device in hass.data[DYSON_FAN_DEVICES]]
    for device in hass.data[DYSON_DEVICES]:
        if device.serial not in device_serials:
            if isinstance(device, DysonPureCool):
                has_purecool_devices = True
                dyson_entity = DysonPureCoolDevice(device)
                hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
            elif isinstance(device, DysonPureCoolLink):
                dyson_entity = DysonPureCoolLinkDevice(hass, device)
                hass.data[DYSON_FAN_DEVICES].append(dyson_entity)

    add_entities(hass.data[DYSON_FAN_DEVICES])

    def service_handle(service):
        """Handle the Dyson services."""
        entity_id = service.data[ATTR_ENTITY_ID]
        fan_device = next(
            (fan for fan in hass.data[DYSON_FAN_DEVICES] if fan.entity_id == entity_id),
            None,
        )
        if fan_device is None:
            _LOGGER.warning("Unable to find Dyson fan device %s", str(entity_id))
            return

        if service.service == SERVICE_SET_NIGHT_MODE:
            fan_device.set_night_mode(service.data[ATTR_NIGHT_MODE])

        if service.service == SERVICE_SET_AUTO_MODE:
            fan_device.set_auto_mode(service.data[ATTR_AUTO_MODE])

        if service.service == SERVICE_SET_ANGLE:
            fan_device.set_angle(
                service.data[ATTR_ANGLE_LOW], service.data[ATTR_ANGLE_HIGH]
            )

        if service.service == SERVICE_SET_FLOW_DIRECTION_FRONT:
            fan_device.set_flow_direction_front(service.data[ATTR_FLOW_DIRECTION_FRONT])

        if service.service == SERVICE_SET_TIMER:
            fan_device.set_timer(service.data[ATTR_TIMER])

        if service.service == SERVICE_SET_DYSON_SPEED:
            fan_device.set_dyson_speed(service.data[ATTR_DYSON_SPEED])

    # Register dyson service(s)
    hass.services.register(
        DYSON_DOMAIN,
        SERVICE_SET_NIGHT_MODE,
        service_handle,
        schema=DYSON_SET_NIGHT_MODE_SCHEMA,
    )
    if has_purecool_devices:
        hass.services.register(
            DYSON_DOMAIN,
            SERVICE_SET_AUTO_MODE,
            service_handle,
            schema=SET_AUTO_MODE_SCHEMA,
        )

        hass.services.register(
            DYSON_DOMAIN, SERVICE_SET_ANGLE, service_handle, schema=SET_ANGLE_SCHEMA
        )

        hass.services.register(
            DYSON_DOMAIN,
            SERVICE_SET_FLOW_DIRECTION_FRONT,
            service_handle,
            schema=SET_FLOW_DIRECTION_FRONT_SCHEMA,
        )

        hass.services.register(
            DYSON_DOMAIN, SERVICE_SET_TIMER, service_handle, schema=SET_TIMER_SCHEMA
        )

        hass.services.register(
            DYSON_DOMAIN,
            SERVICE_SET_DYSON_SPEED,
            service_handle,
            schema=SET_DYSON_SPEED_SCHEMA,
        )


class DysonPureCoolLinkDevice(FanEntity):
    """Representation of a Dyson fan."""

    def __init__(self, hass, device):
        """Initialize the fan."""
        _LOGGER.debug("Creating device %s", device.name)
        self.hass = hass
        self._device = device

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        self.hass.async_add_job(self._device.add_message_listener, self.on_message)

    def on_message(self, message):
        """Call when new messages received from the fan."""
        from libpurecool.dyson_pure_state import DysonPureCoolState

        if isinstance(message, DysonPureCoolState):
            _LOGGER.debug("Message received for fan device %s: %s", self.name, message)
            self.schedule_update_ha_state()

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

    @property
    def name(self):
        """Return the display name of this fan."""
        return self._device.name

    def set_speed(self, speed: str) -> None:
        """Set the speed of the fan. Never called ??."""
        from libpurecool.const import FanSpeed, FanMode

        _LOGGER.debug("Set fan speed to: %s", speed)

        if speed == FanSpeed.FAN_SPEED_AUTO.value:
            self._device.set_configuration(fan_mode=FanMode.AUTO)
        else:
            fan_speed = FanSpeed("{0:04d}".format(int(speed)))
            self._device.set_configuration(fan_mode=FanMode.FAN, fan_speed=fan_speed)

    def turn_on(self, speed: str = None, **kwargs) -> None:
        """Turn on the fan."""
        from libpurecool.const import FanSpeed, FanMode

        _LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
        if speed:
            if speed == FanSpeed.FAN_SPEED_AUTO.value:
                self._device.set_configuration(fan_mode=FanMode.AUTO)
            else:
                fan_speed = FanSpeed("{0:04d}".format(int(speed)))
                self._device.set_configuration(
                    fan_mode=FanMode.FAN, fan_speed=fan_speed
                )
        else:
            # Speed not set, just turn on
            self._device.set_configuration(fan_mode=FanMode.FAN)

    def turn_off(self, **kwargs) -> None:
        """Turn off the fan."""
        from libpurecool.const import FanMode

        _LOGGER.debug("Turn off fan %s", self.name)
        self._device.set_configuration(fan_mode=FanMode.OFF)

    def oscillate(self, oscillating: bool) -> None:
        """Turn on/off oscillating."""
        from libpurecool.const import Oscillation

        _LOGGER.debug("Turn oscillation %s for device %s", oscillating, self.name)

        if oscillating:
            self._device.set_configuration(oscillation=Oscillation.OSCILLATION_ON)
        else:
            self._device.set_configuration(oscillation=Oscillation.OSCILLATION_OFF)

    @property
    def oscillating(self):
        """Return the oscillation state."""
        return self._device.state and self._device.state.oscillation == "ON"

    @property
    def is_on(self):
        """Return true if the entity is on."""
        if self._device.state:
            return self._device.state.fan_mode == "FAN"
        return False

    @property
    def speed(self) -> str:
        """Return the current speed."""
        from libpurecool.const import FanSpeed

        if self._device.state:
            if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
                return self._device.state.speed
            return int(self._device.state.speed)
        return None

    @property
    def current_direction(self):
        """Return direction of the fan [forward, reverse]."""
        return None

    @property
    def night_mode(self):
        """Return Night mode."""
        return self._device.state.night_mode == "ON"

    def set_night_mode(self, night_mode: bool) -> None:
        """Turn fan in night mode."""
        from libpurecool.const import NightMode

        _LOGGER.debug("Set %s night mode %s", self.name, night_mode)
        if night_mode:
            self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_ON)
        else:
            self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_OFF)

    @property
    def auto_mode(self):
        """Return auto mode."""
        return self._device.state.fan_mode == "AUTO"

    def set_auto_mode(self, auto_mode: bool) -> None:
        """Turn fan in auto mode."""
        from libpurecool.const import FanMode

        _LOGGER.debug("Set %s auto mode %s", self.name, auto_mode)
        if auto_mode:
            self._device.set_configuration(fan_mode=FanMode.AUTO)
        else:
            self._device.set_configuration(fan_mode=FanMode.FAN)

    @property
    def speed_list(self) -> list:
        """Get the list of available speeds."""
        from libpurecool.const import FanSpeed

        supported_speeds = [
            FanSpeed.FAN_SPEED_AUTO.value,
            int(FanSpeed.FAN_SPEED_1.value),
            int(FanSpeed.FAN_SPEED_2.value),
            int(FanSpeed.FAN_SPEED_3.value),
            int(FanSpeed.FAN_SPEED_4.value),
            int(FanSpeed.FAN_SPEED_5.value),
            int(FanSpeed.FAN_SPEED_6.value),
            int(FanSpeed.FAN_SPEED_7.value),
            int(FanSpeed.FAN_SPEED_8.value),
            int(FanSpeed.FAN_SPEED_9.value),
            int(FanSpeed.FAN_SPEED_10.value),
        ]

        return supported_speeds

    @property
    def supported_features(self) -> int:
        """Flag supported features."""
        return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED

    @property
    def device_state_attributes(self) -> dict:
        """Return optional state attributes."""
        return {ATTR_NIGHT_MODE: self.night_mode, ATTR_AUTO_MODE: self.auto_mode}


class DysonPureCoolDevice(FanEntity):
    """Representation of a Dyson Purecool (TP04/DP04) fan."""

    def __init__(self, device):
        """Initialize the fan."""
        self._device = device

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        self.hass.async_add_executor_job(
            self._device.add_message_listener, self.on_message
        )

    def on_message(self, message):
        """Call when new messages received from the fan."""
        from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State

        if isinstance(message, DysonPureCoolV2State):
            _LOGGER.debug("Message received for fan device %s: %s", self.name, message)
            self.schedule_update_ha_state()

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

    @property
    def name(self):
        """Return the display name of this fan."""
        return self._device.name

    def turn_on(self, speed: str = None, **kwargs) -> None:
        """Turn on the fan."""
        _LOGGER.debug("Turn on fan %s", self.name)

        if speed is not None:
            self.set_speed(speed)
        else:
            self._device.turn_on()

    def set_speed(self, speed: str) -> None:
        """Set the speed of the fan."""
        from libpurecool.const import FanSpeed

        if speed == SPEED_LOW:
            self._device.set_fan_speed(FanSpeed.FAN_SPEED_4)
        elif speed == SPEED_MEDIUM:
            self._device.set_fan_speed(FanSpeed.FAN_SPEED_7)
        elif speed == SPEED_HIGH:
            self._device.set_fan_speed(FanSpeed.FAN_SPEED_10)

    def turn_off(self, **kwargs):
        """Turn off the fan."""
        _LOGGER.debug("Turn off fan %s", self.name)
        self._device.turn_off()

    def set_dyson_speed(self, speed: str = None) -> None:
        """Set the exact speed of the purecool fan."""
        from libpurecool.const import FanSpeed

        _LOGGER.debug("Set exact speed for fan %s", self.name)

        fan_speed = FanSpeed("{0:04d}".format(int(speed)))
        self._device.set_fan_speed(fan_speed)

    def oscillate(self, oscillating: bool) -> None:
        """Turn on/off oscillating."""
        _LOGGER.debug("Turn oscillation %s for device %s", oscillating, self.name)

        if oscillating:
            self._device.enable_oscillation()
        else:
            self._device.disable_oscillation()

    def set_night_mode(self, night_mode: bool) -> None:
        """Turn on/off night mode."""
        _LOGGER.debug("Turn night mode %s for device %s", night_mode, self.name)

        if night_mode:
            self._device.enable_night_mode()
        else:
            self._device.disable_night_mode()

    def set_auto_mode(self, auto_mode: bool) -> None:
        """Turn auto mode on/off."""
        _LOGGER.debug("Turn auto mode %s for device %s", auto_mode, self.name)
        if auto_mode:
            self._device.enable_auto_mode()
        else:
            self._device.disable_auto_mode()

    def set_angle(self, angle_low: int, angle_high: int) -> None:
        """Set device angle."""
        _LOGGER.debug(
            "set low %s and high angle %s for device %s",
            angle_low,
            angle_high,
            self.name,
        )
        self._device.enable_oscillation(angle_low, angle_high)

    def set_flow_direction_front(self, flow_direction_front: bool) -> None:
        """Set frontal airflow direction."""
        _LOGGER.debug(
            "Set frontal flow direction to %s for device %s",
            flow_direction_front,
            self.name,
        )

        if flow_direction_front:
            self._device.enable_frontal_direction()
        else:
            self._device.disable_frontal_direction()

    def set_timer(self, timer) -> None:
        """Set timer."""
        _LOGGER.debug("Set timer to %s for device %s", timer, self.name)

        if timer == 0:
            self._device.disable_sleep_timer()
        else:
            self._device.enable_sleep_timer(timer)

    @property
    def oscillating(self):
        """Return the oscillation state."""
        return self._device.state and self._device.state.oscillation == "OION"

    @property
    def is_on(self):
        """Return true if the entity is on."""
        if self._device.state:
            return self._device.state.fan_power == "ON"

    @property
    def speed(self):
        """Return the current speed."""
        from libpurecool.const import FanSpeed

        speed_map = {
            FanSpeed.FAN_SPEED_1.value: SPEED_LOW,
            FanSpeed.FAN_SPEED_2.value: SPEED_LOW,
            FanSpeed.FAN_SPEED_3.value: SPEED_LOW,
            FanSpeed.FAN_SPEED_4.value: SPEED_LOW,
            FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM,
            FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM,
            FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM,
            FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM,
            FanSpeed.FAN_SPEED_8.value: SPEED_HIGH,
            FanSpeed.FAN_SPEED_9.value: SPEED_HIGH,
            FanSpeed.FAN_SPEED_10.value: SPEED_HIGH,
        }

        return speed_map[self._device.state.speed]

    @property
    def dyson_speed(self):
        """Return the current speed."""
        from libpurecool.const import FanSpeed

        if self._device.state:
            if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
                return self._device.state.speed
            return int(self._device.state.speed)

    @property
    def night_mode(self):
        """Return Night mode."""
        return self._device.state.night_mode == "ON"

    @property
    def auto_mode(self):
        """Return Auto mode."""
        return self._device.state.auto_mode == "ON"

    @property
    def angle_low(self):
        """Return angle high."""
        return int(self._device.state.oscillation_angle_low)

    @property
    def angle_high(self):
        """Return angle low."""
        return int(self._device.state.oscillation_angle_high)

    @property
    def flow_direction_front(self):
        """Return frontal flow direction."""
        return self._device.state.front_direction == "ON"

    @property
    def timer(self):
        """Return timer."""
        return self._device.state.sleep_timer

    @property
    def hepa_filter(self):
        """Return the HEPA filter state."""
        return int(self._device.state.hepa_filter_state)

    @property
    def carbon_filter(self):
        """Return the carbon filter state."""
        return int(self._device.state.carbon_filter_state)

    @property
    def speed_list(self) -> list:
        """Get the list of available speeds."""
        return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]

    @property
    def dyson_speed_list(self) -> list:
        """Get the list of available dyson speeds."""
        from libpurecool.const import FanSpeed

        return [
            int(FanSpeed.FAN_SPEED_1.value),
            int(FanSpeed.FAN_SPEED_2.value),
            int(FanSpeed.FAN_SPEED_3.value),
            int(FanSpeed.FAN_SPEED_4.value),
            int(FanSpeed.FAN_SPEED_5.value),
            int(FanSpeed.FAN_SPEED_6.value),
            int(FanSpeed.FAN_SPEED_7.value),
            int(FanSpeed.FAN_SPEED_8.value),
            int(FanSpeed.FAN_SPEED_9.value),
            int(FanSpeed.FAN_SPEED_10.value),
        ]

    @property
    def device_serial(self):
        """Return fan's serial number."""
        return self._device.serial

    @property
    def supported_features(self) -> int:
        """Flag supported features."""
        return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED

    @property
    def device_state_attributes(self) -> dict:
        """Return optional state attributes."""
        return {
            ATTR_NIGHT_MODE: self.night_mode,
            ATTR_AUTO_MODE: self.auto_mode,
            ATTR_ANGLE_LOW: self.angle_low,
            ATTR_ANGLE_HIGH: self.angle_high,
            ATTR_FLOW_DIRECTION_FRONT: self.flow_direction_front,
            ATTR_TIMER: self.timer,
            ATTR_HEPA_FILTER: self.hepa_filter,
            ATTR_CARBON_FILTER: self.carbon_filter,
            ATTR_DYSON_SPEED: self.dyson_speed,
            ATTR_DYSON_SPEED_LIST: self.dyson_speed_list,
        }