# -*- 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: Guido Amoruso <guidonte@fluendo.com>

from twisted.internet import task

from elisa.core.media_uri import MediaUri
from elisa.core.utils import defer, caching
from elisa.core.utils.internet import get_page
from elisa.core.components.model import Model
from elisa.core import log

from elisa.plugins.base.models.media import PlayableModel
from elisa.plugins.base.models.audio import TrackModel
from elisa.plugins.base.models.video import VideoModel
from elisa.plugins.base.models.image import ImageModel

from elisa.extern.coherence import et

import re


class RssImageModel(ImageModel):

    def get_cached_data_path(self, reference_num):
        # Overridden from mother class. We don't want to use the resource
        # manager to get the distant image as we are not sure we have a resource
        # provider for it.
        uri = self.references[reference_num]
        cache_path = caching.get_pictures_cache_path()
        return caching.get_and_cache_to_file(uri, cache_path)


def _get_text(element, *paths):
    """Get the text of the first found path of element."""
    for path in paths:
        el = element.find(path)
        if el is not None:
            return el.text or ''
    return ''

def _get_attr(element, *specs):
    """Get the attribute of the first found path of element."""
    for (path, attr) in specs:
        el = element.find(path)
        if el is not None:
            attr = el.attrib.get(attr)
            if attr:
                return attr
    return ''


known_specs = {'media' : 'http://search.yahoo.com/mrss/',
               'itunes' : 'http://www.itunes.com/dtds/podcast-1.0.dtd',
               'atom' : 'http://www.w3.org/2005/Atom'}


class RssFeedModel(Model):
    """
    An RSS 2.0 feed.

    @ivar uri: uri of the feed.
    @type uri: L{elisa.core.media_uri.MediaUri}
    @ivar name: user defined name of the feed
    @type name: C{unicode} or C{str}
    @ivar items: items of the feed
    @type items: C{list} of L{elisa.plugins.rss.RssItemModel}
    """
    def __init__(self):
        super(Model, self).__init__()

        self.uri = None
        self.name = None
        self.items = []

    def _get(self, uri):
        # This method is meant to be monkey-patched in unit tests.
        return get_page(str(uri))

    def load(self):
        """Fetch and parse the RSS feed.

        @return: a deferred telling whether the loading went well
        @rtype: L{elisa.core.utils.defer.Deferred}
        """

        def got_page(response):
            xml = et.parse_xml(response)
            return self._parse_data(xml)

        # Empty the list of items to avoid duplicates.
        self.items = []

        dfr = self._get(self.uri)
        dfr.addCallback(got_page)
        return dfr

    def _parse_data(self, xml):
        """Parse the RSS feed."""

        def iterate(root):
            for element in root.findall('.//item'):
                item = RssItemModel()
                self._fill_item(item, element)
                self.fill_item_hook(item, element)
                yield element

        root = xml.getroot()

        self.title = _get_text(root, './channel/title')
        self.description = _get_text(root, './channel/description')

        return task.coiterate(iterate(root))

    def _fill_item(self, item, element):
        """Parse one RSS item."""

        item.title = _get_text(element, './title').strip()

        item.description = _get_text(element,
            './description',
            './{%s}description' % known_specs['media'],
            './{%s}summary' % known_specs['itunes'],
        ).strip()

        item.type = _get_attr(element,
            ('./enclosure', 'type'),
            ('./{%s}content' % known_specs['media'], 'medium'),
            ('./{%s}content' % known_specs['media'], 'type'),
        )

        thumbnail_uri = _get_attr(element,
            ('./{%s}thumbnail' % known_specs['media'], 'url'),
            ('./{%s}content/{%s}thumbnail' % (known_specs['media'], known_specs['media']), 'url'),
        )

        # We've noticed that some websites embed the thumbnail in the HTML
        # snippet of the description... let's scrape it!
        if not thumbnail_uri and item.description.strip().startswith('<'):
             # the "src" attribute of the <img/> tag
             pattern = re.compile('src="(?P<url>.*?)"')
             match = pattern.search(item.description)
             if match:
                  thumbnail_uri = match.group('url')

        if thumbnail_uri:
            thumbnail = ImageModel()
            thumbnail.references.append(MediaUri(thumbnail_uri))
            item.thumbnails.append(thumbnail)

        uri = _get_attr(element,
            ('./enclosure', 'url'),
            ('./{%s}content' % (known_specs['media']), 'url'),
        )

        if uri != '':
            media_uri = MediaUri(uri)
            if item.type.startswith('image'):
                image_model = RssImageModel()
                # this is not present in ImageModel, but - if I'm not wrong -
                # is needed in the controller
                image_model.title = item.title
                image_model.can_rotate = False
                image_model.references.append(media_uri)

                item.image_model = image_model
            elif item.type.startswith('video'):
                video_model = RssVideoItem()
                playable_model = PlayableModel()
                playable_model.title = item.title
                playable_model.uri = media_uri
                video_model.playable_model = playable_model
                
                item.video_model = video_model
            elif item.type.startswith('audio'):
                track_model = RssTrackItem()
                playable_model = PlayableModel()
                playable_model.title = item.title
                playable_model.uri = media_uri
                track_model.playable_model = playable_model
                
                item.track_model = track_model
            else:
                log.warning(log.un_camelify(self.__class__.__name__),
                            "Unknown media type for the RSS item")

        self.items.append(item)

    def fill_item_hook(self, item, element):
        """Hook for implementing further parsing of one item.

        @param item: the rss item to fill
        @type item: L{RssItemModel}
        @param element: xml 'item' element to parse
        @type element: ElementTree
        """
        pass


class RssVideoItem(VideoModel):
    """
    RSS video item model.
    """
    playable_model = None
     
    def get_playable_model(self):
        return defer.succeed(self.playable_model)

    def _get_title(self):
        return self.playable_model.title

    def _set_title(self, value):
        pass

    title = property(fget=_get_title, fset=_set_title)

class RssTrackItem(TrackModel):
    """
    RSS audio track item model.
    """
    playable_model = None
     
    def get_playable_model(self):
        return defer.succeed(self.playable_model)


class RssItemModel(Model):

    def __init__(self):
        """RSS item model, basic fields.

        When subclassing RssFeedModel and implementing fill_item_hook(), you
        should just add slots to the RssItemModel objects, without bothering
        subclassing this model too.

        Three fields are special: 'video_model', 'track_model' and 
        'image_model'. Only one should be filled with a VideoModel, TrackModel 
        or ImageModel, depending on thetype of item (audio, video or image).

        @ivar title: title
        @type title: C{str}
        @ivar description: description
        @type description: C{str}
        @ivar thumbnails: thumbnails
        @type thumbnails: C{list}
        @ivar type: type of the item (image, video, etc.) or mimetype
        @type type: C{str}
        @ivar image_model: image model if appropriate for the item
        @type image_model: L{elisa.plugins.base.model.image.ImageModel}
        @ivar video_model: video model if appropriate for the item
        @type video_model: L{elisa.plugins.rss.models.RssVideoItem}
        @ivar track_model: track model if appropriate for the item
        @type track_model: L{elisa.plugins.rss.models.RssTrackItem}
        """
        super(RssItemModel, self).__init__()

        self.title = None
        self.description = None
        self.thumbnails = []
        self.type = ''

        self.video_model = None
        self.track_model = None
        self.image_model = None
