# -*- 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: Lionel Martin <lionel@fluendo.com>

"""
Status widget for the player
"""

from elisa.plugins.pigment.widgets.box import HBox, VBox, ALIGNMENT
from elisa.plugins.pigment.widgets.progressbar import ProgressBar
from elisa.plugins.poblesec.widgets.player.volumebar import VolumeBar
from elisa.plugins.poblesec.widgets.player.buffering_bar import BufferingBar
from elisa.plugins.pigment.widgets.const import *
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.poblesec.widgets.sliced_image import SlicedImageHorizontal
from elisa.plugins.pigment.widgets.theme import Theme
from elisa.core.utils.i18n import install_translation
from elisa.extern import enum

import pgm, gobject, time
from twisted.internet import reactor

_ = install_translation('poblesec')

class PlayerStatusLabel(HBox):
    """
    Simple HBox with two labels. useful to display the position at left side
    and the duration at the right side.
    """

    def create_widgets(self):
        # Player status
        # I'm using a HBox to have two labels
        # one aligned on the left, and one on the right.
        self.height = 0.15
        self.visible = True

        self.left = Text()
        self.pack_start(self.left)
        self.left.bg_color = (0, 0, 0, 0)
        self.left.visible = True

        self.right = Text()
        self.right.alignment = pgm.TEXT_ALIGN_RIGHT
        self.pack_start(self.right)
        self.right.bg_color = (0, 0, 0, 0)
        self.right.visible = True

        self.left.height = 1.0
        self.right.height = 1.0

        self.left.width = 0.50
        self.right.width = 0.50

BARS = enum.Enum("SEEKING_BAR", "VOLUME_BAR", "BUFFERING_BAR")


