# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 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 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 Amazon over HTTP.
"""

from elisa.core.components.resource_provider import ResourceProvider
from elisa.core.media_uri import MediaUri

from elisa.plugins.http_client.http_client import ElisaAdvancedHttpClient
from twisted.web2 import responsecode
from twisted.web2.stream import BufferedStream

from elisa.plugins.amazon.models import AmazonItemModel, AmazonItemListModel

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

from twisted.internet import defer, task

import re
from xml.dom import minidom

AWS_SERVER = 'ecs.amazonaws.com'
IMG_SERVER = 'images.amazon.com'


class AmazonResourceProvider(ResourceProvider):

    """
    A resource provider that implements the GET method for use on the Amazon
    Web Services API (see http://aws.amazon.com/ for details).

    The GET method allows to retrieve lists of items containing the results to
    a search query to the Amazon Web Services, or directly an image from the
    Amazon image server.
    """

    # Queries to the Amazon Web Services API
    aws_uri = 'http://' + AWS_SERVER + '/onca/xml\?.*'
    aws_re = re.compile(aws_uri)
    # Queries to the Amazon image server (for album covers)
    img_uri = 'http://' + IMG_SERVER + '/images/.*'
    img_re = re.compile(img_uri)

    supported_uri = aws_uri + '|' + img_uri

    def initialize(self):
        dfr = super(AmazonResourceProvider, self).initialize()
        self._aws_client = ElisaAdvancedHttpClient(host=AWS_SERVER)
        self._img_client = ElisaAdvancedHttpClient(host=IMG_SERVER)
        return dfr

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

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

        dfr.addCallback(parent_clean)
        return dfr

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

        It accepts the following types of URLs:

          - http://ecs.amazonaws.com/onca/xml?.* : query to the Amazon Web
            Services API, returns a list of items
            (L{elisa.plugins.amazon.models.AmazonItemListModel}).

          - http://ecx.images-amazon.com/images/.* : query to the Amazon image
            server for e.g. an audio album cover, returns the image data
            (L{elisa.plugins.base.models.media.RawDataModel}).

        The contextual model is currently not used.
        """
        url = str(uri)

        # Select the correct HTTP client to target
        if self.aws_re.match(url) is not None:
            http_client = self._aws_client
            result_model = AmazonItemListModel()
        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, AmazonItemListModel):
                dom = minidom.parseString(response)

                if dom.firstChild.tagName == 'Errors':
                    error = dom.getElementsByTagName('Error')[0]
                    code = error.getElementsByTagName('Code')[0].firstChild.nodeValue
                    msg = error.getElementsByTagName('Message')[0].firstChild.nodeValue
                    raise ValueError('%s: %s' % (code, msg))

                items = dom.getElementsByTagName('Items')
                if not items:
                    return model
                item_nodes = items[0].getElementsByTagName('Item')

                def iter_nodes(item_nodes, model):
                    for item_node in item_nodes:
                        item = AmazonItemModel()
                        item.asin = item_node.getElementsByTagName('ASIN')[0].firstChild.nodeValue
                        # Let's see if the item contains album covers
                        small_image = item_node.getElementsByTagName('SmallImage')
                        medium_image = item_node.getElementsByTagName('MediumImage')
                        large_image = item_node.getElementsByTagName('LargeImage')
                        if small_image or medium_image or large_image:
                            # It does
                            item.image = ImageModel()
                            if small_image:
                                small_uri = small_image[0].getElementsByTagName('URL')[0].firstChild.nodeValue
                                item.image.references.append(MediaUri(small_uri))
                            if medium_image:
                                medium_uri = medium_image[0].getElementsByTagName('URL')[0].firstChild.nodeValue
                                item.image.references.append(MediaUri(medium_uri))
                            if large_image:
                                large_uri = large_image[0].getElementsByTagName('URL')[0].firstChild.nodeValue
                                item.image.references.append(MediaUri(large_uri))
                        model.items.append(item)
                        yield item

                return task.coiterate(iter_nodes(item_nodes, model)).addCallback(lambda x: model)

            elif isinstance(model, RawDataModel):
                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))

        request_dfr = http_client.request(url)
        request_dfr.addCallback(request_done, result_model)
        return (result_model, request_dfr)
