# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Florian Boucault <florian@fluendo.com>

from elisa.plugins.poblesec.widgets.player.control_ribbon import Control
from elisa.plugins.pigment.widgets.const import *

from elisa.core.utils import defer
from elisa.core.utils.i18n import install_translation

from twisted.internet import reactor
from twisted.internet.task import LoopingCall


_ = install_translation('poblesec')

class MultiStateControl(Control):
    """
    Abstract class for implementing ribbon controls which have several differently 
    looking glyphs (e.g. Play/Pause).
    The class derived from this one must properly implement _glyph_table and 
    _caption_table.
    """
    _glyphs_table = {}
    _caption_table = {}
        
    def set_state(self, val):
        self._state = val
        self.glyphs = self._glyphs_table[val]
        self.caption = self._caption_table[val]

    def get_state(self):
        return self._state


class PlayPauseControl(MultiStateControl):
    """
    Toggle the player state between play and pause.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}
    or L{elisa.plugins.poblesec.slideshow.player.SlideshowController}.
    """
    _glyphs_table = { 'play': \
            {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.play_normal',
             STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.play_selected',
             STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.play_active',
            },
            'pause': \
            {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.pause_normal',
             STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.pause_selected',
             STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.pause_active',
            } }
    _caption_table = { 'play': _("Play"),
                      'pause': _("Pause")
                      }

    def __init__(self, *args):
        super(PlayPauseControl, self).__init__(*args)
        self.set_state('play')
        # FIXME: should disconnect from the signal
        self.slave.player.connect('status-changed', self._on_player_status_changed)

    def _on_player_status_changed(self, player, status):
        if status is self.slave.player.PLAYING:
            self.set_state('pause')
        else:
            self.set_state('play')

    def activate(self):
        self.slave.toggle_play_pause()
        return defer.succeed(None)


class StopControl(Control):
    """
    Stop the playback of the current media.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """

    caption = _("Stop")
    glyphs = \
            {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.stop_normal',
             STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.stop_selected',
             STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.stop_active',
            }

    def activate(self):
        self.slave.player.stop()
        self.slave.exit()
        return defer.succeed(None)


class SkipOrSeekControl(MultiStateControl):
    """
    Abstract class for implementing SkipPrevious/SkipNext ribbon controls with
    seeking functionality if allowed by played model.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """
    def __init__(self, *args):        
        super(SkipOrSeekControl, self).__init__(*args)
        self.slave.player.connect('current-playable-model-changed',
                                  self._on_player_new_track)
        self._seek_task = None
        self._has_seeked = False
        self._seeking_interval = 0.2
        self.set_state('seek')

    def _on_player_new_track(self, player, model):
        # Try to see if we have current track in our player. If not, default to
        # normal behaviour
        try:
            allow_seek = self.slave.player.playlist.current_playable_model.allow_seek
        except AttributeError:
            allow_seek = True

        if allow_seek:
            self.set_state('seek')
        else:
            self.set_state('noseek')

    def _player_seek(self):
        return defer.fail(NotImplementedError())

    def _player_skip(self):
        return defer.fail(NotImplementedError())

    def activate(self):
        if not self._has_seeked:
            self._player_skip()
        return defer.succeed(None)

    def pre_activate(self):
        # no actual seek was performed yet
        self._has_seeked = False

        # schedule first seek event if seeking is allowed
        if self.get_state() == 'seek':
            self._seek_task = LoopingCall(self._seek)
            self._seek_task.start(self._seeking_interval, now=False)
        return defer.succeed(None)

    def _stop_seeking(self):
        self._seek_task.stop()
        self._seek_task = None

    def de_pre_activate(self):
        # cancel regular seeking
        if self._seek_task != None:
            self._stop_seeking()
        return defer.succeed(None)

    def _seek(self):
        # perform the actual seeking
        self._has_seeked = True
        if self._player_seek() < 0:
            # we don't want to seek again when seeking failed
            self._stop_seeking()
            return


