"""
homeassistant.components.scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Allows users to set and activate scenes within Home Assistant.

A scene is a set of states that describe how you want certain entities to be.
For example, light A should be red with 100 brightness. Light B should be on.

A scene is active if all states of the scene match the real states.

If a scene is manually activated it will store the previous state of the
entities. These will be restored when the state is deactivated manually.

If one of the enties that are being tracked change state on its own, the
old state will not be restored when it is being deactivated.
"""
import logging
from collections import namedtuple

from homeassistant.core import State
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.state import reproduce_state
from homeassistant.const import (
    ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF)

DOMAIN = 'scene'
DEPENDENCIES = ['group']

ATTR_ACTIVE_REQUESTED = "active_requested"

CONF_ENTITIES = "entities"

SceneConfig = namedtuple('SceneConfig', ['name', 'states'])


def setup(hass, config):
    """ Sets up scenes. """

    logger = logging.getLogger(__name__)

    scene_configs = config.get(DOMAIN)

    if not isinstance(scene_configs, list):
        logger.error('Scene config should be a list of scenes')
        return False

    component = EntityComponent(logger, DOMAIN, hass)

    component.add_entities(Scene(hass, _process_config(scene_config))
                           for scene_config in scene_configs)

    def handle_scene_service(service):
        """ Handles calls to the switch services. """
        target_scenes = component.extract_from_service(service)

        for scene in target_scenes:
            if service.service == SERVICE_TURN_ON:
                scene.turn_on()
            else:
                scene.turn_off()

    hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_scene_service)
    hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service)

    return True


def _process_config(scene_config):
    """ Process passed in config into a format to work with. """
    name = scene_config.get('name')
    states = {}
    c_entities = dict(scene_config.get(CONF_ENTITIES, {}))

    for entity_id in c_entities:
        if isinstance(c_entities[entity_id], dict):
            state = c_entities[entity_id].pop('state', None)
            attributes = c_entities[entity_id]
        else:
            state = c_entities[entity_id]
            attributes = {}

        # YAML translates 'on' to a boolean
        # http://yaml.org/type/bool.html
        if isinstance(state, bool):
            state = STATE_ON if state else STATE_OFF
        else:
            state = str(state)

        states[entity_id.lower()] = State(entity_id, state, attributes)

    return SceneConfig(name, states)


class Scene(ToggleEntity):
    """ A scene is a group of entities and the states we want them to be. """

    def __init__(self, hass, scene_config):
        self.hass = hass
        self.scene_config = scene_config

        self.is_active = False
        self.prev_states = None
        self.ignore_updates = False

        track_state_change(
            self.hass, self.entity_ids, self.entity_state_changed)

        self.update()

    @property
    def should_poll(self):
        return False

    @property
    def name(self):
        return self.scene_config.name

    @property
    def is_on(self):
        return self.is_active

    @property
    def entity_ids(self):
        """ Entity IDs part of this scene. """
        return self.scene_config.states.keys()

    @property
    def state_attributes(self):
        """ Scene state attributes. """
        return {
            ATTR_ENTITY_ID: list(self.entity_ids),
            ATTR_ACTIVE_REQUESTED: self.prev_states is not None,
        }

    def turn_on(self):
        """ Activates scene. Tries to get entities into requested state. """
        self.prev_states = tuple(self.hass.states.get(entity_id)
                                 for entity_id in self.entity_ids)

        self._reproduce_state(self.scene_config.states.values())

    def turn_off(self):
        """ Deactivates scene and restores old states. """
        if self.prev_states:
            self._reproduce_state(self.prev_states)
            self.prev_states = None

    def entity_state_changed(self, entity_id, old_state, new_state):
        """ Called when an entity part of this scene changes state. """
        if self.ignore_updates:
            return

        # If new state is not what we expect, it can never be active
        if self._state_as_requested(new_state):
            self.update()
        else:
            self.is_active = False
            self.prev_states = None

        self.update_ha_state()

    def update(self):
        """
        Update if the scene is active.

        Will look at each requested state and see if the current entity
        has the same state and has at least the same attributes with the
        same values. The real state can have more attributes.
        """
        self.is_active = all(
            self._state_as_requested(self.hass.states.get(entity_id))
            for entity_id in self.entity_ids)

    def _state_as_requested(self, cur_state):
        """ Returns if given state is as requested. """
        state = self.scene_config.states.get(cur_state and cur_state.entity_id)

        return (cur_state is not None and state.state == cur_state.state and
                all(value == cur_state.attributes.get(key)
                    for key, value in state.attributes.items()))

    def _reproduce_state(self, states):
        """ Wraps reproduce state with Scence specific logic. """
        self.ignore_updates = True
        reproduce_state(self.hass, states, True)
        self.ignore_updates = False

        self.update_ha_state(True)
