"""
Connect two Home Assistant instances via MQTT.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt_eventstream/
"""
import asyncio
import json

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.components.mqtt import (
    valid_publish_topic, valid_subscribe_topic)
from homeassistant.const import (
    ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED,
    EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
from homeassistant.core import EventOrigin, State
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.json import JSONEncoder

DOMAIN = 'mqtt_eventstream'
DEPENDENCIES = ['mqtt']

CONF_PUBLISH_TOPIC = 'publish_topic'
CONF_SUBSCRIBE_TOPIC = 'subscribe_topic'
CONF_PUBLISH_EVENTSTREAM_RECEIVED = 'publish_eventstream_received'
CONF_IGNORE_EVENT = 'ignore_event'

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_PUBLISH_TOPIC): valid_publish_topic,
        vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic,
        vol.Optional(CONF_PUBLISH_EVENTSTREAM_RECEIVED, default=False):
            cv.boolean,
        vol.Optional(CONF_IGNORE_EVENT, default=[]): cv.ensure_list
    }),
}, extra=vol.ALLOW_EXTRA)


@asyncio.coroutine
def async_setup(hass, config):
    """Set up the MQTT eventstream component."""
    mqtt = hass.components.mqtt
    conf = config.get(DOMAIN, {})
    pub_topic = conf.get(CONF_PUBLISH_TOPIC)
    sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC)
    ignore_event = conf.get(CONF_IGNORE_EVENT)

    @callback
    def _event_publisher(event):
        """Handle events by publishing them on the MQTT queue."""
        if event.origin != EventOrigin.local:
            return
        if event.event_type == EVENT_TIME_CHANGED:
            return

        # User-defined events to ignore
        if event.event_type in ignore_event:
            return

        # Filter out the events that were triggered by publishing
        # to the MQTT topic, or you will end up in an infinite loop.
        if event.event_type == EVENT_CALL_SERVICE:
            if (
                    event.data.get('domain') == mqtt.DOMAIN and
                    event.data.get('service') == mqtt.SERVICE_PUBLISH and
                    event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic
            ):
                return

        # Filter out all the "event service executed" events because they
        # are only used internally by core as callbacks for blocking
        # during the interval while a service is being executed.
        # They will serve no purpose to the external system,
        # and thus are unnecessary traffic.
        # And at any rate it would cause an infinite loop to publish them
        # because publishing to an MQTT topic itself triggers one.
        if event.event_type == EVENT_SERVICE_EXECUTED:
            return

        event_info = {'event_type': event.event_type, 'event_data': event.data}
        msg = json.dumps(event_info, cls=JSONEncoder)
        mqtt.async_publish(pub_topic, msg)

    # Only listen for local events if you are going to publish them.
    if pub_topic:
        hass.bus.async_listen(MATCH_ALL, _event_publisher)

    # Process events from a remote server that are received on a queue.
    @callback
    def _event_receiver(topic, payload, qos):
        """Receive events published by and fire them on this hass instance."""
        event = json.loads(payload)
        event_type = event.get('event_type')
        event_data = event.get('event_data')

        # Special case handling for event STATE_CHANGED
        # We will try to convert state dicts back to State objects
        # Copied over from the _handle_api_post_events_event method
        # of the api component.
        if event_type == EVENT_STATE_CHANGED and event_data:
            for key in ('old_state', 'new_state'):
                state = State.from_dict(event_data.get(key))

                if state:
                    event_data[key] = state

        hass.bus.async_fire(
            event_type,
            event_data=event_data,
            origin=EventOrigin.remote
        )

    # Only subscribe if you specified a topic.
    if sub_topic:
        yield from mqtt.async_subscribe(sub_topic, _event_receiver)

    return True
