import os
import urllib.parse

from platypush.context import get_bus, get_plugin
from platypush.plugins.media import PlayerState, MediaPlugin
from platypush.message.event.media import MediaPlayEvent, MediaPlayRequestEvent, \
    MediaPauseEvent, MediaStopEvent, MediaSeekEvent, MediaVolumeChangedEvent, \
    MediaMuteChangedEvent, NewPlayingMediaEvent

from platypush.plugins import action


class MediaVlcPlugin(MediaPlugin):
    """
    Plugin to control vlc instances

    Requires:

        * **python-vlc** (``pip install python-vlc``)
        * **vlc** executable on your system
    """

    def __init__(self, args=None, fullscreen=False, volume=100, *argv, **kwargs):
        """
        Create the vlc wrapper.

        :param args: List of extra arguments to pass to the VLC executable (e.g.
            ``['--sub-language=en', '--snapshot-path=/mnt/snapshots']``)
        :type args: list[str]

        :param fullscreen: Set to True if you want media files to be opened in
            fullscreen by default (can be overridden by `.play()`) (default: False)
        :type fullscreen: bool

        :param volume: Default media volume (default: 100)
        :type volume: int
        """

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

        self._args = args or []
        self._instance = None
        self._player = None
        self._latest_seek = None
        self._default_fullscreen = fullscreen
        self._default_volume = volume
        self._on_stop_callbacks = []
        self._title = None
        self._filename = None

    @classmethod
    def _watched_event_types(cls):
        import vlc
        return [getattr(vlc.EventType, evt) for evt in [
            'MediaPlayerLengthChanged', 'MediaPlayerMediaChanged',
            'MediaDurationChanged', 'MediaPlayerMuted',
            'MediaPlayerUnmuted', 'MediaPlayerOpening', 'MediaPlayerPaused',
            'MediaPlayerPlaying', 'MediaPlayerPositionChanged',
            'MediaPlayerStopped', 'MediaPlayerTimeChanged', 'MediaStateChanged',
            'MediaPlayerForward', 'MediaPlayerBackward',
            'MediaPlayerEndReached', 'MediaPlayerTitleChanged',
            'MediaPlayerAudioVolume',
        ] if hasattr(vlc.EventType, evt)]

    def _init_vlc(self, resource):
        import vlc
        self._reset_state()

        for k, v in self._env.items():
            os.environ[k] = v

        self._instance = vlc.Instance(*self._args)
        self._player = self._instance.media_player_new(resource)

        for evt in self._watched_event_types():
            self._player.event_manager().event_attach(
                eventtype=evt, callback=self._event_callback())

    def _reset_state(self):
        self._latest_seek = None
        self._title = None
        self._filename = None

        if self._player:
            self._player.release()
            self._player = None

        if self._instance:
            self._instance.release()
            self._instance = None

    @staticmethod
    def _post_event(evt_type, **evt):
        bus = get_bus()
        bus.post(evt_type(player='local', plugin='media.vlc', **evt))

    def _event_callback(self):
        def callback(event):
            from vlc import EventType
            self.logger.debug('Received vlc event: {}'.format(event))

            if event.type == EventType.MediaPlayerPlaying:
                self._post_event(MediaPlayEvent, resource=self._get_current_resource())
            elif event.type == EventType.MediaPlayerPaused:
                self._post_event(MediaPauseEvent)
            elif event.type == EventType.MediaPlayerStopped or \
                    event.type == EventType.MediaPlayerEndReached:
                self._reset_state()
                self._post_event(MediaStopEvent)
                for cbk in self._on_stop_callbacks:
                    cbk()
            elif event.type == EventType.MediaPlayerTitleChanged:
                self._filename = event.u.filename
                self._title = event.u.new_title
                self._post_event(NewPlayingMediaEvent, resource=event.u.new_title)
            elif event.type == EventType.MediaPlayerMediaChanged:
                self._filename = event.u.filename
                self._title = event.u.new_title
                self._post_event(NewPlayingMediaEvent, resource=event.u.filename)
            elif event.type == EventType.MediaPlayerLengthChanged:
                self._post_event(NewPlayingMediaEvent, resource=self._get_current_resource())
            elif event.type == EventType.MediaPlayerTimeChanged:
                pos = float(event.u.new_time/1000)
                if self._latest_seek is None or \
                        abs(pos-self._latest_seek) > 5:
                    self._post_event(MediaSeekEvent, position=pos)
                self._latest_seek = pos
            elif event.type == EventType.MediaPlayerAudioVolume:
                self._post_event(MediaVolumeChangedEvent, volume=self._player.audio_get_volume())
            elif event.type == EventType.MediaPlayerMuted:
                self._post_event(MediaMuteChangedEvent, mute=True)
            elif event.type == EventType.MediaPlayerUnmuted:
                self._post_event(MediaMuteChangedEvent, mute=False)

        return callback

    @action
    def play(self, resource, subtitles=None, fullscreen=None, volume=None):
        """
        Play a resource.

        :param resource: Resource to play - can be a local file or a remote URL
        :type resource: str

        :param subtitles: Path to optional subtitle file
        :type subtitles: str

        :param fullscreen: Set to explicitly enable/disable fullscreen (default:
            `fullscreen` configured value or False)
        :type fullscreen: bool

        :param volume: Set to explicitly set the playback volume (default:
            `volume` configured value or 100)
        :type fullscreen: bool
        """

        get_bus().post(MediaPlayRequestEvent(resource=resource))
        resource = self._get_resource(resource)

        if resource.startswith('file://'):
            resource = resource[len('file://'):]

        self._init_vlc(resource)
        if subtitles:
            if subtitles.startswith('file://'):
                subtitles = subtitles[len('file://'):]
            self._player.video_set_subtitle_file(subtitles)

        self._player.play()
        if self.volume:
            self.set_volume(volume=self.volume)

        if fullscreen or self._default_fullscreen:
            self.set_fullscreen(True)

        if volume is not None or self._default_volume is not None:
            self.set_volume(volume if volume is not None
                            else self._default_volume)

        return self.status()

    @action
    def pause(self):
        """ Toggle the paused state """
        if not self._player:
            return None, 'No vlc instance is running'
        if not self._player.can_pause():
            return None, 'The specified media type cannot be paused'

        self._player.pause()
        return self.status()

    @action
    def quit(self):
        """ Quit the player (same as `stop`) """
        self._stop_torrent()
        if not self._player:
            return None, 'No vlc instance is running'

        self._player.stop()
        self._player = None
        return self.status()

    @action
    def stop(self):
        """ Stop the application (same as `quit`) """
        return self.quit()

    @action
    def voldown(self, step=10.0):
        """ Volume down by (default: 10)% """
        if not self._player:
            return None, 'No vlc instance is running'
        return self.set_volume(self._player.audio_get_volume()-step)

    @action
    def volup(self, step=10.0):
        """ Volume up by (default: 10)% """
        if not self._player:
            return None, 'No vlc instance is running'
        return self.set_volume(self._player.audio_get_volume()+step)

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

        :param volume: Volume value between 0 and 100
        :type volume: float
        """
        if not self._player:
            return None, 'No vlc instance is running'

        volume = max(0, min([100, volume]))
        self._player.audio_set_volume(volume)
        status = self.status().output
        status['volume'] = volume
        return status

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

        :param position: Number of seconds relative to the current cursor
        :type position: int
        """
        if not self._player:
            return None, 'No vlc instance is running'
        if not self._player.is_seekable():
            return None, 'The resource is not seekable'

        media = self._player.get_media()
        if not media:
            return None, 'No media loaded'

        pos = min(media.get_duration()/1000, max(0, position))
        self._player.set_time(int(pos*1000))
        return self.status()

    @action
    def back(self, offset=60.0):
        """ Back by (default: 60) seconds """
        if not self._player:
            return None, 'No vlc instance is running'

        media = self._player.get_media()
        if not media:
            return None, 'No media loaded'

        pos = max(0, (self._player.get_time()/1000)-offset)
        return self.seek(pos)

    @action
    def forward(self, offset=60.0):
        """ Forward by (default: 60) seconds """
        if not self._player:
            return None, 'No vlc instance is running'

        media = self._player.get_media()
        if not media:
            return None, 'No media loaded'

        pos = min(media.get_duration()/1000, (self._player.get_time()/1000)+offset)
        return self.seek(pos)

    @action
    def toggle_subtitles(self, visibile=None):
        """ Toggle the subtitles visibility """
        if not self._player:
            return None, 'No vlc instance is running'

        if self._player.video_get_spu_count() == 0:
            return None, 'The media file has no subtitles set'

        if self._player.video_get_spu() is None or \
                self._player.video_get_spu() == -1:
            self._player.video_set_spu(0)
        else:
            self._player.video_set_spu(-1)

    @action
    def toggle_fullscreen(self):
        """ Toggle the fullscreen mode """
        if not self._player:
            return None, 'No vlc instance is running'
        self._player.toggle_fullscreen()

    @action
    def set_fullscreen(self, fullscreen=True):
        """ Set fullscreen mode """
        if not self._player:
            return None, 'No vlc instance is running'
        self._player.set_fullscreen(fullscreen)

    @action
    def set_subtitles(self, filename, **args):
        """ Sets media subtitles from filename """
        if not self._player:
            return None, 'No vlc instance is running'
        if filename.startswith('file://'):
            filename = filename[len('file://'):]

        self._player.video_set_subtitle_file(filename)

    @action
    def remove_subtitles(self):
        """ Removes (hides) the subtitles """
        if not self._player:
            return None, 'No vlc instance is running'
        self._player.video_set_spu(-1)

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

    @action
    def load(self, resource, **args):
        """
        Load/queue a resource/video to the player
        """
        if not self._player:
            return self.play(resource, **args)
        self._player.set_media(resource)
        return self.status()

    @action
    def mute(self):
        """ Toggle mute state """
        if not self._player:
            return None, 'No vlc instance is running'
        self._player.audio_toggle_mute()

    @action
    def set_position(self, position):
        """
        Seek backward/forward to the specified absolute position (same as ``seek``)
        """
        return self.seek(position)

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

        :returns: A dictionary containing the current state.

        Example::

            output = {
                "filename": "filename or stream URL",
                "state": "play"  # or "stop" or "pause"
            }
        """
        import vlc
        if not self._player:
            return {'state': PlayerState.STOP.value}

        status = {}
        vlc_state = self._player.get_state()

        if vlc_state == vlc.State.Playing:
            status['state'] = PlayerState.PLAY.value
        elif vlc_state == vlc.State.Paused:
            status['state'] = PlayerState.PAUSE.value
        else:
            status['state'] = PlayerState.STOP.value

        status['url'] = urllib.parse.unquote(self._player.get_media().get_mrl()) if self._player.get_media() else None
        status['position'] = float(self._player.get_time()/1000) if self._player.get_time() is not None else None

        media = self._player.get_media()
        status['duration'] = media.get_duration()/1000 if media and media.get_duration() is not None else None

        status['seekable'] = status['duration'] is not None
        status['fullscreen'] = self._player.get_fullscreen()
        status['mute'] = self._player.audio_get_mute()
        status['path'] = status['url']
        status['pause'] = status['state'] == PlayerState.PAUSE.value
        status['percent_pos'] = self._player.get_position()*100
        status['filename'] = self._filename
        status['title'] = self._title
        status['volume'] = self._player.audio_get_volume()
        status['volume_max'] = 100

        return status

    def on_stop(self, callback):
        self._on_stop_callbacks.append(callback)

    def _get_current_resource(self):
        if not self._player or not self._player.get_media():
            return
        return self._player.get_media().get_mrl()


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