"""
This component provides light support for the Philips Hue system.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.hue/
"""
from datetime import timedelta
import logging
import random
import re
import socket

import voluptuous as vol

import homeassistant.components.hue as hue

import homeassistant.util as util
from homeassistant.util import yaml
import homeassistant.util.color as color_util
from homeassistant.components.light import (
    ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR,
    ATTR_TRANSITION, ATTR_XY_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM,
    FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
    SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION,
    SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.const import CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME
from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE_HIDDEN
import homeassistant.helpers.config_validation as cv

DEPENDENCIES = ['hue']

_LOGGER = logging.getLogger(__name__)

DATA_KEY = 'hue_lights'
DATA_LIGHTS = 'lights'
DATA_LIGHTGROUPS = 'lightgroups'

MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)

SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION)
SUPPORT_HUE_DIMMABLE = (SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS)
SUPPORT_HUE_COLOR_TEMP = (SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP)
SUPPORT_HUE_COLOR = (SUPPORT_HUE_DIMMABLE | SUPPORT_EFFECT |
                     SUPPORT_RGB_COLOR | SUPPORT_XY_COLOR)
SUPPORT_HUE_EXTENDED = (SUPPORT_HUE_COLOR_TEMP | SUPPORT_HUE_COLOR)

SUPPORT_HUE = {
    'Extended color light': SUPPORT_HUE_EXTENDED,
    'Color light': SUPPORT_HUE_COLOR,
    'Dimmable light': SUPPORT_HUE_DIMMABLE,
    'On/Off plug-in unit': SUPPORT_HUE_ON_OFF,
    'Color temperature light': SUPPORT_HUE_COLOR_TEMP
    }

ATTR_IS_HUE_GROUP = 'is_hue_group'

# Legacy configuration, will be removed in 0.60
CONF_ALLOW_UNREACHABLE = 'allow_unreachable'
DEFAULT_ALLOW_UNREACHABLE = False
CONF_ALLOW_IN_EMULATED_HUE = 'allow_in_emulated_hue'
DEFAULT_ALLOW_IN_EMULATED_HUE = True
CONF_ALLOW_HUE_GROUPS = 'allow_hue_groups'
DEFAULT_ALLOW_HUE_GROUPS = True

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_HOST): cv.string,
    vol.Optional(CONF_ALLOW_UNREACHABLE): cv.boolean,
    vol.Optional(CONF_FILENAME): cv.string,
    vol.Optional(CONF_ALLOW_IN_EMULATED_HUE): cv.boolean,
    vol.Optional(CONF_ALLOW_HUE_GROUPS,
                 default=DEFAULT_ALLOW_HUE_GROUPS): cv.boolean,
})

MIGRATION_ID = 'light_hue_config_migration'
MIGRATION_TITLE = 'Philips Hue Configuration Migration'
MIGRATION_INSTRUCTIONS = """
Configuration for the Philips Hue component has changed; action required.

You have configured at least one bridge:

    hue:
{config}

This configuration is deprecated, please check the
[Hue component](https://home-assistant.io/components/hue/) page for more
information.
"""


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Hue lights."""
    if discovery_info is None or 'bridge_id' not in discovery_info:
        return

    setup_data(hass)

    if config is not None and len(config) > 0:
        # Legacy configuration, will be removed in 0.60
        config_str = yaml.dump([config])
        # Indent so it renders in a fixed-width font
        config_str = re.sub('(?m)^', '      ', config_str)
        hass.components.persistent_notification.async_create(
            MIGRATION_INSTRUCTIONS.format(config=config_str),
            title=MIGRATION_TITLE,
            notification_id=MIGRATION_ID)

    bridge_id = discovery_info['bridge_id']
    bridge = hass.data[hue.DOMAIN][bridge_id]
    unthrottled_update_lights(hass, bridge, add_devices)


def setup_data(hass):
    """Initialize internal data. Useful from tests."""
    if DATA_KEY not in hass.data:
        hass.data[DATA_KEY] = {DATA_LIGHTS: {}, DATA_LIGHTGROUPS: {}}


@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights(hass, bridge, add_devices):
    """Update the Hue light objects with latest info from the bridge."""
    return unthrottled_update_lights(hass, bridge, add_devices)


def unthrottled_update_lights(hass, bridge, add_devices):
    """Internal version of update_lights."""
    import phue

    if not bridge.configured:
        return

    try:
        api = bridge.get_api()
    except phue.PhueRequestTimeout:
        _LOGGER.warning('Timeout trying to reach the bridge')
        return
    except ConnectionRefusedError:
        _LOGGER.error('The bridge refused the connection')
        return
    except socket.error:
        # socket.error when we cannot reach Hue
        _LOGGER.exception('Cannot reach the bridge')
        return

    bridge_type = get_bridge_type(api)

    new_lights = process_lights(
        hass, api, bridge, bridge_type,
        lambda **kw: update_lights(hass, bridge, add_devices, **kw))
    if bridge.allow_hue_groups:
        new_lightgroups = process_groups(
            hass, api, bridge, bridge_type,
            lambda **kw: update_lights(hass, bridge, add_devices, **kw))
        new_lights.extend(new_lightgroups)

    if new_lights:
        add_devices(new_lights)


def get_bridge_type(api):
    """Return the bridge type."""
    api_name = api.get('config').get('name')
    if api_name in ('RaspBee-GW', 'deCONZ-GW'):
        return 'deconz'
    else:
        return 'hue'


def process_lights(hass, api, bridge, bridge_type, update_lights_cb):
    """Set up HueLight objects for all lights."""
    api_lights = api.get('lights')

    if not isinstance(api_lights, dict):
        _LOGGER.error('Got unexpected result from Hue API')
        return []

    new_lights = []

    lights = hass.data[DATA_KEY][DATA_LIGHTS]
    for light_id, info in api_lights.items():
        if light_id not in lights:
            lights[light_id] = HueLight(
                int(light_id), info, bridge,
                update_lights_cb,
                bridge_type, bridge.allow_unreachable,
                bridge.allow_in_emulated_hue)
            new_lights.append(lights[light_id])
        else:
            lights[light_id].info = info
            lights[light_id].schedule_update_ha_state()

    return new_lights


def process_groups(hass, api, bridge, bridge_type, update_lights_cb):
    """Set up HueLight objects for all groups."""
    api_groups = api.get('groups')

    if not isinstance(api_groups, dict):
        _LOGGER.error('Got unexpected result from Hue API')
        return []

    new_lights = []

    groups = hass.data[DATA_KEY][DATA_LIGHTGROUPS]
    for lightgroup_id, info in api_groups.items():
        if 'state' not in info:
            _LOGGER.warning('Group info does not contain state. '
                            'Please update your hub.')
            return []

        if lightgroup_id not in groups:
            groups[lightgroup_id] = HueLight(
                int(lightgroup_id), info, bridge,
                update_lights_cb,
                bridge_type, bridge.allow_unreachable,
                bridge.allow_in_emulated_hue, True)
            new_lights.append(groups[lightgroup_id])
        else:
            groups[lightgroup_id].info = info
            groups[lightgroup_id].schedule_update_ha_state()

    return new_lights


class HueLight(Light):
    """Representation of a Hue light."""

    def __init__(self, light_id, info, bridge, update_lights_cb,
                 bridge_type, allow_unreachable, allow_in_emulated_hue,
                 is_group=False):
        """Initialize the light."""
        self.light_id = light_id
        self.info = info
        self.bridge = bridge
        self.update_lights = update_lights_cb
        self.bridge_type = bridge_type
        self.allow_unreachable = allow_unreachable
        self.is_group = is_group
        self.allow_in_emulated_hue = allow_in_emulated_hue

        if is_group:
            self._command_func = self.bridge.set_group
        else:
            self._command_func = self.bridge.set_light

    @property
    def unique_id(self):
        """Return the ID of this Hue light."""
        lid = self.info.get('uniqueid')

        if lid is None:
            default_type = 'Group' if self.is_group else 'Light'
            ltype = self.info.get('type', default_type)
            lid = '{}.{}.{}'.format(self.name, ltype, self.light_id)

        return '{}.{}'.format(self.__class__, lid)

    @property
    def name(self):
        """Return the name of the Hue light."""
        return self.info.get('name', DEVICE_DEFAULT_NAME)

    @property
    def brightness(self):
        """Return the brightness of this light between 0..255."""
        if self.is_group:
            return self.info['action'].get('bri')
        return self.info['state'].get('bri')

    @property
    def xy_color(self):
        """Return the XY color value."""
        if self.is_group:
            return self.info['action'].get('xy')
        return self.info['state'].get('xy')

    @property
    def color_temp(self):
        """Return the CT color value."""
        if self.is_group:
            return self.info['action'].get('ct')
        return self.info['state'].get('ct')

    @property
    def is_on(self):
        """Return true if device is on."""
        if self.is_group:
            return self.info['state']['any_on']
        elif self.allow_unreachable:
            return self.info['state']['on']
        return self.info['state']['reachable'] and \
            self.info['state']['on']

    @property
    def supported_features(self):
        """Flag supported features."""
        return SUPPORT_HUE.get(self.info.get('type'), SUPPORT_HUE_EXTENDED)

    @property
    def effect_list(self):
        """Return the list of supported effects."""
        return [EFFECT_COLORLOOP, EFFECT_RANDOM]

    def turn_on(self, **kwargs):
        """Turn the specified or all lights on."""
        command = {'on': True}

        if ATTR_TRANSITION in kwargs:
            command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)

        if ATTR_XY_COLOR in kwargs:
            if self.info.get('manufacturername') == 'OSRAM':
                color_hue, sat = color_util.color_xy_to_hs(
                    *kwargs[ATTR_XY_COLOR])
                command['hue'] = color_hue
                command['sat'] = sat
            else:
                command['xy'] = kwargs[ATTR_XY_COLOR]
        elif ATTR_RGB_COLOR in kwargs:
            if self.info.get('manufacturername') == 'OSRAM':
                hsv = color_util.color_RGB_to_hsv(
                    *(int(val) for val in kwargs[ATTR_RGB_COLOR]))
                command['hue'] = hsv[0]
                command['sat'] = hsv[1]
                command['bri'] = hsv[2]
            else:
                xyb = color_util.color_RGB_to_xy(
                    *(int(val) for val in kwargs[ATTR_RGB_COLOR]))
                command['xy'] = xyb[0], xyb[1]
                command['bri'] = xyb[2]
        elif ATTR_COLOR_TEMP in kwargs:
            temp = kwargs[ATTR_COLOR_TEMP]
            command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))

        if ATTR_BRIGHTNESS in kwargs:
            command['bri'] = kwargs[ATTR_BRIGHTNESS]

        flash = kwargs.get(ATTR_FLASH)

        if flash == FLASH_LONG:
            command['alert'] = 'lselect'
            del command['on']
        elif flash == FLASH_SHORT:
            command['alert'] = 'select'
            del command['on']
        elif self.bridge_type == 'hue':
            command['alert'] = 'none'

        effect = kwargs.get(ATTR_EFFECT)

        if effect == EFFECT_COLORLOOP:
            command['effect'] = 'colorloop'
        elif effect == EFFECT_RANDOM:
            command['hue'] = random.randrange(0, 65535)
            command['sat'] = random.randrange(150, 254)
        elif (self.bridge_type == 'hue' and
              self.info.get('manufacturername') == 'Philips'):
            command['effect'] = 'none'

        self._command_func(self.light_id, command)

    def turn_off(self, **kwargs):
        """Turn the specified or all lights off."""
        command = {'on': False}

        if ATTR_TRANSITION in kwargs:
            command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)

        flash = kwargs.get(ATTR_FLASH)

        if flash == FLASH_LONG:
            command['alert'] = 'lselect'
            del command['on']
        elif flash == FLASH_SHORT:
            command['alert'] = 'select'
            del command['on']
        elif self.bridge_type == 'hue':
            command['alert'] = 'none'

        self._command_func(self.light_id, command)

    def update(self):
        """Synchronize state with bridge."""
        self.update_lights(no_throttle=True)

    @property
    def device_state_attributes(self):
        """Return the device state attributes."""
        attributes = {}
        if not self.allow_in_emulated_hue:
            attributes[ATTR_EMULATED_HUE_HIDDEN] = \
                not self.allow_in_emulated_hue
        if self.is_group:
            attributes[ATTR_IS_HUE_GROUP] = self.is_group
        return attributes
