"""Support for Z-Wave lights."""
import logging

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP,
    ATTR_HS_COLOR,
    ATTR_TRANSITION,
    ATTR_WHITE_VALUE,
    DOMAIN as LIGHT_DOMAIN,
    SUPPORT_BRIGHTNESS,
    SUPPORT_COLOR,
    SUPPORT_COLOR_TEMP,
    SUPPORT_TRANSITION,
    SUPPORT_WHITE_VALUE,
    LightEntity,
)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util

from .const import DATA_UNSUBSCRIBE, DOMAIN
from .entity import ZWaveDeviceEntity

_LOGGER = logging.getLogger(__name__)

ATTR_VALUE = "Value"
COLOR_CHANNEL_WARM_WHITE = 0x01
COLOR_CHANNEL_COLD_WHITE = 0x02
COLOR_CHANNEL_RED = 0x04
COLOR_CHANNEL_GREEN = 0x08
COLOR_CHANNEL_BLUE = 0x10


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up Z-Wave Light from Config Entry."""

    @callback
    def async_add_light(values):
        """Add Z-Wave Light."""
        light = ZwaveLight(values)

        async_add_entities([light])

    hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
        async_dispatcher_connect(hass, f"{DOMAIN}_new_{LIGHT_DOMAIN}", async_add_light)
    )


def byte_to_zwave_brightness(value):
    """Convert brightness in 0-255 scale to 0-99 scale.

    `value` -- (int) Brightness byte value from 0-255.
    """
    if value > 0:
        return max(1, round((value / 255) * 99))
    return 0


class ZwaveLight(ZWaveDeviceEntity, LightEntity):
    """Representation of a Z-Wave light."""

    def __init__(self, values):
        """Initialize the light."""
        super().__init__(values)
        self._color_channels = None
        self._hs = None
        self._white = None
        self._ct = None
        self._supported_features = SUPPORT_BRIGHTNESS
        self._min_mireds = 153  # 6500K as a safe default
        self._max_mireds = 370  # 2700K as a safe default

        # make sure that supported features is correctly set
        self.on_value_update()

    @callback
    def on_value_update(self):
        """Call when the underlying value(s) is added or updated."""
        if self.values.dimming_duration is not None:
            self._supported_features |= SUPPORT_TRANSITION

        if self.values.color is not None:
            self._supported_features |= SUPPORT_COLOR

        if self.values.color_channels is not None:
            # Support Color Temp if both white channels
            if (self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) and (
                self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE
            ):
                self._supported_features |= SUPPORT_COLOR_TEMP

            # Support White value if only a single white channel
            if ((self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) != 0) ^ (
                (self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE) != 0
            ):
                self._supported_features |= SUPPORT_WHITE_VALUE

        if self.values.color is not None:
            self._calculate_color_values()

    @property
    def brightness(self):
        """Return the brightness of this light between 0..255.

        Zwave multilevel switches use a range of [0, 99] to control brightness.
        """
        if "target" in self.values:
            return round((self.values.target.value / 99) * 255)
        return round((self.values.primary.value / 99) * 255)

    @property
    def is_on(self):
        """Return true if device is on (brightness above 0)."""
        if "target" in self.values:
            return self.values.target.value > 0
        return self.values.primary.value > 0

    @property
    def supported_features(self):
        """Flag supported features."""
        return self._supported_features

    @property
    def hs_color(self):
        """Return the hs color."""
        return self._hs

    @property
    def white_value(self):
        """Return the white value of this light between 0..255."""
        return self._white

    @property
    def color_temp(self):
        """Return the color temperature."""
        return self._ct

    @property
    def min_mireds(self):
        """Return the coldest color_temp that this light supports."""
        return self._min_mireds

    @property
    def max_mireds(self):
        """Return the warmest color_temp that this light supports."""
        return self._max_mireds

    @callback
    def async_set_duration(self, **kwargs):
        """Set the transition time for the brightness value.

        Zwave Dimming Duration values now use seconds as an
        integer (max: 7620 seconds or 127 mins)
        Build 1205 https://github.com/OpenZWave/open-zwave/commit/f81bc04
        """
        if self.values.dimming_duration is None:
            return

        ozw_version = tuple(
            int(x)
            for x in self.values.primary.ozw_instance.get_status().openzwave_version.split(
                "."
            )
        )

        if ATTR_TRANSITION not in kwargs:
            # no transition specified by user, use defaults
            new_value = 7621  # anything over 7620 uses the factory default
            if ozw_version < (1, 6, 1205):
                new_value = 255  # default for older version

        else:
            # transition specified by user
            new_value = int(max(0, min(7620, kwargs[ATTR_TRANSITION])))
            if ozw_version < (1, 6, 1205):
                transition = kwargs[ATTR_TRANSITION]
                if transition <= 127:
                    new_value = int(transition)
                else:
                    minutes = int(transition / 60)
                    _LOGGER.debug(
                        "Transition rounded to %d minutes for %s",
                        minutes,
                        self.entity_id,
                    )
                    new_value = minutes + 128

        # only send value if it differs from current
        # this prevents a command for nothing
        if self.values.dimming_duration.value != new_value:
            self.values.dimming_duration.send_value(new_value)

    async def async_turn_on(self, **kwargs):
        """Turn the device on."""
        self.async_set_duration(**kwargs)

        rgbw = None
        white = kwargs.get(ATTR_WHITE_VALUE)
        hs_color = kwargs.get(ATTR_HS_COLOR)
        color_temp = kwargs.get(ATTR_COLOR_TEMP)

        if hs_color is not None:
            rgbw = "#"
            for colorval in color_util.color_hs_to_RGB(*hs_color):
                rgbw += f"{colorval:02x}"
            rgbw += "0000"
            # white LED must be off in order for color to work

        elif white is not None:
            if self._color_channels & COLOR_CHANNEL_WARM_WHITE:
                rgbw = f"#000000{white:02x}00"
            else:
                rgbw = f"#00000000{white:02x}"

        elif color_temp is not None:
            # Limit color temp to min/max values
            cold = max(
                0,
                min(
                    255,
                    round(
                        (self._max_mireds - color_temp)
                        / (self._max_mireds - self._min_mireds)
                        * 255
                    ),
                ),
            )
            warm = 255 - cold
            rgbw = f"#000000{warm:02x}{cold:02x}"

        if rgbw and self.values.color:
            self.values.color.send_value(rgbw)

        # Zwave multilevel switches use a range of [0, 99] to control
        # brightness. Level 255 means to set it to previous value.
        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            brightness = byte_to_zwave_brightness(brightness)
        else:
            brightness = 255

        self.values.primary.send_value(brightness)

    async def async_turn_off(self, **kwargs):
        """Turn the device off."""
        self.async_set_duration(**kwargs)

        self.values.primary.send_value(0)

    def _calculate_color_values(self):
        """Parse color rgb and color temperature data."""
        # Color Data String
        data = self.values.color.data[ATTR_VALUE]

        # RGB is always present in the OpenZWave color data string.
        rgb = [int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16)]
        self._hs = color_util.color_RGB_to_hs(*rgb)

        if self.values.color_channels is None:
            return

        # Color Channels
        self._color_channels = self.values.color_channels.data[ATTR_VALUE]

        # Parse remaining color channels. OpenZWave appends white channels
        # that are present.
        index = 7
        temp_warm = 0
        temp_cold = 0

        # Update color temp limits.
        if self.values.min_kelvin:
            self._max_mireds = color_util.color_temperature_kelvin_to_mired(
                self.values.min_kelvin.data[ATTR_VALUE]
            )
        if self.values.max_kelvin:
            self._min_mireds = color_util.color_temperature_kelvin_to_mired(
                self.values.max_kelvin.data[ATTR_VALUE]
            )

        # Warm white
        if self._color_channels & COLOR_CHANNEL_WARM_WHITE:
            self._white = int(data[index : index + 2], 16)
            temp_warm = self._white

        index += 2

        # Cold white
        if self._color_channels & COLOR_CHANNEL_COLD_WHITE:
            self._white = int(data[index : index + 2], 16)
            temp_cold = self._white

        # Calculate color temps based on white LED status
        if temp_cold or temp_warm:
            self._ct = round(
                self._max_mireds
                - ((temp_cold / 255) * (self._max_mireds - self._min_mireds))
            )

        if not (
            self._color_channels & COLOR_CHANNEL_RED
            or self._color_channels & COLOR_CHANNEL_GREEN
            or self._color_channels & COLOR_CHANNEL_BLUE
        ):
            self._hs = None