class ProgressBarCollection(VBox):
    """
    Collection of three progress bars:
     - seeking_bar
     - volume_bar
     - buffer_bar

    The widget contains the three progress bars and a 3 PlayerStatusLabel.
    Only one progress bar (and its label) is visible at the same moment.
    """

    def create_widgets(self):
        # Group that contains the 3 progress bar
        self.bar_group = Widget()
        self.pack_start(self.bar_group)
        self.bar_group.position = (0.0, 0.0, 0.0)
        self.bar_group.visible = True

        # Seeking progress bar
        self.seeking_bar = ProgressBar()
        self.seeking_bar._orientation = HORIZONTAL
        self.bar_group.add(self.seeking_bar)
        self.seeking_bar.bg_color = (0, 0, 0, 0) 
        self.seeking_bar.position = (0.0, 0.0, 0.0)
        self.seeking_bar.size = (1.0, 1.0)
        bg = SlicedImageHorizontal()
        self.seeking_bar.background = bg
        self.seeking_bar.visible = False

        # Volume progress bar
        self.volume_bar = VolumeBar()
        self.volume_bar.visible = False
        self.bar_group.add(self.volume_bar)
        self.volume_bar.position = (0.0, 0.0, 0.0)
        self.volume_bar.size = (1.0, 1.0)

        # Buffering bar
        self.buffering_bar = BufferingBar()
        self.buffering_bar._orientation = HORIZONTAL
        self.bar_group.add(self.buffering_bar)
        self.buffering_bar.bg_color = (0, 0, 0, 0) 
        self.buffering_bar.position = (0.0, 0.0, 0.0)
        self.buffering_bar.size = (1.0, 1.0)
        self.buffering_bar.visible = False
        self.buffering_bar.sensitive = False

        # Group of PlayerStatusLabel, one for each progress bar
        self.label_group = Widget()
        self.pack_start(self.label_group)
        self.label_group.position = (0.0, 0.0, 0.0)
        self.label_group.visible = True

        # PlayerStatusLabel for the seeking bar
        self.seeking_bar_label = PlayerStatusLabel()
        self.label_group.add(self.seeking_bar_label)
        self.seeking_bar_label.bg_color = (0, 0, 0, 0) 
        self.seeking_bar_label.position = (0.0, 0.0, 0.0)
        self.seeking_bar_label.size = (1.0, 1.0)
        self.seeking_bar_label.visible = False

        # PlayerStatusLabel for the volume bar
        self.volume_bar_label = Text()
        self.label_group.add(self.volume_bar_label)
        self.volume_bar_label.bg_color = (0, 0, 0, 0) 
        self.volume_bar_label.position = (0.0, 0.0, 0.0)
        self.volume_bar_label.size = (1.0, 1.0)
        self.volume_bar_label.visible = False

        # PlayerStatusLabel for the buffering bar
        self.buffering_bar_label = Text()
        self.label_group.add(self.buffering_bar_label)
        self.buffering_bar_label.bg_color = (0, 0, 0, 0) 
        self.buffering_bar_label.position = (0.0, 0.0, 0.0)
        self.buffering_bar_label.size = (1.0, 1.0)
        self.buffering_bar_label.visible = False

        # By default, the seeking bar and it label are visible
        self._visible_bar = BARS.SEEKING_BAR
        self.set_visible_bar(self._visible_bar)

    def update_style_properties(self, props=None):
        if props is None:
            return

        theme = Theme.get_default()

        remaining_props = {}

        for key, value in props.iteritems():
            if key == 'osd-pbc-seeking-bar-bg-left':
                filename = theme.get_resource(value)
                self.seeking_bar.background.left_cap.set_from_file(filename)
            elif key == 'osd-pbc-seeking-bar-bg-right':
                filename = theme.get_resource(value)
                self.seeking_bar.background.right_cap.set_from_file(filename)
            elif key == 'osd-pbc-seeking-bar-bg-body':
                filename = theme.get_resource(value)
                self.seeking_bar.background.body.set_from_file(filename)
            elif key == 'osd-pbc-seeking-bar-cursor-spacing-x':
                self.seeking_bar.style.spacing_x = value
                self.volume_bar.progressbar.style.spacing_x = value
            elif key == 'osd-pbc-seeking-bar-cursor-spacing-y':
                self.seeking_bar.style.spacing_y = value
                self.volume_bar.progressbar.style.spacing_y = value
            else:
                remaining_props[key] = value

        if len(remaining_props) > 0:
            return super(ProgressBarCollection, self).update_style_properties(remaining_props)

    def set_visible_bar(self, value):
        """
        Display a specific bar with the associated label
        
        @param value: the bar to display
        @type  value: L{BARS}
        """

        if value == BARS.SEEKING_BAR:
            new_visible_widget = self.seeking_bar
            new_visible_label = self.seeking_bar_label
        elif value == BARS.BUFFERING_BAR:
            new_visible_widget = self.buffering_bar
            new_visible_label = self.buffering_bar_label
        elif value == BARS.VOLUME_BAR:
            new_visible_widget = self.volume_bar
            # Actually hiding the volume bar label
            new_visible_label = None

        if new_visible_widget == self.buffering_bar:
            if self._visible_bar != BARS.BUFFERING_BAR:
                 self.buffering_bar.start_animation()
        else:
            if self._visible_bar == BARS.BUFFERING_BAR:
                self.buffering_bar.stop_animation()

        for widget in (self.seeking_bar, self.volume_bar, self.buffering_bar):
            if widget == new_visible_widget:
                widget.sensitive = True
                widget.visible = True
            else:
                widget.sensitive = False
                widget.visible = False
        
        for widget in (self.seeking_bar_label, self.volume_bar_label, \
                                                      self.buffering_bar_label):
            if widget == new_visible_label:
                widget.visible = True
            else:
                widget.visible = False
                
        self._visible_bar =  value            
                        
    def get_visible_bar(self):
        """
        Return the visible bar.

        @rtype: L{BARS}
        """
        return self._visible_bar
                
    def set_sensitive(self, value):
        """
        Enable or disable the sensitive mode for all the bars
        ( note that the buffering bar is not included because the sensitive mode
        is always disabled for it )
        
        @param value: True or False
        @type  value: bool
        """
        if self._visible_bar == BARS.SEEKING_BAR:
            self.seeking_bar.sensitive = value
            self.volume_bar.progressbar.sensitive = not value
        elif self._visible_bar == BARS.VOLUME_BAR:
            self.volume_bar.progressbar.sensitive = value
            self.seeking_bar.sensitive = not value


