# -*- 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>

"""
Unit tests for the Flickr resource provider.
"""

from twisted.trial.unittest import TestCase
from twisted.internet import defer

from elisa.core.media_uri import MediaUri
from elisa.core.utils.misc import run_functional_tests_check

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

from elisa.plugins.flickr.models import FlickrResponseModel, \
                                        FlickrPhotoModel, FlickrTagModel

import elisa.plugins.flickr.flickr_api as flickr
from elisa.plugins.flickr.resource_provider import FlickrResourceProvider

from elisa.core.resource_manager import NoMatchingResourceProvider

import os.path
import binascii

from xml.dom import minidom


class TestFlickrResourceProvider(TestCase):

    """
    This test case tests the FlickrResourceProvider class.
    """

    token = "FAKE TOKEN"

    def get_cached_token(self):
        return self.token

    def setUp(self):
        # patch get_cached_token
        self._get_cached_token = flickr.get_cached_token
        flickr.get_cached_token = self.get_cached_token

        self.provider = FlickrResourceProvider()
        return self.provider.initialize()

    def tearDown(self):
        flickr.get_cached_token = self._get_cached_token
        return self.provider.clean()

    def test_invalid_get_uri(self):
        """
        Test an invalid URI passed to the get method.
        """
        uri = 'http://%s/services/upload' % flickr.API_SERVER
        result_model, request_defer = self.provider.get(uri)
        self.failUnlessFailure(request_defer, NoMatchingResourceProvider)
        return request_defer

    def test_invalid_post_uri(self):
        """
        Test an invalid URI passed to the post method.
        """
        uri = 'http://%s/services/rest/' % flickr.API_SERVER
        request_defer = self.provider.post(uri, {}, None)
        self.failUnlessFailure(request_defer, NoMatchingResourceProvider)
        return request_defer

    def test_invalid_post_uri_2(self):
        """
        Test an invalid URI passed to the post method.
        """
        uri = 'http://%s/services/crap/' % flickr.API_SERVER
        request_defer = self.provider.post(uri, {}, None)
        self.failUnlessFailure(request_defer, NoMatchingResourceProvider)
        return request_defer

    def test_api_call_echo(self):
        """
        Test a simple API call.
        """
        run_functional_tests_check()
        method = 'flickr.test.echo'
        uri = flickr.generate_call_uri(method=method)

        def request_done(result_model):
            self.failUnless(isinstance(result_model, FlickrResponseModel))
            self.failUnless(hasattr(result_model, 'data'))
            dom = minidom.parseString(result_model.data)
            rsp = dom.firstChild
            self.failUnlessEqual(rsp.nodeName, 'rsp')
            self.failUnlessEqual(rsp.attributes.getNamedItem('stat').nodeValue, 'ok')
            self.failUnlessEqual(rsp.getElementsByTagName('method')[0].firstChild.nodeValue, method)
            self.failUnlessEqual(rsp.getElementsByTagName('api_key')[0].firstChild.nodeValue, flickr._key)
            return result_model

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(request_done)
        return request_defer

    def test_api_call_no_key(self):
        """
        Test a simple API call without providing an API key.
        """
        run_functional_tests_check()
        uri = 'http://%s/services/rest/?method=flickr.test.echo' % flickr.API_SERVER
        result_model, request_defer = self.provider.get(uri)
        self.failUnlessFailure(request_defer, ValueError)
        return request_defer

    def test_api_call_invalid_key(self):
        """
        Test a simple API call providing an invalid API key.
        """
        key = flickr._key
        flickr._key = 'a' * len(key)
        uri = flickr.generate_call_uri(method='flickr.test.echo')
        flickr._key = key
        result_model, request_defer = self.provider.get(uri)
        self.failUnlessFailure(request_defer, ValueError)
        return request_defer

    def test_fill_response_model_1(self):
        """
        Test that the response model is correctly filled when the response to
        the query is a list of photos.
        """
        run_functional_tests_check()
        user_id = '80651083@N00'
        uri = flickr.generate_call_uri(method='flickr.people.getPublicPhotos',
                                       arguments={'user_id': user_id})

        def got_response(response):
            self.failUnless(isinstance(response, FlickrResponseModel))
            self.failUnless(hasattr(response, 'photos'))
            self.failUnless(reduce(lambda x, y: x and isinstance(y, FlickrPhotoModel), response.photos))
            return response

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(got_response)
        return request_defer

    def test_fill_response_model_2(self):
        """
        Test that the response model is correctly filled when the response to
        the query is a photo.
        """
        run_functional_tests_check()
        photo_id = '2566013012'
        uri = flickr.generate_call_uri(method='flickr.photos.getInfo',
                                       arguments={'photo_id': photo_id})

        def got_response(response):
            self.failUnless(isinstance(response, FlickrResponseModel))
            self.failUnless(hasattr(response, 'photo'))
            self.failUnless(isinstance(response.photo, FlickrPhotoModel))
            self.failUnlessEqual(response.photo.flickr_id, photo_id)
            return response

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(got_response)
        return request_defer

    def test_fill_response_model_3(self):
        """
        Test that the response model is correctly filled when the response to
        the query is a list of tags.
        """
        run_functional_tests_check()
        uri = flickr.generate_call_uri(method='flickr.tags.getHotList')

        def got_response(response):
            self.failUnless(isinstance(response, FlickrResponseModel))
            self.failUnless(hasattr(response, 'tags'))
            self.failUnless(reduce(lambda x, y: x and isinstance(y, FlickrTagModel), response.tags))
            return response

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(got_response)
        return request_defer

    def test_fill_response_model_4(self):
        """
        Test that the response model is correctly filled when the response to
        the query is a list of sizes for a photo.
        """
        run_functional_tests_check()
        photo_id = '2566013012'
        uri = flickr.generate_call_uri(method='flickr.photos.getSizes',
                                       arguments={'photo_id': photo_id})

        def got_response(response):
            self.failUnless(isinstance(response, FlickrResponseModel))
            self.failUnless(hasattr(response, 'sizes'))
            self.failUnless(isinstance(response.sizes, ImageModel))
            self.failUnlessEqual(len(response.sizes.references), 3)
            return response

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(got_response)
        return request_defer

    def test_image_retrieval(self):
        """
        Test retrieving an image from its complete URL.
        """
        run_functional_tests_check()
        uri = 'http://farm4.%s/3051/2537816751_68be501931_m.jpg' % flickr.IMG_SERVER
        image_size = 15246

        def request_done(result_model):
            self.failUnless(isinstance(result_model, RawDataModel))
            self.assertEqual(result_model.size, image_size)
            header = 'ffd8' + 'ffe0' # SOI marker + JFIF marker
            self.assertEqual(binascii.hexlify(result_model.data[:4]), header)
            return result_model

        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(request_done)
        return request_defer

    def test_invalid_image_retrieval(self):
        """
        Test retrieving an image that does not exist.
        """
        run_functional_tests_check()
        uri = 'http://farm1.%s/crap.jpg' % flickr.IMG_SERVER

        result_model, request_defer = self.provider.get(uri)
        self.failUnlessFailure(request_defer, IOError)
        return request_defer

    def test_multiple_images_retrieval(self):
        """
        Test concurrent retrieval of multiple images from the same farm.
        """
        run_functional_tests_check()
        first_uri = 'http://farm3.%s/2374/2541399644_f24a624438_m_d.jpg' % flickr.IMG_SERVER
        second_uri = 'http://farm3.%s/2169/2543964231_ed8ab99ae8_m_d.jpg' % flickr.IMG_SERVER

        def request_done(result_model):
            self.failUnless(hasattr(result_model, 'data'))
            header = 'ffd8' + 'ffe0' # SOI marker + JFIF marker
            self.assertEqual(binascii.hexlify(result_model.data[:4]), header)
            return result_model

        first_result_model, first_request_defer = self.provider.get(first_uri)
        second_result_model, second_request_defer = self.provider.get(second_uri)

        first_request_defer.addCallback(request_done)
        second_request_defer.addCallback(request_done)
        return defer.DeferredList([first_request_defer, second_request_defer])

    def test_multiple_images_retrieval_stress(self):
        """
        Stress test for concurrent retrieval of multiple images from the same
        farm.
        """
        run_functional_tests_check()
        root = 'http://farm4.%s/' % flickr.IMG_SERVER
        photos = ['3280/2586466863_b0a236a54f_t.jpg',
                  '3175/2587332686_bc0100ea1d_t.jpg',
                  '3423/3726370946_33c464d43e_t.jpg',
                  '3143/2587121948_b0b7896346_t.jpg',
                  '3192/2588374913_5a494f5c6c_t.jpg',
                  '3108/2587180848_dd5cb077e8_t.jpg',
                  '3261/2587315128_08362d7d94_t.jpg',
                  '3147/2588189712_91c6d7cd18_t.jpg',
                  '3065/2586808340_e1a53b78c7_t.jpg',
                  '3021/2587068704_1a45edc3be_t.jpg']

        def request_done(result_model):
            self.failUnless(hasattr(result_model, 'data'))
            header = 'ffd8' + 'ffe0' # SOI marker + JFIF marker
            self.assertEqual(binascii.hexlify(result_model.data[:4]), header)
            return result_model

        request_defers = []
        for photo in photos:
            uri = MediaUri(root + photo)
            result_model, request_defer = self.provider.get(uri)
            request_defer.addCallback(request_done)

            request_defers.append(request_defer)

        return defer.DeferredList(request_defers)

    def test_multiple_images_concurrent_retrieval(self):
        """
        Test concurrent retrieval of multiple images from different farms.
        """
        run_functional_tests_check()
        first_uri = 'http://farm4.%s/3109/2543837561_420bcc199b_m_d.jpg' % flickr.IMG_SERVER
        second_uri = 'http://farm3.%s/2117/2537054688_fb9cc1f65b_m_d.jpg' % flickr.IMG_SERVER

        def request_done(result_model):
            self.failUnless(hasattr(result_model, 'data'))
            header = 'ffd8' + 'ffe0' # SOI marker + JFIF marker
            self.assertEqual(binascii.hexlify(result_model.data[:4]), header)
            return result_model

        first_result_model, first_request_defer = self.provider.get(first_uri)
        second_result_model, second_request_defer = self.provider.get(second_uri)

        first_request_defer.addCallback(request_done)
        second_request_defer.addCallback(request_done)
        return defer.DeferredList([first_request_defer, second_request_defer])

    def test_authenticated_api_call(self):
        """
        Test an authenticated call to the Flickr API.
        """
        def got_response(response):
            self.failUnless(isinstance(response, FlickrResponseModel))
            return response

        uri = flickr.generate_call_uri(method='flickr.favorites.getList',
                                       authenticated=True)
        result_model, request_defer = self.provider.get(uri)
        request_defer.addCallback(got_response)
        return request_defer

    test_authenticated_api_call.skip = "Does not work with a fake token"

    def test_upload_then_delete_photo(self):
        """
        Test uploading a photo to the user account, and then deleting it.
        """
        filename = os.path.join(os.path.dirname(__file__),
                                'data', 'smiley.jpg')
        uri = 'http://%s/services/upload/' % flickr.API_SERVER
        arguments = {'api_key': flickr._key,
                     'auth_token': self.token}
        flickr.sign_arguments(arguments)

        def posted(photo_id):
            # Now delete the photo
            def deleted(response, photo_id):
                return photo_id

            uri = flickr.generate_call_uri(method='flickr.photos.delete',
                                           arguments={'photo_id': photo_id},
                                           authenticated=True)
            result_model, delete_defer = self.provider.get(uri)
            delete_defer.addCallback(deleted, photo_id)
            return delete_defer

        post_defer = self.provider.post(uri, arguments, filename)
        post_defer.addCallback(posted)
        return post_defer

    test_upload_then_delete_photo.skip = "Does not work with a fake token"

    def test_upload_fails(self):
        """
        Test a failing photo upload.
        """
        run_functional_tests_check()
        filename = os.path.join(os.path.dirname(__file__),
                                'data', 'smiley.jpg')
        uri = 'http://%s/services/upload/' % flickr.API_SERVER
        arguments = {'api_key': flickr._key,
                     'auth_token': self.token}
        # Voluntarily forget to sign the arguments to make the upload fail

        post_defer = self.provider.post(uri, arguments, filename)
        self.failUnlessFailure(post_defer, Exception)
        return post_defer

    def test_cancel_request_pipelined(self):
        """
        Test cancelling a request when the server supports request pipelining.
        That is the case of the Flickr API server.
        """
        run_functional_tests_check()
        method = 'flickr.test.echo'
        uri = flickr.generate_call_uri(method=method)
        request_deferreds = []

        def request_done(result_model):
            self.failUnless(isinstance(result_model, FlickrResponseModel))
            return result_model

        def first_request_done(result_model):
            # Let's cancel the third request after the first one is done
            request_deferreds[2].cancel()
            return request_done(result_model)

        def second_request_done(result_model):
            return request_done(result_model)

        def third_request_done(result_model):
            self.fail('This should never happen')

        def third_request_cancelled(failure):
            return failure

        def fourth_request_done(result_model):
            return request_done(result_model)

        for i in xrange(4):
            result_model, request_defer = self.provider.get(uri)
            request_deferreds.append(request_defer)

        request_deferreds[0].addCallback(first_request_done)
        request_deferreds[1].addCallback(second_request_done)
        request_deferreds[2].addCallback(third_request_done)
        request_deferreds[2].addErrback(third_request_cancelled)
        request_deferreds[3].addCallback(fourth_request_done)

        return defer.DeferredList(request_deferreds, consumeErrors=True)

    def test_cancel_request_not_pipelined(self):
        """
        Test cancelling a request when the server does not support request
        pipelining. That is the case of the image servers.
        """
        run_functional_tests_check()
        root = 'http://farm4.%s/' % flickr.IMG_SERVER
        photos = ['3280/2586466863_b0a236a54f_t.jpg',
                  '3175/2587332686_bc0100ea1d_t.jpg',
                  '3163/2586300619_b85095a276_t.jpg',
                  '3143/2587121948_b0b7896346_t.jpg']
        request_deferreds = []

        def request_done(result_model):
            self.failUnless(hasattr(result_model, 'data'))
            header = 'ffd8' + 'ffe0' # SOI marker + JFIF marker
            self.assertEqual(binascii.hexlify(result_model.data[:4]), header)
            return result_model

        def first_request_done(result_model):
            # Let's cancel the third request after the first one is done
            request_deferreds[2].cancel()
            return request_done(result_model)

        def second_request_done(result_model):
            return request_done(result_model)

        def third_request_done(result_model):
            self.fail('This should never happen')

        def third_request_cancelled(failure):
            return failure

        def fourth_request_done(result_model):
            return request_done(result_model)

        for photo in photos:
            result_model, request_defer = self.provider.get(MediaUri(root + photo))
            request_deferreds.append(request_defer)

        request_deferreds[0].addCallback(first_request_done)
        request_deferreds[1].addCallback(second_request_done)
        request_deferreds[2].addCallback(third_request_done)
        request_deferreds[2].addErrback(third_request_cancelled)
        request_deferreds[3].addCallback(fourth_request_done)

        return defer.DeferredList(request_deferreds, consumeErrors=True)