class SkipPreviousControl(SkipOrSeekControl):
    """
    Skip to the previous track/video in the playlist and play it, or
    rewind if pressed longer.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """
    _glyphs_table = { 'seek': \
                {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.rewind_normal',
                 STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.rewind_selected',
                 STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.rewind_active',
                 },
                 'noseek':
                 {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.rewind_noseek_normal',
                  STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.rewind_noseek_selected',
                  STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.rewind_noseek_active',
                  } }
    _caption_table = { 'seek': _("Rewind/Skip Previous"), \
                       'noseek': _("Previous") }

    def _player_seek(self):
        return self.slave.player.seek_backward()

    def _player_skip(self):
        if self.slave.player.playlist.allow_previous:
            self.slave.player.play_previous()


class SkipNextControl(SkipOrSeekControl):
    """
    Skip to the next track/video in the playlist and play it, or
    fast-forward if pressed longer.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """
    _glyphs_table = { 'seek':\
                {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.forward_normal',
                 STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.forward_selected',
                 STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.forward_active',
                 },
                 'noseek':
                 {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.forward_noseek_normal',
                  STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.forward_noseek_selected',
                  STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.forward_noseek_active',
                  } }
    _caption_table = { 'seek': _("Fast Forward/Skip Next"), \
                       'noseek': _("Next") }

    def _player_seek(self):
        return self.slave.player.seek_forward()

    def _player_skip(self):
        if self.slave.player.playlist.allow_next:
            self.slave.player.play_next()


class VolumeUpControl(Control):
    """
    Make the volume higher.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """

    caption = _("Volume Up")
    glyphs = \
            {STATE_NORMAL: 'elisa.plugins.poblesec.player.volume.volup_normal',
             STATE_SELECTED: 'elisa.plugins.poblesec.player.volume.volup_selected',
             STATE_PRESSED: 'elisa.plugins.poblesec.player.volume.volup_active',
            }

    def activate(self):
        self.slave.volume_up()
        return defer.succeed(None)

class VolumeDownControl(Control):
    """
    Make the volume lower.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.PlayerController}.
    """

    caption = _("Volume Down")
    glyphs = \
            {STATE_NORMAL: 'elisa.plugins.poblesec.player.volume.voldown_normal',
             STATE_SELECTED: 'elisa.plugins.poblesec.player.volume.voldown_selected',
             STATE_PRESSED: 'elisa.plugins.poblesec.player.volume.voldown_active',
            }

    def activate(self):
        self.slave.volume_down()
        return defer.succeed(None)

class MultiCaptionControl(Control):
    """
    Class that can temporarily change its caption.
    """

    duration = 3
    _default_caption = ""

    def __init__(self, *args, **kwargs):
        super(MultiCaptionControl, self).__init__(*args, **kwargs)
        self.caption = self._default_caption
        self._temporary_caption = ""
        self._delayed_reset = None

    def _temporary_caption__set(self, caption):

        def reset_caption():
            self.caption = self._default_caption
            self._temporary_caption = ""

        if self._delayed_reset is not None and not self._delayed_reset.called:
            self._delayed_reset.cancel()

        self._temporary_caption = caption
        self.caption = caption

        self._delayed_reset = reactor.callLater(self.duration, reset_caption)

    def _temporary_caption__get(self):
        return self._temporary_caption

    temporary_caption = property(fget=_temporary_caption__get,
                                 fset=_temporary_caption__set)




class SubtitleStreamControl(MultiCaptionControl):
    """
    Change the subtitle track.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.VideoPlayerController}.
    """

    _default_caption = _("Subtitle Selection")

    glyphs = {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.subs_select_normal',
              STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.subs_select_selected',
              STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.subs_select_active',
            }

    def activate(self):
        language = self.slave.next_subtitle()
        if language:
            self.temporary_caption = language
        return defer.succeed(None)

class AudioStreamControl(MultiCaptionControl):
    """
    Change the audio track.

    Its L{slave} passed to the constructor must be of type
    L{elisa.plugins.poblesec.player_video.VideoPlayerController}.
    """

    _default_caption = _("Audio Channel Selection")

    glyphs = {STATE_NORMAL: 'elisa.plugins.poblesec.player.glyphs.audio_select_normal',
              STATE_SELECTED: 'elisa.plugins.poblesec.player.glyphs.audio_select_selected',
              STATE_PRESSED: 'elisa.plugins.poblesec.player.glyphs.audio_select_active',
            }

    def activate(self):
        language = self.slave.next_audio()
        if language:
            self.temporary_caption = language
        return defer.succeed(None)