class StatusDisplayDetails(VBox):

    """
    right part of the status widget
    with the scroll bar, title, details and player status
    """

    def create_widgets(self):
        self.spacing = 0.01

        # Title of the status box
        self.title = Text()
        self.title.ellipsize = pgm.TEXT_ELLIPSIZE_END
        self.title.label = ""
        self.pack_start(self.title)
        self.title.bg_color = (0, 0, 0, 0)
        self.title.visible = True

        # details of the media in the status box line 1
        self.details = Text()
        self.details.label = ""
        self.details.ellipsize = pgm.TEXT_ELLIPSIZE_END
        self.pack_start(self.details)
        self.details.bg_color = (0, 0, 0, 0)
        self.details.visible = True

        # details of the media in the status box, line2
        self.details_2 = Text()
        self.details_2.label = ""
        self.details_2.ellipsize = pgm.TEXT_ELLIPSIZE_END
        self.pack_start(self.details_2)
        self.details_2.bg_color = (0, 0, 0, 0)  
        self.details_2.visible = True

        # empty space between the line 2 of the details and the seek bars
        self.status_empty_space = Image()
        self.pack_start(self.status_empty_space)
        self.status_empty_space.bg_color = (0, 0, 0, 0)   
        self.status_empty_space.visible = True

        # Smart progress bar
        self.progress_bars = ProgressBarCollection()
        self.pack_start(self.progress_bars)
        self.progress_bars.visible = True


class StatusDisplay(HBox):
    """
    Main widget that displays the details of the current
    file: preview, title, detailed info, seeking bar and position information.

    It is a horizontal box that contains 3 elements:

     - preview: the preview of the file

     - status_display_details: a widget that contains a title, seeking bars and
                             information

     - right_empty_space: an empty space used in positioning

    Emit the signals:

     - seeking-changed: when the position of the seeking bar changed

     - volume-changed: when the position of the volume bar changed

    @ivar current_index: the current value, from 0 to (items_number - 1)
    @type current_index: C{int}
    """
    
    __gsignals__ = {
        'seeking-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                           (gobject.TYPE_INT,)),
        'volume-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                           (gobject.TYPE_INT,)),
        }

    def create_widgets(self):
        # Default formated position of the seeking bar (used for display)
        self._formated_seeking_position = self._format_bar_position(0)
        self._formated_seeking_duration = self._format_bar_position(0)
        self._seeking_position = -1
        self.alignment = ALIGNMENT.START

        # Default position of the volume bar
        self._volume_max = 0
        self._volume_position = 0

        # HBox initialization
        self.spacing = 0.01
        self.alignment = ALIGNMENT.START

        # Image preview on the left
        self.preview = Image()
        self.pack_start(self.preview)
        self.preview.bg_color = (0, 0, 0, 0)    
        self.preview.size = (0.35, 1.00)
        self.preview.layout = pgm.IMAGE_SCALED
        self.preview.alignment = pgm.IMAGE_RIGHT
        self.preview.visible = True

        # Details of the current file (bars, title, position...)
        self.status_display_details = StatusDisplayDetails()
        self.pack_start(self.status_display_details)
        self.status_display_details.size = (0.35, 1.00)
        self.status_display_details.visible = True
        self.status_display_details.progress_bars.seeking_bar.connect \
                                ('index-changed', self._seeking_bar_index_cb)
        self.status_display_details.progress_bars.volume_bar.progressbar.connect \
                                ('index-changed', self._volume_bar_index_cb)

        # empty space. Needed for the picture case, the video case using all
        # the space
        self.right_empty_space = Image()
        self.pack_start(self.right_empty_space)
        self.right_empty_space.size = (0.30, 1.00)
        self.right_empty_space.bg_color = (0, 0, 0, 0)
        self.right_empty_space.visible = True
        self._update_seeking_counter()
        self._update_volume_counter()

    def _update_style_properties(self, props=None):
        if props is None:
            return

        remaining_props = {}

        for key, value in props.iteritems():
            if key == 'center-position':
                # Unused?
                self.preview.width = value
                self.status_display_details.width = 1.0 - value
            else:
                remaining_props[key] = value

        if len(remaining_props) > 0:
            return super(StatusDisplay, self).update_style_properties(remaining_props)

    def _format_bar_position(self, value):
        if value == -1:
            return 0
        return str(value)
                
    def set_seeking_duration(self, length):
        """
        Set the duration of the seeking bar
        
        @param length: the position to set
        @type length:  C{int}
        """
        self.status_display_details.progress_bars.seeking_bar.items_number = \
                                                            length
        self._formated_seeking_duration = self._format_bar_position(length)
        self._update_seeking_counter()
        
    def set_seeking_position(self, value):
        """
        Set the position of the seeking bar
        
        @param value: the position to set
        @type value:  C{int}
        """
        self._seeking_position = value
        self._formated_seeking_position = self._format_bar_position(value + 1)

        # update progress bar if it's visible, otherwise force an update of the
        # position label
        if self.status_display_details.progress_bars.get_visible_bar() == \
                                            BARS.SEEKING_BAR:
            self.status_display_details.progress_bars.seeking_bar.current_index \
            = self._seeking_position

        self._update_seeking_counter()
    
    def _update_seeking_counter(self):
        pbc = self.status_display_details.progress_bars
        pbc.seeking_bar_label.left.label = _("Photo %(index)s of %(count)s") %  \
            {'index': self._formated_seeking_position,
             'count': self._formated_seeking_duration}
    
    def _seeking_bar_index_cb(self, widget, index, old_index):
        if self._seeking_position == index:
            # it is because we set the index
            return
        
        self._formated_seeking_position = value + 1
        self._update_seeking_counter()

        self.emit('seeking-changed', value)
        
    def _volume_bar_index_cb(self, widget, index, old_index):
        if int(self._volume_position) == index:
            # it is because we set the index
            return

        self._volume_position = index
        self._update_volume_counter()
        
        self.emit('volume-changed', index)
                
            
    def set_volume_max(self, value):
        """
        Set the maximum (duration) of the volume bar
        
        @param value: the volume maximum value
        @type  value: int
        """
        pbc = self.status_display_details.progress_bars
        pbc.volume_bar.progressbar.items_number = int(value)
        self._volume_max = int(value)
        self._update_volume_counter()

    def set_volume_position(self, value):
        """
        Set the volume level in the volume bar
        
        @param value: the volume value
        @type  value: int
        """
        self._volume_position = int(value)
        pbc = self.status_display_details.progress_bars
        pbc.volume_bar.progressbar.current_index = self._volume_position
        self._update_volume_counter()
            
    def _update_volume_counter(self):
        """
        update the labels and the volume bar
        """
        pbc = self.status_display_details.progress_bars
        if self._volume_position == 0.0:
            volume_level = 0.0
        else:
            volume_level = float(self._volume_position)/self._volume_max*100.0
        
        pbc.volume_bar_label.label = "%s %s%%" % (_('Volume'), int(volume_level))


                
    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        widget = cls()
        widget.visible = True

        widget.position = (0.0, 1.0, 0.0)
        widget.size = (4.0, 0.50)
        
        return widget
    
