# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2007-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>

"""
Provide access to resources served by Youtube over HTTP.
"""

from elisa.core.components.resource_provider import ResourceProvider

from elisa.plugins.http_client.http_client import ElisaAdvancedHttpClient
from elisa.plugins.http_client.extern.client_http import ClientRequest

from twisted.web2 import responsecode
from twisted.web2.stream import BufferedStream

from twisted.internet import defer

from elisa.plugins.base.models.media import PlayableModel, RawDataModel
from elisa.plugins.base.models.image import ImageModel

from elisa.plugins.youtube.models import YoutubeVideoModel, \
                                         YoutubeVideoListModel

from elisa.core.media_uri import MediaUri

from xml.dom import minidom
import re
import string

API_SERVER = 'gdata.youtube.com'
VIDEO_SERVER = 'www.youtube.com'
IMG_SERVER = 'i.ytimg.com'

class YoutubeResourceProvider(ResourceProvider):

    """
    A resource provider that implements the GET method for use on the Youtube
    API (see http://code.google.com/apis/youtube/developers_guide_protocol.html
    for details).

    The GET method allows to retrieve lists of videos using the Youtube API
    feeds, or directly a given video from its Youtube id, or an image thumbnail
    for a video.
    """

    # Queries to the Youtube API
    api_uri = 'http://%s/feeds/api/.*' % API_SERVER
    api_re = re.compile(api_uri)
    # Queries to the video server
    video_uri = 'http://%s/watch\?v=.*' % VIDEO_SERVER
    video_re = re.compile(video_uri)
    # Queries to the image server
    img_uri = 'http://%s/vi/.*\.jpg' % IMG_SERVER
    img_re = re.compile(img_uri)

    supported_uri = api_uri + '|' + video_uri + '|' + img_uri

    _real_uri_re = re.compile(r'var swfArgs = (\{.*\});')

    def initialize(self):
        super(YoutubeResourceProvider, self).initialize()
        self._api_client = ElisaAdvancedHttpClient(host=API_SERVER)
        self._video_client = ElisaAdvancedHttpClient(host=VIDEO_SERVER)
        self._img_client = ElisaAdvancedHttpClient(host=IMG_SERVER)
        return defer.succeed(self)

    def clean(self):
        # Close all open HTTP connections
        dfr = defer.DeferredList([self._api_client.close(),
                                   self._video_client.close(),
                                   self._img_client.close()],
                                  consumeErrors=True)

        def parent_clean(result):
            return super(YoutubeResourceProvider, self).clean()

        dfr.addCallback(parent_clean)
        return dfr

    def get(self, uri, context_model=None):
        """
        GET request to the Youtube servers.

        It accepts the following types of URLs:

          - http://gdata.youtube.com/feeds/api/.* : query to the video feeds of
            the Youtube API, returns a list of videos
            (L{elisa.plugins.youtube.models.YoutubeVideoListModel}). For a
            complete list of available feeds, see
            http://code.google.com/apis/youtube/developers_guide_protocol.html#Standard_feeds

          - http://www.youtube.com/watch?v=.* : query to the Youtube server for
            a given video, returns the playable URL of the SWF video file
            (L{elisa.plugins.base.models.media.PlayableModel}).

          - http://img.youtube.com/vi/.*.jpg : query to the Youtube image
            server for a video thumbnail, returns the image data
            (L{elisa.plugins.base.models.media.RawDataModel}).

        The contextual model is used when querying for the playable URL of a
        video, if present it should be a
        L{elisa.plugins.youtube.models.YoutubeVideoModel}. Its
        C{playable_model} will then be set to the retrieved model.
        """
        url = str(uri)

        # Select the correct HTTP client to target
        if self.api_re.match(url) is not None:
            http_client = self._api_client
            result_model = YoutubeVideoListModel()
        elif self.video_re.match(url) is not None:
            http_client = self._video_client
            result_model = PlayableModel()
        elif self.img_re.match(url) is not None:
            http_client = self._img_client
            result_model = RawDataModel()

        def response_read(response, model):

            # Parse the response and populate the model accordingly
            if isinstance(model, YoutubeVideoListModel):
                # Populate a list of videos
                dom = minidom.parseString(response)
                entries = dom.getElementsByTagName('entry')
                for entry in entries:
                    video = YoutubeVideoModel()
                    video.youtube_id = entry.getElementsByTagName('id')[0].firstChild.nodeValue
                    # Assume the title is always present
                    video.title = entry.getElementsByTagName('media:title')[0].firstChild.nodeValue
                    # The description may not be present
                    try:
                        if entry.getElementsByTagName('media:description')[0].firstChild != None:
                            video.description = entry.getElementsByTagName('media:description')[0].firstChild.nodeValue
                        else:
                            video.description = ""
                    except IndexError, AttributeError:
                        pass
                    # The keywords list may not be present
                    try:
                        keywords_node = entry.getElementsByTagName('media:keywords')[0].firstChild
                        if keywords_node != None:
                            keywords = keywords_node.nodeValue
                            video.keywords = map(string.strip, keywords.split(','))
                        else:
                            video.keywords = []
                    except IndexError, AttributeError:
                        pass
                    # The player URI should always be present
                    video.playable_uri = MediaUri(entry.getElementsByTagName('media:player')[0].attributes['url'].nodeValue)
                    thumbnails = entry.getElementsByTagName('media:thumbnail')
                    for thumbnail in thumbnails:
                        t = ImageModel()
                        t.references.append(MediaUri(thumbnail.attributes['url'].nodeValue))
                        video.thumbnails.append(t)
                    model.videos.append(video)

            elif isinstance(model, PlayableModel):
                # Retrieve the real URI of the SWF video file for a video
                swfargs = self._real_uri_re.search(response).groups()[0]
                replaces = {'null': 'None', 'true': 'True', 'false': 'False'}
                for key, value in replaces.iteritems():
                    swfargs = swfargs.replace(key, value)
                swfargs = eval(swfargs)
                playable_uri = 'http://%s/get_video?video_id=%s&t=%s' % \
                               (VIDEO_SERVER, swfargs['video_id'], swfargs['t'])
                playable_uri += '&fmt=18' # H264 encoded video, better quality
                model.uri = MediaUri(playable_uri)
                if context_model and isinstance(context_model, YoutubeVideoModel):
                    model.title = context_model.title
                    context_model.playable_model = model

            elif isinstance(model, RawDataModel):
                # Image raw data
                model.data = response
                model.size = len(response)

            return model

        def request_done(response, model):
            if response.code == responsecode.OK:
                # Read the response stream
                read_dfr = BufferedStream(response.stream).readExactly()
                read_dfr.addCallback(response_read, model)
                return read_dfr
            elif response.code == responsecode.NOT_FOUND:
                # 404 error code: resource not found
                return defer.fail(IOError('Resource not found at %s' % url))
            else:
                # Other HTTP response code
                return defer.fail(Exception('Received an %d HTTP response code' % response.code))

        resource = str('%s?%s' % (uri.path, uri.get_params_string()))
        request = ClientRequest('GET', resource, None, None)
        request_dfr = http_client.request_full(request)
        request_dfr.addCallback(request_done, result_model)
        return (result_model, request_dfr)

    def post(self, uri, **parameters):
        """
        POST request to the Youtube servers.

        DOCME (not implemented yet)
        """
        return defer.fail(NotImplementedError())
