# 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.
#
# Authors: Olivier Tilloy <olivier@fluendo.com>
#          Benjamin Kampmann <benjamin@fluendo.com>

from elisa.core.tests.elisa_test_case import ElisaTestCase

from elisa.core.components.metadata_capability import MetadataCapability
from elisa.core.components.model import Model
from elisa.core.manager import AlreadyRegistered
from elisa.core.metadata_manager import MetadataManager, \
                                        IncompleteMetadataResponse

from elisa.core.utils import defer

from twisted.internet import task


class FlexibleMeta(MetadataCapability):
    able_called_once = False
    get_called_once = False

    def able_to_handle(self, *args):
        self.able_called_once = not self.able_called_once
        return self.able

    def get_metadata(self, *args):
        self.get_called_once = not self.get_called_once
        return defer.succeed("Yay")


class MyException(Exception):
    pass


class TestMetadataManager(ElisaTestCase):

    def setUp(self):
        ElisaTestCase.setUp(self)
        self.manager = MetadataManager()

    def test_register_unregister(self):
        """
        Test whether registering and unregistering work in a very simple case.
        """
        def capability_created(capability):
            capability.rank = 100
            dfr = self.manager.register_component(capability)

            def check_capability_registered(result):
                self.failUnlessIn(capability, self.manager.components)

            def check_capability_unregistered(result):
                self.failIfIn(capability, self.manager.components)

            dfr.addCallback(check_capability_registered)
            dfr.addCallback(lambda result: \
                                self.manager.unregister_component(capability))
            dfr.addCallback(check_capability_unregistered)
            return dfr

        create_dfr = FlexibleMeta.create()
        create_dfr.addCallback(capability_created)
        return create_dfr

    def test_register_already_registered(self):
        """
        Test that trying to register a capability that has already been
        registered fails.
        """
        def capability_created(capability):
            dfr = self.manager.register_component(capability)

            def try_to_register_again(result):
                dfr = self.manager.register_component(capability)
                return self.failUnlessFailure(dfr, AlreadyRegistered)

            dfr.addCallback(try_to_register_again)
            return dfr

        create_dfr = FlexibleMeta.create()
        create_dfr.addCallback(capability_created)
        return create_dfr

    def test_ranking(self):
        """
        Test whether the capabilities are correctly ordered by decreasing rank.
        """
        def _register_capability(result, capability):
            return self.manager.register_component(capability)

        def _unregister_capability(result, capability):
            return self.manager.unregister_component(capability)

        def start_tests(create_result, capabilities):
            dfr = _register_capability(None, capabilities[0])
            dfr.addCallback(_register_capability, capabilities[1])
            dfr.addCallback(_register_capability, capabilities[2])

            def test_rank(result):
                # Test that the list is ordered by decreasing rank
                ranks = [capability.rank for capability in \
                            self.manager.components]
                sorted_ranks = ranks[:]
                sorted_ranks.sort()
                sorted_ranks.reverse()
                self.failUnlessEquals(ranks, sorted_ranks)

            dfr.addCallback(test_rank)

            def copy_component_list(result):
                # make a copy of the list of components
                self._components_copy = self.manager.components[:]

            dfr.addCallback(copy_component_list)

            dfr.addCallback(_unregister_capability, capabilities[0])
            dfr.addCallback(_unregister_capability, capabilities[1])
            dfr.addCallback(_unregister_capability, capabilities[2])

            # Register in a different order
            dfr.addCallback(_register_capability, capabilities[1])
            dfr.addCallback(_register_capability, capabilities[0])
            dfr.addCallback(_register_capability, capabilities[2])

            def check_order(result):
                self.failUnlessEquals(self._components_copy,
                                      self.manager.components)

            # Should be the same order
            dfr.addCallback(check_order)

            dfr.addCallback(_unregister_capability, capabilities[0])
            dfr.addCallback(_unregister_capability, capabilities[1])
            dfr.addCallback(_unregister_capability, capabilities[2])

            # Register again in a different order
            dfr.addCallback(_register_capability, capabilities[0])
            dfr.addCallback(_register_capability, capabilities[2])
            dfr.addCallback(_register_capability, capabilities[1])

            # Should still be the same order
            dfr.addCallback(check_order)

            dfr.addCallback(_unregister_capability, capabilities[0])
            dfr.addCallback(_unregister_capability, capabilities[1])
            dfr.addCallback(_unregister_capability, capabilities[2])

            return dfr

        created = []

        def capability_created(capability, rank, created):
            capability.rank = rank
            created.append(capability)

        s1_dfr = FlexibleMeta.create()
        s1_dfr.addCallback(capability_created, 1, created)

        s3_dfr = FlexibleMeta.create()
        s3_dfr.addCallback(capability_created, 3, created)

        s123_dfr = FlexibleMeta.create()
        s123_dfr.addCallback(capability_created, 123, created)

        dfr = defer.DeferredList([s1_dfr, s3_dfr, s123_dfr])
        dfr.addCallback(start_tests, created)
        return dfr

    def test_able_and_call(self):
        """
        Test whether the able_to_handle(...) method is correctly called.
        """
        def check_called(get_result, capability):
            self.assertTrue(capability.able_called_once)
            self.assertTrue(capability.get_called_once)

        def capability_created(capability):
            capability.rank = 123
            capability.able = True
            capability.failing = False
            dfr = self.manager.register_component(capability)

            def registered(result):
                get_dfr = self.manager.get_metadata('yeah', lambda model: False)
                self.failUnlessFailure(get_dfr, IncompleteMetadataResponse)
                get_dfr.addCallback(check_called, capability)
                return get_dfr

            dfr.addCallback(registered)
            return dfr

        dfr = FlexibleMeta.create()
        dfr.addCallback(capability_created)
        return dfr

    def test_multiple_able(self):
        """
        Test whether only the capabilities able to handle a request are hit.
        """
        def check_called(get_result, capabilities):
            self.assertTrue(self.manager.components[2].get_called_once)
            self.assertFalse(self.manager.components[1].get_called_once)
            self.assertTrue(self.manager.components[0].get_called_once)

        def start_tests(create_result, capabilities):
            def register_capabilities():
                for capability in capabilities:
                    dfr = self.manager.register_component(capability)
                    yield dfr

            dfr = task.coiterate(register_capabilities())

            def registered(result):
                get_dfr = self.manager.get_metadata('yay', lambda model: False)
                self.failUnlessFailure(get_dfr, IncompleteMetadataResponse)
                get_dfr.addCallback(check_called, capabilities)
                return get_dfr

            dfr.addCallback(registered)
            return dfr

        created = []

        def capability_created(capability, rank, able, created):
            capability.rank = rank
            capability.able = able
            created.append(capability)

        first_dfr = FlexibleMeta.create()
        first_dfr.addCallback(capability_created, 100, True, created)

        second_dfr = FlexibleMeta.create()
        second_dfr.addCallback(capability_created, 200, False, created)

        third_dfr = FlexibleMeta.create()
        third_dfr.addCallback(capability_created, 300, True, created)

        dfr = defer.DeferredList([first_dfr, second_dfr, third_dfr])
        dfr.addCallback(start_tests, created)
        return dfr

    def test_metadata_filled(self):
        """
        Test the behaviour when a metadata request is correctly processed.
        Only one metadata capability is registered, it fills the requested
        metadata.
        """
        def got_metadata(get_result, model):
            self.failUnless(model.filled)

        def capability_created(capability):

            def get_metadata(model):
                model.filled = True
                return defer.succeed('Filled')

            capability.get_metadata = get_metadata
            capability.able = True
            dfr = self.manager.register_component(capability)

            def registered(result):
                model = Model()
                model.filled = False
                get_dfr = self.manager.get_metadata(model,
                                                    lambda model: model.filled)
                get_dfr.addCallback(got_metadata, model)
                return get_dfr

            dfr.addCallback(registered)
            return dfr

        create_dfr = FlexibleMeta.create()
        create_dfr.addCallback(capability_created)
        return create_dfr

    def test_metadata_filled_by_first_capability(self):
        """
        Test the behaviour when a metadata request is correctly processed.
        Two metadata capabilities are registered, the first one fills the
        metadata, therefore the second one is not tried.
        """
        def got_metadata(get_result, model):
            self.failUnless(model.filled)
            self.failIf(self.second_capability.get_called_once)

        def start_tests(create_result):
            model = Model()
            model.filled = False
            get_dfr = self.manager.get_metadata(model,
                                                lambda model: model.filled)
            get_dfr.addCallback(got_metadata, model)
            return get_dfr

        def get_metadata(model):
            model.filled = True
            return defer.succeed('Filled')

        def capability_created(capability, rank):
            capability.rank = rank
            capability.able = True
            if rank == 200:
                capability.get_metadata = get_metadata
            else:
                self.second_capability = capability
            dfr = self.manager.register_component(capability)
            return dfr

        first_dfr = FlexibleMeta.create()
        first_dfr.addCallback(capability_created, 200)

        second_dfr = FlexibleMeta.create()
        second_dfr.addCallback(capability_created, 100)

        dfr = defer.DeferredList([first_dfr, second_dfr])
        dfr.addCallback(start_tests)
        return dfr

    def test_metadata_filled_by_second_capability(self):
        """
        Test the behaviour when a metadata request is correctly processed.
        Two metadata capabilities are registered, the first one does not fill
        the metadata, the second one does.
        """
        def got_metadata(get_result, model):
            self.failUnless(model.filled)

        def start_tests(create_result):
            model = Model()
            model.filled = False
            get_dfr = self.manager.get_metadata(model,
                                                lambda model: model.filled)
            get_dfr.addCallback(got_metadata, model)
            return get_dfr

        def get_metadata(model):
            model.filled = True
            return defer.succeed('Filled')

        def capability_created(capability, rank):
            capability.rank = rank
            capability.able = True
            if rank == 100:
                capability.get_metadata = get_metadata
            dfr = self.manager.register_component(capability)
            return dfr

        first_dfr = FlexibleMeta.create()
        first_dfr.addCallback(capability_created, 200)

        second_dfr = FlexibleMeta.create()
        second_dfr.addCallback(capability_created, 100)

        dfr = defer.DeferredList([first_dfr, second_dfr])
        dfr.addCallback(start_tests)
        return dfr

    def test_one_fails(self):
        """
        Test what happens when one capability in the list fails.
        """
        def check_called(get_result):
            self.failUnless(self.manager.components[2].get_called_once)
            self.failUnless(self.manager.components[0].get_called_once)

        def start_tests(create_result, capabilities):
            def register_capabilities():
                for capability in capabilities:
                    dfr = self.manager.register_component(capability)
                    yield dfr

            dfr = task.coiterate(register_capabilities())

            def registered(result):
                get_dfr = self.manager.get_metadata('yuhu', lambda model: False)
                self.failUnlessFailure(get_dfr, IncompleteMetadataResponse)
                get_dfr.addCallback(check_called)
                return get_dfr

            dfr.addCallback(registered)
            return dfr

        created = []

        def capability_created(capability, rank, able, created,
                               get_metadata=None):
            capability.rank = rank
            capability.able = able
            if get_metadata is not None:
                capability.get_metadata = get_metadata
            created.append(capability)

        first_dfr = FlexibleMeta.create()
        first_dfr.addCallback(capability_created, 100, True, created)

        def fail(*args):
            return defer.fail(MyException('no, dude'))

        second_dfr = FlexibleMeta.create()
        # get_metadata(*args) called on the second capability will fail,
        # the manager should swallow this failure and continue the iteration
        second_dfr.addCallback(capability_created, 200, True, created, fail)

        third_dfr = FlexibleMeta.create()
        third_dfr.addCallback(capability_created, 300, True, created)

        dfr = defer.DeferredList([first_dfr, second_dfr, third_dfr])
        dfr.addCallback(start_tests, created)
        return dfr

    def test_external_exception_raised_1(self):
        """
        Test what happens when exceptions are raised outside the twisted part.

        First test, able_to_handle(...) raises a custom exception.
        """
        def fail(*args):
            raise MyException('dead')

        def capability_created(capability, able_to_handle):
            capability.rank = 9
            capability.able_to_handle = able_to_handle

            dfr = self.manager.register_component(capability)

            def registered(result):
                get_dfr = self.manager.get_metadata('beach',
                                                    lambda model: False)
                self.failUnlessFailure(get_dfr, IncompleteMetadataResponse)
                return get_dfr

            dfr.addCallback(registered)
            return dfr

        dfr = FlexibleMeta.create()
        dfr.addCallback(capability_created, fail)
        return dfr

    def test_external_exception_raised_2(self):
        """
        Test what happens when exceptions are raised outside the twisted part.

        Second test, get_metadata(...) raises a custom exception.
        """
        def fail(*args):
            raise MyException('dead')

        def capability_created(capability):
            capability.rank = 7
            capability.able = True
            capability.get_metadata = fail

            dfr = self.manager.register_component(capability)

            def registered(result):
                get_dfr = self.manager.get_metadata('beach', lambda model: False)
                self.failUnlessFailure(get_dfr, IncompleteMetadataResponse)
                return get_dfr

            dfr.addCallback(registered)

        dfr = FlexibleMeta.create()
        dfr.addCallback(capability_created)
        return dfr

    def test_cancel_request(self):
        """
        Test that cancelling a metadata request works as expected.
        """
        # TODO: implement this test
        pass

    test_cancel_request.skip = 'This test is not implemented at the moment'
