# -*- 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: Olivier Tilloy <olivier@fluendo.com>

"""
Common models related to media.
"""

from elisa.core.components.model import Model
from elisa.core.utils import defer, notifying_list, caching

import gobject


class PlayableModel(Model):

    """
    A playable model contains the real URI of a playable media, that is,
    typically, a URI directly usable by gstreamer to play streamed data.

    It also potentially contains a title for the media. If the title
    is not set, any read access to that property will return the
    filename of the uri instance attribute.

    @ivar uri:   the real URI of the playable media
    @type uri:   L{elisa.core.media_uri.MediaUri}
    @ivar title: the title of the playable media
    @type title: C{unicode}
    @ivar allow_pause: whether the player can pause the media playback or not
    @type allow_pause: C{bool}
    @ivar allow_seek: whether the player can seek inside the media playback or not
    @type allow_seek: C{bool}
    @ivar source_properties: the properties of the source element of the 
                       GStreamer pipeline for this playable media
    @type source_properties: C{dict} of C{unicode} keys and values
    """

    def __init__(self):
        """
        Constructor. Initialize all the fields.
        """
        super(PlayableModel, self).__init__()
        self.uri = None
        self.title = None
        self.allow_pause = True
        self.allow_seek = True
        self.source_properties = {} 

    def __eq__(self, other):
        """
        Compare two playable models.

        Two playable models are considered equal if and only if their URIs are
        equal.

        @param other: another playable model
        @type other:  L{elisa.plugins.base.models.media.PlayableModel}

        @return:      C{True} if the two models are equal, C{False} otherwise
        @rtype:       C{bool}
        """
        return (self.uri == other.uri)

    def title_set(self, title):
        self._title = title

    def titlet_get(self):
        title = self._title
        if (not title) and self.uri:
            title = self.uri.filename
        return title

    title = property(fset=title_set,fget=titlet_get)


class PlaylistModel(Model, notifying_list.List):
    """
    A playlist model is a list of models that can be played by
    a player. The models stored in this list *must* implement the
    get_playable_model method, like
    L{elisa.plugins.base.models.audio.TrackModel} and
    L{elisa.plugins.base.models.video.VideoModel}.

    @ivar allow_previous: if it is possible to set as current the previous track
                          in the playlist relatively to the current one by
                          calling the method 'previous_track'
    @type allow_previous: C{bool}
    @ivar allow_next: if it is possible to set as current the next track
                      in the playlist relatively to the current one by
                      calling the method 'next_track'
    @type allow_next: C{bool}
    @ivar allow_jump: if it is possible to set as current any track knowing its
                      index in the playlist by calling the method
                      'set_current_index'
    @type allow_jump: C{bool}
    @ivar current_playable_model: playable model corresponding to the current
                        track in the playlist; it differs from the model
                        stored in the playlist because it is not the model
                        itself but only the shell containing the necessary
                        playback information
    @type current_playable_model: L{elisa.plugins.base.models.media.PlayableModel}
    @ivar current_model: a model (video, track etc.) corresponding to the
                        current track in the playlist; it points to one of the
                        models in the playlist itself
    @type current_model: C{elisa.core.components.model.Model}
    @ivar current_index: index in the playlist of the current model being played
    @type current_index: C{int}
    """
    gobject.signal_new('current-model-changed',
                       notifying_list.Notifier,
                       gobject.SIGNAL_RUN_LAST,
                       gobject.TYPE_NONE,
                       (gobject.TYPE_PYOBJECT,))
    gobject.signal_new('current-playable-model-changed',
                       notifying_list.Notifier,
                       gobject.SIGNAL_RUN_LAST,
                       gobject.TYPE_NONE,
                       (gobject.TYPE_PYOBJECT,))

    def __init__(self):
        """
        Constructor. Initialize all the fields.
        """
        super(PlaylistModel, self).__init__()
        self.allow_previous = True
        self.allow_next = True
        self.allow_jump = True
        self.current_model = None
        self.current_playable_model = None
        self.current_index = -1

    def previous_track(self):
        """
        Decrement L{current_index} of the playlist and return the
        PlayableModel located at the new position in the playlist.

        @rtype: L{twisted.internet.defer.Deferred}
        """
        if self.current_index > 0:
            dfr = self.set_current_index(self.current_index-1)
        else:
            dfr = defer.fail(Exception("Beginning of playlist reached"))
        return dfr

    def next_track(self):
        """
        Increment L{current_index} of the playlist and return the
        PlayableModel located at the new position in the playlist.

        @rtype: L{twisted.internet.defer.Deferred}
        """
        if self.current_index < len(self)-1:
            dfr = self.set_current_index(self.current_index+1)
        else:
            dfr = defer.fail(Exception("End of playlist reached"))
        return dfr

    def set_current_index(self, index):
        """
        Set L{current_index} of the playlist and return the
        PlayableModel located at the new position in the playlist.

        @rtype: L{twisted.internet.defer.Deferred}
        """
        def got_playable_model(model):
            self.current_playable_model = model
            self.notifier.emit('current-playable-model-changed', model)
            return model

        self.current_index = index
        self.current_model = self[index]
        self.notifier.emit('current-model-changed', self[index])

        dfr = self._retrieve_playable_model_at(index)
        dfr.addCallback(got_playable_model)
        return dfr

    def _retrieve_playable_model_at(self, index):
        track = self[index]
        if not isinstance(track, PlayableModel):
            dfr = track.get_playable_model()
        else:
            dfr = defer.succeed(track)
        return dfr


class RawDataModel(Model):

    """
    A raw data model contains raw data from a media file (can be binary, text,
    etc...).

    @ivar data: the raw data
    @type data: C{str}
    @ivar size: the total size of the data
    @type size: C{int}
    """

    def __init__(self):
        """
        Constructor. Initialize all the fields.
        """
        super(RawDataModel, self).__init__()
        self.data = None
        self.size = None


# TODO: subclass the RawDataModel to have a model that reads its data
#       asynchronously.
