# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 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 Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.


__maintainer__ = 'Florian Boucault <florian@fluendo.com>'
__maintainer2__ = 'Lionel Martin <lionel@fluendo.com>'

import pygst
pygst.require('0.10')
import gst

from elisa.core import plugin_registry, common
from elisa.core import player
from elisa.extern.translation import gettexter, N_, Translatable
T_ = gettexter('elisa-poblenou-frontend')

import pgm, gst, math

from pgm.graph.group import Group
from pgm.graph.image import Image
from pgm.graph.text import Text

from raval_widgets.player_osd import PlayerOsd
from raval_widgets.volume_osd import VolumeOsd
from raval_widgets.dock import Dock

from twisted.internet import reactor

class VisualisationError(Exception):
    pass

# The Visualisation Fake
class ElisaVisualisationBin(gst.Bin):

    def __init__(self, visualisation, size, pgm_image):
        """
        The ElisaVisualisationBin is a wrapper for the different visualisation
        elements of GStreamer.

        @param visualisation:   the GStreamer Element to use
        @type visualisation:    string
        @param size:            a size in pixel to calculate the visualisation
                                size for
        @type size:             int
        @param pgm_image:       the pgm_image that is used for sinking
        @type pgm_image:         L{pgm.graph.image.Image}
        """

        gst.Bin.__init__(self)

        self._visualisation = visualisation
        self._pgm_image = pgm_image

        self._capsfilter = gst.element_factory_make('capsfilter')
        self.add(self._capsfilter)

        caps_pad = self._capsfilter.get_pad('src')

        self._visu = gst.element_factory_make(self._visualisation)
        self.add(self._visu)

        height = size * pgm_image.height / pgm_image.width

        visu_sink = self._visu.get_pad('sink')
        visu_src = self._visu.get_pad('src')

        caps = visu_src.get_pad_template().caps

        if caps == None:
            raise VisualisationError("The Visualisation element %s does not" \
                                     " have any Caps." % self._visualisation)

        caps = caps.copy()

        if caps.is_fixed() == False:
            caps.make_writable()
            for i in xrange(0, caps.get_size()):
                cur_struct = caps.get_structure(i)

                cur_struct.fixate_field_nearest_int('width', size)

                cur_struct.fixate_field_nearest_int('height', height)
        else:
            raise VisualisationError("The Caps of the GstElement are fixed.")

        self._capsfilter.set_property('caps', caps)

        self._ghost_sink = gst.GhostPad('sink', visu_sink )
        self._ghost_src = gst.GhostPad('src', caps_pad)

        self._visu.link(self._capsfilter)
        self.add_pad(self._ghost_sink)
        self.add_pad(self._ghost_src)


