# Copyright (C) 2008-2010 Adam Olsen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
#
# The developers of the Exaile media player hereby grant permission
# for non-GPL compatible GStreamer and Exaile plugins to be used and
# distributed together with GStreamer and Exaile. This permission is
# above and beyond the permissions granted by the GPL license by which
# Exaile is covered. If you modify this code, you may extend this
# exception to your version of the code, but you are not obligated to
# do so. If you do not wish to do so, delete this exception statement
# from your version.


from gi.repository import Gst

from xl.providers import ProviderHandler

import logging
logger = logging.getLogger(__name__)


def element_link_many(*elems):
    for i in range(len(elems) - 1):
        e = elems[i]
        n = elems[i + 1]
        e.link(n)

def disable_video_text(bin):
    # From quod libet: disable all video/text decoding in playbin
    GST_PLAY_FLAG_VIDEO = 1 << 0
    GST_PLAY_FLAG_TEXT = 1 << 2
    flags = bin.get_property("flags")
    flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT)
    bin.set_property("flags", flags)


class ElementBin(Gst.Bin):
    """
        A bin for easily containing elements

        elements are added to the elements dictionary in the form of
            elements[position] = element
        where position is a value from 0-100 indicating its position
        in the resulting bin, and element is the Gst.Element itself.

        changes made to elements do not apply until setup_elements()
        is called
    """
    def __init__(self, name=None):
        if name:
            Gst.Bin.__init__(self, name=name)
        else:
            Gst.Bin.__init__(self)
        
        self.elements = {}
        self.added_elems = []
        self.srcpad = None
        self.sinkpad = None
        self.src = None
        self.sink = None
    
    def setup_elements(self):
        '''
            This function should only be called when state is NULL
            
            :returns: True if there are elements, False otherwise
        '''
        name = self.get_name()
        logger.debug("%s: Removing all elements", name)
        
        if len(self.added_elems) > 0:
            for elem in self.added_elems:
                elem.set_state(Gst.State.NULL)
                self.remove(elem)
        
        all_elems = list(self.elements.iteritems())
        all_elems.sort()
        
        elems = []
        for _unused, e in all_elems:
            
            # Don't add empty elements!
            if hasattr(e, 'setup_elements'):
                if not e.setup_elements():
                    continue
                
            elems.append(e)
        
        self.added_elems = elems
        
        if len(elems) == 0:
            return False
        
        for e in elems:
            self.add(e)
            e.sync_state_with_parent()
            logger.debug("%s: Adding %s", name, e.get_name())
        
        element_link_many(*elems)
        
        self.srcpad = elems[-1].get_static_pad("src")
        if self.src is not None:
            self.src.set_target(self.srcpad)
        else:
            self.src = Gst.GhostPad.new('src', self.srcpad)
            self.add_pad(self.src)
            
        self.sinkpad = elems[0].get_static_pad("sink")
        if self.sink is not None:
            self.sink.set_target(self.sinkpad)
        else:
            self.sink = Gst.GhostPad.new('sink', self.sinkpad)
            self.add_pad(self.sink)
            
        return True

class ProviderBin(ElementBin, ProviderHandler):
    """
        A ProviderBin is a Gst.Bin that adds and removes elements from itself
        using the providers system. Providers should be a subclass of
        Gst.Element and provide the following attributes:
            name  - name to use for this element
            index - priority within the pipeline. range [0-100] integer.
                    lower numbers are higher priority. elements must
                    choose a unique number.
    """
    def __init__(self, servicename, name=None):
        """
            :param servicename: the Provider name to listen for
        """
        if name is None:
            name = servicename
        ElementBin.__init__(self, name=name)
        ProviderHandler.__init__(self, servicename)

        self.reset_providers()
    
    def reset_providers(self):
        self.elements = {}
        dups = {}
        for provider in self.get_providers():
            idx = provider.index
            if idx in self.elements:
                dup = dups.setdefault(idx, [self.elements[idx].name])
                dup.append(provider.name)
                while idx in self.elements:
                    idx += 1
            try:
                self.elements[idx] = provider()
            except:
                logger.exception("Could not create %s element for %s.",
                                 provider, self.get_name())
        
        for k, v in dups.iteritems():
            logger.warning("Audio plugins %s are sharing index %s (may have unpredictable output!)",
                            v, k)
        
    def on_provider_added(self, provider):
        self.reset_providers()

    def on_provider_removed(self, provider):
        self.reset_providers()


def parse_stream_tags(track, tag_list):
    """
        Called when a tag is found in a stream.
        
        :type track:    xl.trax.Track
        :type tag_list: Gst.TagList
        
        Gst.TagList guarantees that all strings are either ASCII or UTF8
    """
    
    newsong = False
    
    keep = ['bitrate',
            'duration',
            'track-number',
            'album',
            'artist',
            'genre',
            'comment',
            'title']
    
    # Build a dictionary first
    tags = {}
    for i in xrange(tag_list.n_tags()):
        k = tag_list.nth_tag_name(i)
        if k not in keep:
            continue
        
        values = [tag_list.get_value_index(k, vi) for vi in xrange(tag_list.get_tag_size(k))]
        if isinstance(values[0], str):
            try:
                values = [unicode(v, 'utf-8') for v in values]
            except UnicodeDecodeError:
                logger.debug("Can't decode: " + repr(values))
                continue
        
        tags[k] = values
    
    v = tags.get('bitrate')
    if v:
        track.set_tag_raw('__bitrate', int(v[0]))
        
    v = tags.get('duration')
    if v:
        track.set_tag_raw('__length', float(v[0])/Gst.SECOND)
    
    v = tags.get('track-number')
    if v:
        track.set_tag_raw('tracknumber', v)
    
    v = tags.get('album')
    if v:
        track.set_tag_raw('album', v)
    
    v = tags.get('artist')
    if v:
        track.set_tag_raw('artist', v)
    
    v = tags.get('genre')
    if v:
        track.set_tag_raw('genre', v)   
    
    # if there's a comment, but no album, set album to the comment
    v = tags.get('comment')
    if v and not track.get_tag_raw('album'):
        track.set_tag_raw('album', v)
        
    v = tags.get('title')  
    if v:   
        try:
            if track.get_tag_raw('__rawtitle') != v:
                track.set_tag_raw('__rawtitle', v)
                newsong = True
        except AttributeError:
            track.set_tag_raw('__rawtitle', v)
            newsong = True

        if not track.get_tag_raw('artist'):            
            title_array = v[0].split(' - ', 1)
            if len(title_array) == 1 or \
                    track.get_loc_for_io().lower().endswith(".mp3"):
                track.set_tag_raw('title', v)
            else:
                track.set_tag_raw('artist', [title_array[0]])
                track.set_tag_raw('title', [title_array[1]])
        else:
            track.set_tag_raw('title', v)
    
    return newsong



# vim: et sts=4 sw=4
