import enum
import os
import re
import subprocess

import urllib.request
import urllib.parse

from platypush.context import get_backend, get_plugin
from platypush.plugins.media import MediaPlugin, PlayerState
from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \
    VideoStopEvent, NewPlayingVideoEvent

from platypush.plugins import action


class PlayerEvent(enum.Enum):
    STOP = 'stop'
    PLAY = 'play'
    PAUSE = 'pause'


class MediaOmxplayerPlugin(MediaPlugin):
    """
    Plugin to control video and media playback using OMXPlayer.

    Requires:

        * **omxplayer** installed on your system (see your distro instructions)
        * **omxplayer-wrapper** (``pip install omxplayer-wrapper``)
    """

    def __init__(self, args=[], *argv, **kwargs):
        """
        :param args: Arguments that will be passed to the OMXPlayer constructor
            (e.g. subtitles, volume, start position, window size etc.) see
            https://github.com/popcornmix/omxplayer#synopsis and
            http://python-omxplayer-wrapper.readthedocs.io/en/latest/omxplayer/#omxplayer.player.OMXPlayer
        :type args: list
        """

        super().__init__(*argv, **kwargs)

        self.args = args
        self._player = None
        self._handlers = { e.value: [] for e in PlayerEvent }

    @action
    def play(self, resource, subtitles=None, *args, **kwargs):
        """
        Play a resource.

        :param resource: Resource to play. Supported types:

            * Local files (format: ``file://<path>/<file>``)
            * Remote videos (format: ``https://<url>/<resource>``)
            * YouTube videos (format: ``https://www.youtube.com/watch?v=<id>``)
            * Torrents (format: Magnet links, Torrent URLs or local Torrent files)
        """

        if subtitles:
            args.append('--subtitles', subtitles)

        resource = self._get_resource(resource)
        if self._player:
            try:
                self._player.stop()
                self._player = None
            except Exception as e:
                self.logger.exception(e)
                self.logger.warning('Unable to stop a previously running instance ' +
                                'of OMXPlayer, trying to play anyway')

        try:
            from omxplayer import OMXPlayer
            self._player = OMXPlayer(resource, args=self.args)
            self._init_player_handlers()
        except DBusException as e:
            self.logger.warning('DBus connection failed: you will probably not ' +
                            'be able to control the media')
            self.logger.exception(e)

        return self.status()

    @action
    def pause(self):
        """ Pause the playback """
        if self._player: self._player.play_pause()

    @action
    def stop(self):
        """ Stop the playback """
        if self._player:
            self._player.stop()

        return {'status':'stop'}

    @action
    def quit(self):
        """ Quit the player """
        if self._player:
            self._player.stop()
            self._player.quit()
            self._player = None

        return {'status':'stop'}


    @action
    def voldown(self):
        """ Volume down by 10% """
        if self._player:
            self._player.set_volume(max(-6000, self._player.volume()-1000))
        return self.status()

    @action
    def volup(self):
        """ Volume up by 10% """
        if self._player:
            self._player.set_volume(min(0, self._player.volume()+1000))
        return self.status()

    @action
    def back(self, offset=60):
        """ Back by (default: 60) seconds """
        if self._player:
            self._player.seek(-offset)
        return self.status()

    @action
    def forward(self, offset=60):
        """ Forward by (default: 60) seconds """
        if self._player:
            self._player.seek(+offset)
        return self.status()

    @action
    def next(self):
        """ Play the next track/video """
        if self._player:
            self._player.stop()

        if self.videos_queue:
            video = self.videos_queue.pop(0)
            return self.play(video)

    @action
    def hide_subtitles(self):
        """ Hide the subtitles """
        if self._player: self._player.hide_subtitles()
        return self.status()

    @action
    def hide_video(self):
        """ Hide the video """
        if self._player: self._player.hide_video()
        return self.status()

    @action
    def is_playing(self):
        """
        :returns: True if it's playing, False otherwise
        """

        if self._player: return self._player.is_playing()
        else: return False

    @action
    def load(self, resource, pause=False):
        """
        Load a resource/video in the player.

        :param pause: If set, load the video in paused mode (default: False)
        :type pause: bool
        """

        if self._player: self._player.load(resource, pause)
        return self.status()

    @action
    def metadata(self):
        """ Get the metadata of the current video """
        if self._player:
            return self._player.metadata()
        return self.status()

    @action
    def mute(self):
        """ Mute the player """
        if self._player: self._player.mute()
        return self.status()

    @action
    def unmute(self):
        """ Unmute the player """
        if self._player: self._player.unmute()
        return self.status()

    @action
    def seek(self, relative_position):
        """
        Seek backward/forward by the specified number of seconds

        :param relative_position: Number of seconds relative to the current cursor
        :type relative_position: int
        """

        if self._player: self._player.seek(relative_position)
        return self.status()

    @action
    def set_position(self, position):
        """
        Seek backward/forward to the specified absolute position

        :param position: Number of seconds from the start
        :type position: int
        """

        if self._player: self._player.set_seek(position)
        return self.status()

    @action
    def set_volume(self, volume):
        """
        Set the volume

        :param volume: Volume value between 0 and 100
        :type volume: int
        """

        # Transform a [0,100] value to an OMXPlayer volume in [-6000,0]
        volume = 60.0*volume - 6000
        if self._player: self._player.set_volume(volume)
        return self.status()

    @action
    def status(self):
        """
        Get the current player state.

        :returns: A dictionary containing the current state.

        Example::

            output = {
                "source": "https://www.youtube.com/watch?v=7L9KkZoNZkA",
                "state": "play",
                "volume": 80,
                "elapsed": 123,
                "duration": 300,
                "width": 800,
                "height": 600
            }
        """

        state = PlayerState.STOP.value

        if self._player:
            state = self._player.playback_status().lower()
            if state == 'playing': state = PlayerState.PLAY.value
            elif state == 'stopped': state = PlayerState.STOP.value
            elif state == 'paused': state = PlayerState.PAUSE.value

            return {
                'source': self._player.get_source(),
                'state': state,
                'volume': self._player.volume(),
                'elapsed': self._player.position(),
                'duration': self._player.duration(),
                'width': self._player.width(),
                'height': self._player.height(),
            }
        else:
            return {
                'state': PlayerState.STOP.value
            }

    @action
    def send_message(self, msg):
        try:
            redis = get_backend('redis')
            if not redis:
                raise KeyError()
        except KeyError:
            self.logger.warning("Backend {} does not implement send_message " +
                                "and the fallback Redis backend isn't configured")
            return

        redis.send_message(msg)

    def add_handler(self, event_type, callback):
        if event_type not in self._handlers.keys():
            raise AttributeError('{} is not a valid PlayerEvent type'.
                                 format(event_type))

        self._handlers[event_type].append(callback)

    def on_play(self):
        def _f(player):
            video = self._player.get_source()
            self.send_message(VideoPlayEvent(video=video))
            for callback in self._handlers[PlayerEvent.PLAY.value]:
                callback(video)

        return _f

    def on_pause(self):
        def _f(player):
            video = self._player.get_source()
            self.send_message(VideoPauseEvent(video=video))
            for callback in self._handlers[PlayerEvent.PAUSE.value]:
                callback(video)

        return _f

    def on_stop(self):
        def _f(player):
            self.send_message(VideoStopEvent())
            for callback in self._handlers[PlayerEvent.STOP.value]:
                callback()
        return _f


    def _init_player_handlers(self):
        if not self._player:
            return

        self._player.playEvent += self.on_play()
        self._player.pauseEvent += self.on_pause()
        self._player.stopEvent += self.on_stop()


# vim:sw=4:ts=4:et: