# -*- 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__ = 'Benjamin Kampmann <benjamin@fluendo.com>'

from elisa.core.component import Component

from elisa.core import common

from elisa.core.player import PlayerPlaying, PlayerStopping, \
                              PlayerPausing, PlayerLoading, \
                              STATES

from twisted.internet import reactor

import gst

class PlayerEngine(Component):
    """
    A PlayerEngine provides various media playback related functionalities. It
    declares the uri schemes it supports and will be automatically instantiated by
    the L{elisa.core.player_engine_registry.PlayerEngineRegistry} if needed.

    Messages listed at L{elisa.core.player} have to be sent by the engine at
    appropriate times.
        
    @cvar uri_schemes:      the uri-schemes this engine supports associated
                            with their ranking value between 0 (highest rank)
                            and 255 (lowest rank)
    @type uri_schemes:      dict

    @ivar _pipeline:        the internally used pipeline
    @type _pipeline:        L{gst.Pipeline}

    @ivar video_sink:       the videosink of this player engine
    @type video_sink:       L{gst.BaseSink}

    @ivar audio_sink:       the audiosink of this player engine
    @type audio_sink:       L{gst.BaseSink}

    @ivar visualisation:    the visualisation element for the player engine
    @type visualisation:    L{gst.Element}

    @ivar volume:           a value between 0 and 10
    @type volume:           float

    @ivar position:         the position we are currently playing in
                            nanoseconds
    @type position:         float

    @ivar duration:         (read-only) the total length of the loaded uri in
                            nanoseconds
    @type duration:         float

    @ivar speed:            The speed of the current playback:
                                - Normal playback is 1.0
                                - a positive value means forward
                                - a negative one backward
                                - a value bigger (+/-) 1.0 means faster
                                - a value smaller (+/-) 1.0 means slower
                                - the value 0.0 (equivalent to pause) is not 
                                  allowed
    @type speed:            float

    @ivar state:            (read-only) The current state.
    @type state:            L{elisa.core.player.STATES}

    @ivar uri:              (write-only) change the engine to be able to play
                            the set uri.
    @type uri:              L{elisa.core.media_uri.Mediauri}
                            

    @ivar message_sender:   who is the sender of messages (per default it is
                            self)
    @type message_sender:   instance
    """

    uri_schemes = {}

    def __init__(self):
        Component.__init__(self)
        self._pipeline = None
        self._state = STATES.STOPPED
        self._speed = 1.0
        self.message_sender = self
        self._current_uri = None


    # Main Player Functionality

    def play(self, trigger_message=True):
        """
        Play the media. If trigger_message is set to True, this triggers first
        the message L{elisa.core.player.PlayerLoading} message and if the
        playback is really starting, it triggers
        L{elisa.core.player.PlayerPlaying}. Otherwise it does not trigger any
        messages.

        @param trigger_message: should the player trigger messages here
        @type trigger_message:  bool
        """
        if not self._pipeline:
            return

        if self.state == STATES.STOPPED:
            self._state = STATES.LOADING

            if trigger_message:
                self.info("Loading %r", str(self._current_uri))
                self._send_msg(PlayerLoading())

            self._pipeline_set_state(gst.STATE_PLAYING)

        elif self.state == STATES.PAUSED:
            self._state = STATES.PLAYING

            if trigger_message:
                self._send_msg(PlayerPlaying()) 

            self._pipeline_set_state(gst.STATE_PLAYING)



    def pause(self, trigger_message=True):
        """
        Pause the playback. If trigger_message is set to True, this triggers
        the L{elisa.core.player.PlayerPausing} message.

        @param trigger_message: should the player trigger a message here
        @type trigger_message:  bool
        """
        if self._pipeline:
            if self.state == STATES.PLAYING:
                self.info("Pausing Player")
                self._state = STATES.PAUSED
                if trigger_message:
                    self._send_msg(PlayerPausing())
                self._pipeline_set_state(gst.STATE_PAUSED)


    def stop(self, trigger_message=True):
        """
        Stop the playback. 
        If trigger_message is set, this method triggers the
        L{elisa.core.player.PlayerStopping} message.

        @param trigger_message: should the player trigger a message here
        @type trigger_message:  bool
        """
        if self._pipeline:
            self.info("Stopping Player")
            if self.state != STATES.STOPPED:
                self._state = STATES.STOPPED
                self._pipeline_set_state(gst.STATE_READY)
                if trigger_message:
                    self._send_msg(PlayerStopping())

    # Volume

    def volume__set(self, volume):
        pass

    def volume__get(self):
        pass

    # For URI Support

    def uri__set(self, uri):
        pass

    # States and Attributes of the player

    def position__get(self):
        if self.state not in (STATES.PLAYING, STATES.PAUSED):
            return -1

        try:
            position, format = self._pipeline.query_position(gst.FORMAT_TIME)
            return position
        except:
            return -1

    def position__set(self, position):
        if position >= 0 and position <= self.duration:
            self.debug("Position set to %s" % position)
            self._seek_to_location(position)

    def state__get(self):
        return self._state

    def duration__get(self):
        if self.state not in (STATES.PLAYING, STATES.PAUSED):
            return -1
        try:
            duration, format = self._pipeline.query_duration(gst.FORMAT_TIME)
            return duration
        except:
            return -1

    def speed__get(self):
        return self._speed

    def speed_set(self, speed):
        self._speed = speed
        self._seek_to_location(-1)

    # Video Sink

    def video_sink__get(self):
        pass

    def video_sink__set(self, sink):
        pass

    # Audio Sink

    def audio_sink__get(self):
        pass

    def audio_sink__set(self, sink):
        pass

    # Visualisation Plugin

    def visualisation__get(self):
        pass

    def visualisation__set(self, gst_element):
        pass

    # Internal Methods for easier usage

    def _send_msg(self, message):
        """
        Makes it easier to send a message for the developers...
        @param message: the message you want to send. Normally one of
                        L{elisa.core.player.PlayerLoading}, 
                        L{elisa.core.player.PlayerPlaying},
                        L{elisa.core.player.PlayerPausing} or 
                        L{elisa.core.player.PlayerStopping}
        @type message:  L{elisa.base_components.message.Message}
        """
        self.debug("Sending message %r" % message)

        common.application.bus.send_message(message,
                                            sender = self.message_sender)

    def _seek_to_location(self, location):
        """
        seek to the given location with self.speed
        """

        if location == gst.CLOCK_TIME_NONE or not self._pipeline:
            return

        human_loc = gst.TIME_ARGS(location)
        self.info("Seeking to location : %s" % human_loc)
       
        if location < 0:
            speed = self.speed
            if speed > 0:
                start = self.position
                end = self.duration
            else:
                end = self.position
                start = 0
        else:
            speed = 1.0
            if location < self.position:
                start = location
                end = self.duration
            else:
                start = location
                end = self.duration

        event = self._pipeline.seek(speed, gst.FORMAT_TIME,
                                gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
                                gst.SEEK_TYPE_SET, start,
                                gst.SEEK_TYPE_SET, end)

    def _pipeline_set_state(self, state):
        """
        Set the state of the pipeline asynchronisly
        """
        # FIXME: Because of a bug in the gnomevfs gstreamer element
        # set_state might block
        # This workaround should be removed as soon as possible.
        if not self._pipeline:
            return
        reactor.callInThread(self._pipeline.set_state, state)


