"""Media Player component to integrate TVs exposing the Joint Space API."""
from datetime import timedelta
import logging

import voluptuous as vol

from homeassistant.components.media_player import (
    MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.components.media_player.const import (
    SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
    SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
    SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
    MEDIA_TYPE_CHANNEL, SUPPORT_PLAY_MEDIA)
from homeassistant.const import (
    CONF_API_VERSION, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import call_later, track_time_interval
from homeassistant.helpers.script import Script

_LOGGER = logging.getLogger(__name__)

SUPPORT_PHILIPS_JS = SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | \
                     SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
                     SUPPORT_SELECT_SOURCE | SUPPORT_NEXT_TRACK | \
                     SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY_MEDIA

CONF_ON_ACTION = 'turn_on_action'

DEFAULT_NAME = "Philips TV"
DEFAULT_API_VERSION = '1'
DEFAULT_SCAN_INTERVAL = 30

DELAY_ACTION_DEFAULT = 2.0
DELAY_ACTION_ON = 10.0

PREFIX_SEPARATOR = ': '
PREFIX_SOURCE = 'Input'
PREFIX_CHANNEL = 'Channel'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Optional(CONF_API_VERSION, default=DEFAULT_API_VERSION): cv.string,
    vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
})


def _inverted(data):
    return {v: k for k, v in data.items()}


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Philips TV platform."""
    import haphilipsjs

    name = config.get(CONF_NAME)
    host = config.get(CONF_HOST)
    api_version = config.get(CONF_API_VERSION)
    turn_on_action = config.get(CONF_ON_ACTION)

    tvapi = haphilipsjs.PhilipsTV(host, api_version)
    on_script = Script(hass, turn_on_action) if turn_on_action else None

    add_entities([PhilipsTV(tvapi, name, on_script)])


class PhilipsTV(MediaPlayerDevice):
    """Representation of a Philips TV exposing the JointSpace API."""

    def __init__(self, tv, name, on_script):
        """Initialize the Philips TV."""
        self._tv = tv
        self._name = name
        self._sources = {}
        self._channels = {}
        self._on_script = on_script
        self._supports = SUPPORT_PHILIPS_JS
        if self._on_script:
            self._supports |= SUPPORT_TURN_ON
        self._update_task = None

    def _update_soon(self, delay):
        """Reschedule update task."""
        if self._update_task:
            self._update_task()
            self._update_task = None

        self.schedule_update_ha_state(
            force_refresh=False)

        def update_forced(event_time):
            self.schedule_update_ha_state(force_refresh=True)

        def update_and_restart(event_time):
            update_forced(event_time)
            self._update_task = track_time_interval(
                self.hass, update_forced,
                timedelta(seconds=DEFAULT_SCAN_INTERVAL))

        call_later(self.hass, delay, update_and_restart)

    async def async_added_to_hass(self):
        """Start running updates once we are added to hass."""
        await self.hass.async_add_executor_job(
            self._update_soon, 0)

    @property
    def name(self):
        """Return the device name."""
        return self._name

    @property
    def should_poll(self):
        """Device should be polled."""
        return False

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return self._supports

    @property
    def state(self):
        """Get the device state. An exception means OFF state."""
        if self._tv.on:
            return STATE_ON
        return STATE_OFF

    @property
    def source(self):
        """Return the current input source."""
        if self.media_content_type == MEDIA_TYPE_CHANNEL:
            name = self._channels.get(self._tv.channel_id)
            prefix = PREFIX_CHANNEL
        else:
            name = self._sources.get(self._tv.source_id)
            prefix = PREFIX_SOURCE

        if name is None:
            return None
        return prefix + PREFIX_SEPARATOR + name

    @property
    def source_list(self):
        """List of available input sources."""
        complete = []
        for source in self._sources.values():
            complete.append(PREFIX_SOURCE + PREFIX_SEPARATOR + source)
        for channel in self._channels.values():
            complete.append(PREFIX_CHANNEL + PREFIX_SEPARATOR + channel)
        return complete

    def select_source(self, source):
        """Set the input source."""
        data = source.split(PREFIX_SEPARATOR, 1)
        if data[0] == PREFIX_SOURCE:
            source_id = _inverted(self._sources).get(data[1])
            if source_id:
                self._tv.setSource(source_id)
        elif data[0] == PREFIX_CHANNEL:
            channel_id = _inverted(self._channels).get(data[1])
            if channel_id:
                self._tv.setChannel(channel_id)
        self._update_soon(DELAY_ACTION_DEFAULT)

    @property
    def volume_level(self):
        """Volume level of the media player (0..1)."""
        return self._tv.volume

    @property
    def is_volume_muted(self):
        """Boolean if volume is currently muted."""
        return self._tv.muted

    def turn_on(self):
        """Turn on the device."""
        if self._on_script:
            self._on_script.run()
            self._update_soon(DELAY_ACTION_ON)

    def turn_off(self):
        """Turn off the device."""
        self._tv.sendKey('Standby')
        self._tv.on = False
        self._update_soon(DELAY_ACTION_DEFAULT)

    def volume_up(self):
        """Send volume up command."""
        self._tv.sendKey('VolumeUp')
        self._update_soon(DELAY_ACTION_DEFAULT)

    def volume_down(self):
        """Send volume down command."""
        self._tv.sendKey('VolumeDown')
        self._update_soon(DELAY_ACTION_DEFAULT)

    def mute_volume(self, mute):
        """Send mute command."""
        self._tv.setVolume(None, mute)
        self._update_soon(DELAY_ACTION_DEFAULT)

    def set_volume_level(self, volume):
        """Set volume level, range 0..1."""
        self._tv.setVolume(volume, self._tv.muted)
        self._update_soon(DELAY_ACTION_DEFAULT)

    def media_previous_track(self):
        """Send rewind command."""
        self._tv.sendKey('Previous')
        self._update_soon(DELAY_ACTION_DEFAULT)

    def media_next_track(self):
        """Send fast forward command."""
        self._tv.sendKey('Next')
        self._update_soon(DELAY_ACTION_DEFAULT)

    @property
    def media_channel(self):
        """Get current channel if it's a channel."""
        if self.media_content_type == MEDIA_TYPE_CHANNEL:
            return self._channels.get(self._tv.channel_id)
        return None

    @property
    def media_title(self):
        """Title of current playing media."""
        if self.media_content_type == MEDIA_TYPE_CHANNEL:
            return self._channels.get(self._tv.channel_id)
        return self._sources.get(self._tv.source_id)

    @property
    def media_content_type(self):
        """Return content type of playing media."""
        if (self._tv.source_id == 'tv' or self._tv.source_id == '11'):
            return MEDIA_TYPE_CHANNEL
        if (self._tv.source_id is None and self._tv.channels):
            return MEDIA_TYPE_CHANNEL
        return None

    @property
    def media_content_id(self):
        """Content type of current playing media."""
        if self.media_content_type == MEDIA_TYPE_CHANNEL:
            return self._channels.get(self._tv.channel_id)
        return None

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        return {
            'channel_list': list(self._channels.values())
        }

    def play_media(self, media_type, media_id, **kwargs):
        """Play a piece of media."""
        _LOGGER.debug(
            "Call play media type <%s>, Id <%s>", media_type, media_id)

        if media_type == MEDIA_TYPE_CHANNEL:
            channel_id = _inverted(self._channels).get(media_id)
            if channel_id:
                self._tv.setChannel(channel_id)
                self._update_soon(DELAY_ACTION_DEFAULT)
            else:
                _LOGGER.error("Unable to find channel <%s>", media_id)
        else:
            _LOGGER.error("Unsupported media type <%s>", media_type)

    def update(self):
        """Get the latest data and update device state."""
        self._tv.update()

        self._sources = {
            srcid: source['name'] or "Source {}".format(srcid)
            for srcid, source in (self._tv.sources or {}).items()
        }

        self._channels = {
            chid: channel['name']
            for chid, channel in (self._tv.channels or {}).items()
        }
