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

from elisa.core.tests.elisa_test_case import ElisaTestCase
from elisa.core.resource_manager import ResourceManager, \
                                        ResourceProviderNotFound, \
                                        NoMatchingResourceProvider
from elisa.core.manager import CannotUnregister
from elisa.core.media_uri import MediaUri
from elisa.core.components.resource_provider import ResourceProvider
from elisa.core.components.model import Model
from elisa.core.utils.defer import Deferred

import re


class FooResourceProvider(ResourceProvider): 
    """
    A stupid providers that just stores the last called method and its
    parameters
    """
    supported_uri = "http://.*"

    def _last_called(self, method, *args, **kw):
        self.last_called = method
        self.last_arguments = args
        self.last_kw = kw
        model = Model()
        dfr = Deferred()
        return model, dfr

    def get(self, *args, **kwargs):
        return self._last_called('get', *args, **kwargs)

    def post(self, *args, **kwargs):
        return self._last_called('post', *args, **kwargs)

    def delete(self, *args, **kwargs):
        return self._last_called('delete', *args, **kwargs)

    def put(self, *args, **kwargs):
        return self._last_called('put', *args, **kwargs)


class TestResourceManager(ElisaTestCase):

    def setUp(self):
        """
        create the manager, create the test provider and register it
        """
        ElisaTestCase.setUp(self)

        # Set up a test resource manager
        self._manager = ResourceManager()

        def provider_created(provider):
            self._test_comp = provider
            # Register the provider
            dfr = self._manager.register_component(self._test_comp)
            return dfr

        # Set up a test resource provider
        create_dfr = FooResourceProvider.create()
        create_dfr.addCallback(provider_created)
        return create_dfr

    def test_register_unregister(self):
        """
        Test that registering and unregistering a resource provider works as
        expected.
        """
        def provider_created(provider):
            # Register the provider
            dfr = self._manager.register_component(provider)

            def check_provider_registered(result):
                self.failUnlessIn(provider, self._manager.components)

            def check_uri(result):
                uri_regexp = (re.compile(provider.supported_uri), provider)
                self.failUnlessIn(uri_regexp,
                                  self._manager._providers_uri_regexps)

            def check_provider_unregistered(result):
                self.failIfIn(provider, self._manager.components)

            dfr.addCallback(check_provider_registered)
            dfr.addCallback(check_uri)
            dfr.addCallback(lambda result: \
                                self._manager.unregister_component(provider))
            dfr.addCallback(check_provider_unregistered)
            return dfr

        create_dfr = FooResourceProvider.create()
        create_dfr.addCallback(provider_created)
        return create_dfr

    def test_get_known_uri_without_context(self):
        """
        Tests if all requests gets proxied to the given test component
        """

        uri = MediaUri("http://gogol.com")

        # test if get gets passed through
        result = self._manager.get(uri)
        self.check_result(result, 'get', uri, None)

        # test if post gets passed through
        result = self._manager.post(uri, key='sth')
        self.check_result(result, 'post', uri, key='sth')

        # test if delete gets passed through
        result = self._manager.delete(uri)
        self.check_result(result, 'delete', uri)

        # test if put gets passed through
        result = self._manager.put('t', uri)
        self.check_result(result, 'put', 't', uri, None)

    def test_put_parameters(self):
        """
        Test if specific parameters are passed correctly.
        """

        uri = MediaUri("http://gogol.com")

        result = self._manager.put('foo', uri, foo_option='abc')
        self.check_result(result, 'put', 'foo', uri, None, foo_option='abc')

    def test_get_known_uri_with_models(self): 
        """
        Tests if all requests gets proxied to the given test component
        """
        uri = MediaUri("http://gogol.com")

        # test if get gets passed through
        context_model = Model()
        result = self._manager.get(uri, context_model=context_model)
        self.check_result(result, 'get', uri, context_model)

        # test if post gets passed through
        result = self._manager.post(uri, key='sth')
        self.check_result(result, 'post', uri, key='sth')

        # test if delete gets passed through
        result = self._manager.delete(uri)
        self.check_result(result, 'delete', uri)

        # test if put gets passed through
        source_model = Model()
        result = self._manager.put('t', uri, source_model=source_model)
        self.check_result(result, 'put', 't', uri, source_model)

    def test_get_unknown_uri(self):
        """
        test if the manager raises the NoMatchingResourceProvider Exception in
        case the given URI can't get proceeded
        """
        uri = MediaUri("unkwown://azerty.com")
        self.assertRaises(NoMatchingResourceProvider, self._manager.get, uri)

    def test_nonexpresion_providers(self):
        """
        test sure that a resource provider without supported uri or set to an
        empty string will get loaded but ignored when asking for a uri
        """

        # not set check
        class NoUrisExpression(ResourceProvider):
            pass

        non_uris = NoUrisExpression()
        
        before = len(self._manager.components)
        
        self._manager.register_component(non_uris)
        self.assertEquals(len(self._manager.components), before+1)

        self._manager.unregister_component(non_uris)
        self.assertEquals(len(self._manager.components), before)

        # empty check
        class EmptyUrisExpression(ResourceProvider):
            supported_uri = ''

        empty_uris = EmptyUrisExpression()
        before = len(self._manager.components)
        
        self._manager.register_component(empty_uris)
        self.assertEquals(len(self._manager.components), before+1)

        self._manager.unregister_component(empty_uris)
        self.assertEquals(len(self._manager.components), before)

    def test_broken_providers(self):
        """
        be sure that registering fails on a ResourceProvider that has a
        corrupted supported_uri regular expression
        """

        # test whether the manager fails on registering a ResourceProvider with a
        # wrong regular expression

        class WrongExpression(ResourceProvider):
            supported_uri = "[]"

        dfr = self._manager.register_component(WrongExpression())
        return self.failUnlessFailure(dfr, Exception)

    def test_malformed_parameters(self):
        """
        be sure that the manager fails when you call it with bad data
        """
        # try to do it with a string that can't get converted to unicde
        self.assertRaises(UnicodeDecodeError, self._manager.get, '\xcd')

    def test_unregister_wrong_component(self):
        """
        Trying to unregister a component, that is not registered is bad
        coding. Make sure that it fails in such a case
        """
        dfr = self._manager.unregister_component(ResourceProvider())
        res_dfr = self.failUnlessFailure(dfr, CannotUnregister)

        def unregister_other_instance(result):
            # The unregistration is always about the SAME INSTANCE. Passing a
            # new instance to unregister must fail.
            class TestProvider(ResourceProvider):
                supported_uri = 'nothing special'

            test_provider = TestProvider()
            dfr = self._manager.register_component(test_provider)

            def try_to_unregister_other_instance(result):
                dfr = self._manager.unregister_component(TestProvider())
                res_dfr = self.failUnlessFailure(dfr, CannotUnregister)

            dfr.addCallback(try_to_unregister_other_instance)
            dfr.addCallback(lambda result: \
                            self._manager.unregister_component(test_provider))
            return dfr

        res_dfr.addCallback(unregister_other_instance)
        return res_dfr

    # helper for checking
    def check_result(self, result, method, *args, **kw):
        """
        internal method to check if the given result is the expected one
        """
        model, dfr = result
        self.failUnless(isinstance(model, Model))
        self.failUnless(isinstance(dfr, Deferred))

        # test that the last method call was the right one
        self.assertEquals(self._test_comp.last_called, method)

        # check if the given parameters and keywords match
        self.assertEquals(self._test_comp.last_arguments, args)
        self.assertEquals(self._test_comp.last_kw, kw)

    def test_get_resource_provider_by_path(self):
        """
        Test that the get_resource_provider_by_path method works as expected.
        """
        path = 'elisa.core.tests.test_resource_manager:FooResourceProvider'
        component = self._manager.get_resource_provider_by_path(path)
        self.failUnlessEqual(component, self._test_comp)

        bad_path = 'elisa.core.tests.test_resource_manager:BarResourceProvider'
        self.failUnlessRaises(ResourceProviderNotFound,
                              self._manager.get_resource_provider_by_path,
                              bad_path)