class PictureStatusDisplay(StatusDisplay):
    """
    Customization of StatusDisplay for the picture case :
    The player position details right label is not used
    """

    factor = 1

    def __init__(self):
        super(PictureStatusDisplay, self).__init__()
        bars = self.status_display_details.progress_bars
        bars.seeking_bar.visible = False
        bars.seeking_bar_label.left.width = 1.0
        bars.seeking_bar_label.right.width = 0.0

        self._hiding_call = None

    def display_volume_bar(self):
        """
        Display the volume bar for 1 second then display back the image indexes.
        """
        bars = self.status_display_details.progress_bars
        bars.set_visible_bar(BARS.VOLUME_BAR)

        # schedule hiding of volume bar
        if self._hiding_call != None and self._hiding_call.active():
            self._hiding_call.cancel()
        self._hiding_call = reactor.callLater(1.0, self.hide_volume_bar)

    def hide_volume_bar(self):
        """
        Hide the volume bar and display the image indexes.
        """
        bars = self.status_display_details.progress_bars
        bars.volume_bar.visible = False
        bars.seeking_bar_label.visible = True

    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        widget = cls()
        widget.position = (0.0, 1.0, 0.0)
        widget.size = (4.0, 0.50)
        widget.preview.set_from_file \
               ('elisa-plugins/elisa/plugins/pigment/widgets/data/antibes.jpg')
        widget.status_display_details.title.label = "Antibes"
        widget.status_display_details.details.label = "08/01/2006"
        widget.set_seeking_duration(17)
        widget.set_seeking_position(2)
        widget.visible = True

        return widget