BasePlayerView = plugin_registry.get_component_class('base:player_view')
# The View
class PlayerView(BasePlayerView):
    supported_controllers = ('base:player_controller',)

    config_doc = {'subtitle_font_size' : 'The size the subtitle font' \
                                         ' should have (in pgm-size)',
                  'visualisation' : 'Use this GStreamer Element for the' \
                                    ' visualisation in the player',
                  'visualisation_size' : 'the size the visualisation should'\
                                         ' use for calculating the real size'
                                         }
    default_config = {'subtitle_font_size' : '0.2',
                      'visualisation' : 'libvisual_jess',
                      'visualisation_size' : 400,
                    }

    def __init__(self):
        super(PlayerView, self).__init__()
        self.context_path = "pigment:pigment_context"

        # group containing all the widgets of the player view
        self.group = None

        # background image displaying the video
        self._background = None

        # Rotating for the loading animation
        self._interaction = None
        self._loading = False
        self._rotation_matrix = pgm.mat4x4_new_identity()
        self._rotation_matrix.translate(0.5, 0.5, 0.0)
        self._rotation_matrix.rotate_z(math.pi / 30.0)
        self._rotation_matrix.translate(-0.5, -0.5, 0.0)
    
        # subtitles overlay
        self._subtitles = None

        # status (current position, title...) on screen display
        self._osd_status = None

        # play/pause button
        self._osd_play_button = None

        # go back button
        self._osd_back_button = None

        # volume control
        self._osd_volume = None
        
        # two possible layouts: 'normal' or 'mouse'
        self._osd_layout = 'normal'
        self._osd_fadeout_delay = 3.0

        self.debug("Connecting to player messages sent over the bus")
        bus = common.application.bus
        bus.register(self._player_loading, player.PlayerLoading)
        bus.register(self._player_buffering, player.PlayerBuffering)
        bus.register(self._player_starting, player.PlayerPlaying)
        bus.register(self._player_stopped, player.PlayerStopping)

        # The id of the reactor.callLater, for the time, when the subtitle
        # should be cleared (because the duration of the buffer wants that)
        self._sub_cleaner = None


    def initialize(self):
        super(PlayerView, self).initialize()
        self._subtitle_font_size = float(self.config.get('subtitle_font_size',
                                                         '0.24'))


    def update_subtitle_buffer(self, input_buffer):
        def clean():
            self._subtitles.label = ''
        
        if self._sub_cleaner and self._sub_cleaner.active():
            self._sub_cleaner.cancel()

        self._subtitles.markup = str(input_buffer)
        duration = input_buffer.duration
        if duration > 0:
            self._sub_cleaner = reactor.callLater(duration / gst.SECOND, clean)

    def _player_buffering(self, msg, sender):
        self._player_loading(msg, sender)

    def _player_loading(self, msg, sender):
        if sender == self.player:
            self._background.bg_a = 0
            self._enable_loading_animation(True)

    def _player_starting(self, msg, sender):
        if sender == self.player:
            self._enable_loading_animation(False)
            if self.controller.model.media_type == 'video':
                self._background.bg_a = 255

    def _player_stopped(self, msg, sender):
        if sender == self.player:
            self._enable_loading_animation(False)
        
        
    def _error(self, msg, sender):
        if sender == self.player:
            self._enable_loading_animation(False)
            content = self.controller.model.media_type
            error_icon = self.frontend.theme.get_media("file_%s_unreadable" %
                                                            content)
            self._background.clear()
            self._interaction.set_from_file(error_icon)
            self._interaction.set_name('error_icon')
            super(PlayerView, self)._error(msg, sender)

    def mouse_moved(self, viewport, event):
        if not self.controller.focused:
            return False

        if self.controller.model.media_type == 'audio':
            self.osd_show(-1, True)
        else:
            self.osd_show(self._osd_fadeout_delay, True)

        return True
        
    def osd_hide(self):
        self._osd_status.hide()
        self._osd_back_button.hide()
        self._osd_volume.hide()
        self._osd_play_button.hide()
        
    def osd_show(self, time_visible=-1, from_mouse=False, show_volume=False):
        if self.frontend.context.touchscreen == True:
            from_mouse = True

        if from_mouse:
            self._osd_back_button.show(time_visible)
        else:
            self._osd_back_button.hide()

        if show_volume:
            self._osd_volume.show(time_visible)
        else:
            self._osd_status.show(time_visible)        
            self._osd_play_button.show(time_visible)

    def _background_clicked(self, drawable, x, y, z, button, time):
        if self.group.visible == False or self.group.opacity == 0.0:
            return False
        
        if (not self.controller.focused) or \
           (self._background.storage_type == pgm.IMAGE_EMPTY):
            return False
        
        if self.player.state == player.STATES.STOPPED:
            return False

        if not self.controller.focused:
            self.controller.focus()
        else:
            if self.controller.model.media_type == 'audio':
                self.osd_show(-1,True)
            else:
                self.osd_show(self._osd_fadeout_delay, True)

        return True
            
    def _osd_status_clicked(self, drawable, x, y, z, button, time):
        if not self.controller.focused:
            return False

        # FIXME: should handle setting the current position in the media
        
        return False

    def _osd_back_button_clicked(self, drawable, x, y, z, button, time):
        if not self.controller.focused:
            return False
        self.controller.parent.focus()
        return True
    
    
    def _osd_play_button_clicked(self, drawable, x, y, z, button, time):
        if not self.controller.focused:
            return False
        
        if self.controller.model.state == player.STATES.PLAYING:
            self.controller.model.state = player.STATES.PAUSED
        else:
            self.controller.model.state = player.STATES.PLAYING     

        return True
    
    def _update_play_button(self, state):
        if state == player.STATES.PAUSED:
            icon = self._play_button_image
        else:
            icon = self._pause_button_image
        
        self._osd_play_button.image_from_path(icon)
        
    def _enable_loading_animation(self, enabled):
        if enabled and self.frontend:
            if self._loading:
                return
            wait_icon = self.frontend.theme.get_media("waiting_icon")
            self._interaction.visible = True
            self._interaction.set_from_file(wait_icon)
            self._interaction.set_name('wait icon')
            self._loading = True
            reactor.callLater(0.023, self._transform_mapping_matrix_cb)
        else:
            self._loading = False
            self._interaction.clear()
            self._interaction.visible = False
            self._background.visible = True
                    
    def _transform_mapping_matrix_cb(self):
        if self._loading:
            self._interaction.mapping_matrix *= self._rotation_matrix
            reactor.callLater(0.017, self._transform_mapping_matrix_cb)

    def attribute_set(self, origin, key, old_value, new_value):
        # On the first initialize
        if key == 'uri' and old_value == None:
            # Visualisation Support
            try:
                self._visualisation = ElisaVisualisationBin(
                                    self.config.get('visualisation'),
                                    int(self.config.get('visualisation_size')),
                                    self._background)
                self.player.visualisation = self._visualisation
            except gst.PluginNotFoundError, exc:
                if isinstance(exc, gst.PluginNotFoundError):
                    self.warning("VisualisationElement '%s' not found." \
                                 " Deactivated." % exc)

            except VisualisationError, exc:
                self.warning("Visualisation deactivated: %s" % exc)
                self.player.visualisation = None

        super(PlayerView, self).attribute_set(origin, key, old_value, new_value)

    def frontend_changed(self, previous_frontend, new_frontend):
        if new_frontend == None:
            return

        super(PlayerView,self).frontend_changed(previous_frontend, new_frontend)
     
        viewport = new_frontend.context.viewport_handle
        viewport.connect("motion-notify-event", self.mouse_moved)

        if self.group == None:
            canvas = self.frontend.context.viewport_handle.get_canvas()

            # FIXME: why is the OSD not in self.group? Because of resizing
            # issue that will be solved when drawable.regenerate is in place
            # in Pigment.

            # create the group containing all the widgets of the player
            self.group = Group(canvas, pgm.DRAWABLE_FAR)
            self.group.visible = True
            self.context_handle = self.group
            
            # media retrieval from the theme
            # FIXME: need theme_changed support
            theme = self.frontend.theme
            self._normal_osd_status_bg = theme.get_media("dock_background")
            self._mouse_osd_status_bg = theme.get_media("dock_background_mouse")
            self._play_button_image = theme.get_media("play_button_mouse")
            self._pause_button_image = theme.get_media("pause_button_mouse")
            
            self._create_background()
            self._create_interaction()
            self._create_subtitles()
            self._create_playpause_button()
            self._create_osd_status_widget()
            self._create_osd_volume_widget()
            self._create_back_widget()
            
                           
    def _create_subtitles(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        
        # create the subtitles overlay
        self._subtitles = Text()
        # 2 lines of subtitles should fit
        self._subtitles.font_height = 0.5
        self._subtitles.weight = pgm.TEXT_WEIGHT_BOLD

        height = self._subtitle_font_size * 2.0
        self._subtitles.position = (0.0, canvas.height - height - 0.1, 1.1)
        self._subtitles.size = (canvas.width, height)
        
        self._subtitles.alignment = pgm.TEXT_ALIGN_CENTER
        self._subtitles.bg_color = (0, 255, 0, 0)
        self._subtitles.fg_color = (255, 255, 255, 255)
        self._subtitles.outline_width = self._subtitles.height * 0.015
        self._subtitles.font_family = "Nimbus Sans L Bold"
        self._subtitles.outline_color = (0, 0, 0, 255)
        self._subtitles.opacity = 255
        self._subtitles.visible = True
        self.group.add(self._subtitles)

        self.player.subtitle_callback = self.update_subtitle_buffer

    def _create_playpause_button(self): 
        canvas = self.frontend.context.viewport_handle.get_canvas()
           
        px, py = 0.7, 0.8
        iw, ih = canvas.width, canvas.height
        osd_status_w = iw * px
        osd_status_h = ih * 0.15

        self._osd_play_button = Dock(canvas,
                                     pgm.DRAWABLE_FAR,
                                     osd_status_h, osd_status_h,
                                     self._pause_button_image)
        self._osd_play_button.opacity = 0
        self._osd_play_button.visible = True
        
        px, py = 0.7, 0.8
        iw, ih = canvas.width, canvas.height
        osd_status_w = iw * px
        osd_status_h = ih * 0.15
        
        self._osd_status_position = ((1-px) * iw/2.0, ih * py, 0.0)
        x = self._osd_status_position[0] - osd_status_h/2.0 + iw * 0.02
        self._osd_play_button.position = (x,
                                          self._osd_status_position[1],
                                          self._osd_status_position[2])

        self._osd_play_button.connect("clicked", self._osd_play_button_clicked)
        
    def _create_osd_status_widget(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        theme = self.frontend.theme
            
        osd_status_bar_bg = theme.get_media("dock_bar_bg")
        osd_status_bar_fg = theme.get_media("dock_bar_fg")
           
        px, py = 0.7, 0.8
        iw, ih = canvas.width, canvas.height
        osd_status_w = iw * px
        osd_status_h = ih * 0.15
        self._osd_status = PlayerOsd(canvas,
                                     pgm.DRAWABLE_FAR,
                                     10000,
                                     osd_status_w, osd_status_h,
                                     osd_status_bar_bg,
                                     osd_status_bar_fg,
                                     self._normal_osd_status_bg)
        self._osd_status.opacity = 0
        self._osd_status.position = self._osd_status_position
        self._update_osd_layout()
        self._osd_status.visible = True        
        self._osd_status.connect("clicked", self._osd_status_clicked)

    def _create_osd_volume_widget(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        iw, ih = canvas.width, canvas.height
        
        trans = common.application.translator
        volume_name = trans.translateTranslatable(T_(N_("Volume: %s%%")),
                                                  self.frontend.languages)

        osd_volume_bg = self.frontend.theme.get_media("vol_dock_background")

        self._osd_volume = VolumeOsd(canvas,
                                     pgm.DRAWABLE_FAR,
                                     volume_name,
                                     iw*0.28, ih*0.1,
                                     osd_volume_bg)
        self._osd_volume.position = (0.15, 0.10, 0)
        self._osd_volume.opacity = 0
        self._osd_volume.visible = True

    def _create_back_widget(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        
        px, py = 0.7, 0.8
        iw, ih = canvas.width, canvas.height
        osd_status_w = iw*px
        osd_status_h = ih*0.15
        back_button_image = self.frontend.theme.get_media("back_button")
        back_size = osd_status_h
        offset = ih * 0.02
        
        self._osd_back_button = Dock(canvas,
                                     pgm.DRAWABLE_FAR,
                                     back_size, back_size,
                                     back_button_image)
        self._osd_back_button.position = (iw - back_size - offset, offset, 0)
        self._osd_back_button.opacity = 0
        self._osd_back_button.visible = True
        self._osd_back_button.connect("clicked", self._osd_back_button_clicked)

    def _create_interaction(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        
        self._interaction = Image()
        self._interaction.position = (0.0, 0.0, 0.0)
        width = canvas.width / 2.0
        height = canvas.height / 2.0
        self._interaction.width = width
        self._interaction.height = height
        self._interaction.x = width / 2.0
        self._interaction.y = height / 2.0

        # downscaling
        scale = pgm.mat4x4_new_identity()
        scale.translate(0.5, 0.5, 0.0)
        factor = 2.0
        scale.scale(factor, factor, 0.0)
        scale.translate(-0.5, -0.5, 0.0)
#        self._interaction.mapping_matrix *= scale

        self._interaction.bg_color = (0, 0, 0, 0)
        self._interaction.opacity = 255
        self._interaction.visible = True
        self._interaction.connect("clicked", self._background_clicked)
        self.group.add(self._interaction)


    def _create_background(self):
        canvas = self.frontend.context.viewport_handle.get_canvas()
        
        self._background = Image()
        self._background.position = (0.0, 0.0, 0.0)
        self._background.size = canvas.size

        self._background.bg_color = (0, 0, 0, 0)
        self._background.opacity = 255
        self._background.visible = True
        self._background.connect("clicked", self._background_clicked)
        self.group.add(self._background)

        # create the output video sink and link it to the player
        video_sink = gst.element_factory_make('pgmimagesink')
        video_sink.set_property('image', self._background)
        self.player.video_sink = video_sink
        
    def _update_osd_layout(self):
        px, py = 0.7, 0.8
        canvas = self.frontend.context.viewport_handle.get_canvas()
        iw, ih = canvas.width, canvas.height
        osd_status_h = ih*0.15
        
        x = self._osd_status_position[0] + (osd_status_h/2.0)
        self._osd_status.position = (x,
                                     self._osd_status_position[1],
                                     self._osd_status_position[2])
        self._osd_status.image_from_path(self._mouse_osd_status_bg)
    

    def uri_changed(self, uri):
        self._background.clear()
        if uri == None:
            label = ''
        else:
            label = uri.label
            if isinstance(label, Translatable):
                translator = common.application.translator
                label = translator.translateTranslatable(label,
                                                         self.frontend.languages)

        self._osd_status.title = label
        super(PlayerView, self).uri_changed(uri)

    def focused_changed(self, new_focused):
        if not new_focused:
            self.osd_hide()
        else:
            if self.controller.state == player.STATES.PLAYING:
                if self.controller.model.media_type == 'audio':
                    self.osd_show()
                    self.group.visible = True
            else:
                self.osd_show(self._osd_fadeout_delay, True)
                
    def state_changed(self, state):
        super(PlayerView, self).state_changed(state)
        self.debug("state changed to: %r", state)
        
        self._update_play_button(state)

        if self.controller.model.uri and self.controller.focused:
            # FIXME: this part is not working in playlist on the first item
            if self.controller.model.media_type == 'audio':
                osd_time = -1
            else:
                osd_time = self._osd_fadeout_delay
                
            if state == player.STATES.PAUSED:
                self.osd_show(osd_time)
                self.group.visible = True
            elif state == player.STATES.PLAYING:
                self.osd_show(osd_time)
            elif state == player.STATES.STOPPED:
                self._background.clear()
            else:
                self.group.visible = True
            # FIXME: this whole refresh_dock looks hackish
            reactor.callLater(0.05, self.refresh_dock)

        elif state == player.STATES.STOPPED:
            self._background.clear()

    def seek_to_changed(self, position):
        super(PlayerView, self).seek_to_changed(position)
        self._osd_status.time = self.player.position / 1000000.00
        if self.controller.model.media_type == 'audio':
            self.osd_show()
        else:
            self.osd_show(self._osd_fadeout_delay)

    def volume_changed(self, value):
        super(PlayerView, self).volume_changed(value)
        self._osd_volume.volume = value * 50.0
        if self.controller.state in (player.STATES.PLAYING,
                                     player.STATES.PAUSED) and \
               self.controller.focused:
            self.osd_show(self._osd_fadeout_delay, False, True)
            
    def pause_requested_changed(self, value):
        # show the dock if the user press OK once
        if self.controller.model.state == player.STATES.PLAYING and value:
            if not self._osd_status.is_visible():
                self.osd_show(self._osd_fadeout_delay, True)
            else:
                self.controller.model.state = player.STATES.PAUSED
                
    def refresh_dock(self):
        # FIXME: a division in a code called every 50 ms is not good
        duration = int(self.player.duration / 1000000)
        self._osd_status.playing_length = duration

        self._osd_status.time = self.player.position / 1000000.0

        if self.controller.state == player.STATES.PLAYING:
            reactor.callLater(0.05, self.refresh_dock)
