from pyHS100 import SmartDevice, SmartDeviceException
import re
from typing import Any, Dict, Optional, Tuple

TPLINK_KELVIN = {'LB130': (2500, 9000),
                 'LB120': (2700, 6500),
                 'LB230': (2500, 9000),
                 'KB130': (2500, 9000)}


class SmartBulb(SmartDevice):
    """Representation of a TP-Link Smart Bulb.

    Usage example when used as library:
    p = SmartBulb("192.168.1.105")
    # print the devices alias
    print(p.alias)
    # change state of bulb
    p.state = "ON"
    p.state = "OFF"
    # query and print current state of plug
    print(p.state)
    # check whether the bulb supports color changes
    if p.is_color:
    # set the color to an HSV tuple
    p.hsv = (180, 100, 100)
    # get the current HSV value
    print(p.hsv)
    # check whether the bulb supports setting color temperature
    if p.is_variable_color_temp:
    # set the color temperature in Kelvin
    p.color_temp = 3000
    # get the current color temperature
    print(p.color_temp)
    # check whether the bulb is dimmable
    if p.is_dimmable:
    # set the bulb to 50% brightness
    p.brightness = 50
    # check the current brightness
    print(p.brightness)

    Errors reported by the device are raised as SmartDeviceExceptions,
    and should be handled by the user of the library.

    """
    # bulb states
    BULB_STATE_ON = 'ON'
    BULB_STATE_OFF = 'OFF'

    def __init__(self,
                 host: str,
                 protocol: 'TPLinkSmartHomeProtocol' = None) -> None:
        SmartDevice.__init__(self, host, protocol)
        self.emeter_type = "smartlife.iot.common.emeter"

    @property
    def is_color(self) -> bool:
        """
        Whether the bulb supports color changes

        :return: True if the bulb supports color changes, False otherwise
        :rtype: bool
        """
        return bool(self.sys_info['is_color'])

    @property
    def is_dimmable(self) -> bool:
        """
        Whether the bulb supports brightness changes

        :return: True if the bulb supports brightness changes, False otherwise
        :rtype: bool
        """
        return bool(self.sys_info['is_dimmable'])

    @property
    def is_variable_color_temp(self) -> bool:
        """
        Whether the bulb supports color temperature changes

        :return: True if the bulb supports color temperature changes, False
        otherwise
        :rtype: bool
        """
        return bool(self.sys_info['is_variable_color_temp'])

    @property
    def valid_temperature_range(self) -> Tuple[int, int]:
        """
        Returns the white temperature range (in Kelvin)
        depending on the bulb model

        :return: White temperature range in Kelvin (minimun, maximum)
        :rtype: tuple
        """
        if not self.is_variable_color_temp:
            return (0, 0)
        for model, temp_range in TPLINK_KELVIN.items():
            if re.match(model, self.sys_info['model']):
                return temp_range
        return (0, 0)

    def get_light_state(self) -> Dict:
        return self._query_helper("smartlife.iot.smartbulb.lightingservice",
                                  "get_light_state")

    def set_light_state(self, state: Dict) -> Dict:
        return self._query_helper("smartlife.iot.smartbulb.lightingservice",
                                  "transition_light_state", state)

    @property
    def hsv(self) -> Optional[Tuple[int, int, int]]:
        """
        Returns the current HSV state of the bulb, if supported

        :return: hue, saturation and value (degrees, %, %)
        :rtype: tuple
        """

        if not self.is_color:
            return None

        light_state = self.get_light_state()
        if not self.is_on:
            hue = light_state['dft_on_state']['hue']
            saturation = light_state['dft_on_state']['saturation']
            value = light_state['dft_on_state']['brightness']
        else:
            hue = light_state['hue']
            saturation = light_state['saturation']
            value = light_state['brightness']

        return hue, saturation, value

    @hsv.setter
    def hsv(self, state: Tuple[int, int, int]):
        """
        Sets new HSV, if supported

        :param tuple state: hue, saturation and value (degrees, %, %)
        """
        if not self.is_color:
            return None

        if not isinstance(state[0], int) or not (0 <= state[0] <= 360):
            raise SmartDeviceException(
                    'Invalid hue value: {} '
                    '(valid range: 0-360)'.format(state[0]))

        if not isinstance(state[1], int) or not (0 <= state[1] <= 100):
            raise SmartDeviceException(
                    'Invalid saturation value: {} '
                    '(valid range: 0-100%)'.format(state[1]))

        if not isinstance(state[2], int) or not (0 <= state[2] <= 100):
            raise SmartDeviceException(
                    'Invalid brightness value: {} '
                    '(valid range: 0-100%)'.format(state[2]))

        light_state = {
            "hue": state[0],
            "saturation": state[1],
            "brightness": state[2],
            "color_temp": 0
            }
        self.set_light_state(light_state)

    @property
    def color_temp(self) -> Optional[int]:
        """
        Color temperature of the device, if supported

        :return: Color temperature in Kelvin
        :rtype: int
        """
        if not self.is_variable_color_temp:
            return None

        light_state = self.get_light_state()
        if not self.is_on:
            return int(light_state['dft_on_state']['color_temp'])
        else:
            return int(light_state['color_temp'])

    @color_temp.setter
    def color_temp(self, temp: int) -> None:
        """
        Set the color temperature of the device, if supported

        :param int temp: The new color temperature, in Kelvin
        """
        if not self.is_variable_color_temp:
            return None

        if temp < self.valid_temperature_range[0] or \
                temp > self.valid_temperature_range[1]:
            raise ValueError("Temperature should be between {} "
                             "and {}".format(*self.valid_temperature_range))

        light_state = {
            "color_temp": temp,
        }
        self.set_light_state(light_state)

    @property
    def brightness(self) -> Optional[int]:
        """
        Current brightness of the device, if supported

        :return: brightness in percent
        :rtype: int
        """
        if not self.is_dimmable:
            return None

        light_state = self.get_light_state()
        if not self.is_on:
            return int(light_state['dft_on_state']['brightness'])
        else:
            return int(light_state['brightness'])

    @brightness.setter
    def brightness(self, brightness: int) -> None:
        """
        Set the current brightness of the device, if supported

        :param int brightness: brightness in percent
        """
        if not self.is_dimmable:
            return None

        light_state = {
            "brightness": brightness,
        }
        self.set_light_state(light_state)

    @property
    def state(self) -> str:
        """
        Retrieve the bulb state

        :returns: one of
                  BULB_STATE_ON
                  BULB_STATE_OFF
        :rtype: str
        """
        light_state = self.get_light_state()
        if light_state['on_off']:
            return self.BULB_STATE_ON
        return self.BULB_STATE_OFF

    @state.setter
    def state(self, bulb_state: str) -> None:
        """
        Set the new bulb state

        :param bulb_state: one of
                           BULB_STATE_ON
                           BULB_STATE_OFF
        """
        if bulb_state == self.BULB_STATE_ON:
            new_state = 1
        elif bulb_state == self.BULB_STATE_OFF:
            new_state = 0
        else:
            raise ValueError

        light_state = {
            "on_off": new_state,
        }
        self.set_light_state(light_state)

    @property
    def state_information(self) -> Dict[str, Any]:
        """
        Return bulb-specific state information.
        :return: Bulb information dict, keys in user-presentable form.
        :rtype: dict
        """
        info = {
            'Brightness': self.brightness,
            'Is dimmable': self.is_dimmable,
        }  # type: Dict[str, Any]
        if self.is_variable_color_temp:
            info["Color temperature"] = self.color_temp
        if self.is_color:
            info["HSV"] = self.hsv

        return info

    @property
    def is_on(self) -> bool:
        return bool(self.state == self.BULB_STATE_ON)

    def turn_off(self) -> None:
        """
        Turn the bulb off.
        """
        self.state = self.BULB_STATE_OFF

    def turn_on(self) -> None:
        """
        Turn the bulb on.
        """
        self.state = self.BULB_STATE_ON

    @property
    def has_emeter(self) -> bool:
        return True