class VideoStatusDisplay(StatusDisplay):
    """
    Customization of StatusDisplay for the video case: The seeking bar
    label is formatted in a different way.
    """
    
    factor = 100

    def __init__(self):
        self._strfformat = "%H:%M:%S"
        super(VideoStatusDisplay, self).__init__()

    def _format_bar_position(self, value):
        if value == -1:
            value = 0
        return time.strftime(self._strfformat, time.gmtime(value))
        
    def set_seeking_position(self, value):
        """
        Set the position of the video seeking bar
        
        @param value: the position to set in seconds
        @type  value: int
        """
        self._formated_seeking_position = self._format_bar_position(value)
        self._seeking_position = value * self.factor

        # update progress bar if it's visible, otherwise force an update of the
        # position label
        if self.status_display_details.progress_bars.get_visible_bar() == \
                                            BARS.SEEKING_BAR:
            self.status_display_details.progress_bars.seeking_bar.current_index = \
            self._seeking_position
            
        self._update_seeking_counter()

    def _recalculate_strfformat(self, value):
        if value >= 3600:
            self._strfformat = "%H:%M:%S"
        elif value >= 60:
            self._strfformat = "%M:%S"
        elif value > 0:
            self._strfformat = "%Ss"
        else:
            self._strfformat = ""

    def set_seeking_duration(self, value):
        """
        Set the duration of the video seeking bar
        
        @param value: the duration to set in seconds
        @type  value: int
        """
        # hide the progress bar if we have no duration information (e.g: live stream)
        seeking_bar = self.status_display_details.progress_bars.seeking_bar
        if value == -1:
            seeking_bar.visible = False
        elif not seeking_bar.visible:
            seeking_bar.visible = True

        seeking_bar.items_number =  value * self.factor

        self._recalculate_strfformat(value)
        self._formated_seeking_duration = self._format_bar_position(value)
        self._update_seeking_counter()
        
    def _update_seeking_counter(self):
        """
        update the labels and the video seeking bar
        """
        # no point in displaying a duration of 0 seconds
        pbc = self.status_display_details.progress_bars
        if self._formated_seeking_duration != "00:00:00":
            pbc.seeking_bar_label.left.label = self._formated_seeking_position
            pbc.seeking_bar_label.right.label = self._formated_seeking_duration
        else:
            pbc.seeking_bar_label.left.label = ""
            pbc.seeking_bar_label.right.label = ""
            
    def _seeking_bar_index_cb(self, widget, index, old_index):
        if self._seeking_position == index:
            # it is because we set the index
            return
        
        self._formated_seeking_position = self._format_bar_position(index / self.factor)
        self._update_seeking_counter()

        self.emit('seeking-changed', index / self.factor)
        
    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        widget = cls()
        widget.position = (0.0, 1.0, 0.0)
        widget.size = (4.0, 0.50)
        widget.status_display_details.title.label = "Ratatouille"
        widget.status_display_details.details.label = "press space to swap"
        widget.status_display_details.details_2.label = "Seeking bar"
        widget.set_seeking_duration(3250)
        widget.set_seeking_position(300)
        widget.set_volume_max(100)
        widget.set_volume_position(50)
        widget.status_display_details.progress_bars.buffering_bar_label.label = \
        "No buffering bar for the moment"        
        
        def key_press_event_cb(self, viewport, event, grid):
            if event.keyval == pgm.keysyms.space:
                pdc = self.status_display_details.progress_bars
                visible = pdc.get_visible_bar()
                
                if visible == BARS.SEEKING_BAR:
                    pdc.set_visible_bar(BARS.VOLUME_BAR)
                elif visible == BARS.VOLUME_BAR:
                    pdc.set_visible_bar(BARS.BUFFERING_BAR)
                else:
                    pdc.set_visible_bar(BARS.SEEKING_BAR)
        
        widget.connect('key-press-event', key_press_event_cb)
        widget.visible = True
        
        return widget

if __name__ == '__main__':
    import logging

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    
    widget = VideoStatusDisplay.demo()
    
    try:
        __IPYTHON__
    except NameError:
        pgm.main()
