# Elisa - Home multimedia server
# Copyright (C) 2006-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 Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" 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 import plugin, config
from elisa.core.backend import Backend
from elisa.base_components.model import Model
from elisa.base_components.controller import Controller
from elisa.base_components.controller import ModelNotSupported


class FooModel(Model):
    def __init__(self):
        Model.__init__(self)
        self.path = "base_test:foo_model"

class BarModel(Model):
    def __init__(self):
        Model.__init__(self)
        self.path = "base_test:bar_model"

class FooController(Controller):

    supported_models = ("base_test:foo_model",)
    bindings = (("model_child1", "controller_child1"),
                ("model_child2", ["controller_child2", "controller_child3"]))

    def __init__(self):
        Controller.__init__(self)
        self.reset()

    def reset(self):
        self.old_model = False
        self.new_model = False
        self.old_backend = False
        self.new_backend = False

    def model_changed(self, old_model, new_model):
        self.old_model = old_model
        self.new_model = new_model

    def backend_changed(self, old_backend, new_backend):
        self.old_backend = old_backend
        self.new_backend = new_backend

class BarController(FooController):

    supported_models = ("base_test:bar_model",)

class BaseTest(plugin.Plugin):
    components = {'foo_model': {'path': FooModel},
                  'bar_model': {'path': BarModel},
                  'foo_controller': {'path': FooController},
                  'bar_controller': {'path': BarController},}

MAPPINGS="""\
[base_test:bar_model]
supported_controllers = ['base_test:bar_controller',]
controller = 'base_test:bar_controller'
supported_views = ['base_test:bar_view']
view = 'base_test:bar_view'
"""


class TestController(ElisaTestCase):

    def setUp(self):
        ElisaTestCase.setUp(self)

        from elisa.core import common
        plugin_registry = common.application.plugin_registry
        plugin_registry.register_plugin(BaseTest)
 
        self._foo = FooController()
        self._foo.initialize()
        self._bar = BarController()
        self._bar.initialize()

    def test_backend_changed(self):
        # no backend assigned
        self.failUnlessEqual(self._foo.backend, None)

        backend = Backend()

        # gives a backend to foo
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, None)
        self.failUnlessEqual(self._foo.new_backend, backend)

        # ... or None
        self._foo.reset()
        self._foo.backend = None
        self.failUnlessEqual(self._foo.backend, None)
        self.failUnlessEqual(self._foo.old_backend, backend)
        self.failUnlessEqual(self._foo.new_backend, None)

        # ... or again a real backend
        self._foo.reset()
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, None)
        self.failUnlessEqual(self._foo.new_backend, backend)

        # ... twice the same
        self._foo.reset()
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, False)
        self.failUnlessEqual(self._foo.new_backend, False)

    def test_backend_inheritance(self):
        # no backend assigned
        self.failUnlessEqual(self._foo.backend, None)
        self.failUnlessEqual(self._bar.backend, None)

        backend = Backend()

        # gives a backend to foo
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, None)
        self.failUnlessEqual(self._foo.new_backend, backend)
 
        # backend must be passed to controller's children
        # ... whether it is a real backend
        self._bar.parent = self._foo
        self.failUnlessEqual(self._bar.backend, backend)
        self.failUnlessEqual(self._bar.old_backend, None)
        self.failUnlessEqual(self._bar.new_backend, backend)

        # ... or None
        self._foo.reset()
        self._bar.reset()
        self._foo.backend = None
        self.failUnlessEqual(self._foo.backend, None)
        self.failUnlessEqual(self._foo.old_backend, backend)
        self.failUnlessEqual(self._foo.new_backend, None)
        self.failUnlessEqual(self._bar.backend, None)
        self.failUnlessEqual(self._bar.old_backend, backend)
        self.failUnlessEqual(self._bar.new_backend, None)

        # ... or again a real backend
        self._foo.reset()
        self._bar.reset()
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, None)
        self.failUnlessEqual(self._foo.new_backend, backend)
        self.failUnlessEqual(self._bar.backend, backend)
        self.failUnlessEqual(self._bar.old_backend, None)
        self.failUnlessEqual(self._bar.new_backend, backend)

        # ... twice the same
        self._foo.reset()
        self._bar.reset()
        self._foo.backend = backend
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, False)
        self.failUnlessEqual(self._foo.new_backend, False)
        self.failUnlessEqual(self._bar.backend, backend)
        self.failUnlessEqual(self._bar.old_backend, False)
        self.failUnlessEqual(self._bar.new_backend, False)


        # changing the backend of a controller which has a parent
        # with a different backend is impossible
        def set_backend(controller, backend):
            controller.backend = backend

        self._foo.reset()
        self._bar.reset()
        self.failUnlessRaises(Exception, reset_backend, self._bar, Backend())
        self.failUnlessRaises(Exception, reset_backend, self._bar, None)

        # ... and nothing should change
        self.failUnlessEqual(self._foo.backend, backend)
        self.failUnlessEqual(self._foo.old_backend, False)
        self.failUnlessEqual(self._foo.new_backend, False)
        self.failUnlessEqual(self._bar.backend, backend)
        self.failUnlessEqual(self._bar.old_backend, False)
        self.failUnlessEqual(self._bar.new_backend, False)

    test_backend_inheritance.skip = "should be implemented for bound controllers"

        
    def test_model_changed(self):
        # tests with a supported model
        model = FooModel()
        self._foo.model = model
        self.failUnlessEqual(self._foo.model, model)
        self.failUnlessEqual(self._foo.old_model, None)
        self.failUnlessEqual(self._foo.new_model, model)

        # re-assigning the same model should not do anything
        self._foo.reset()
        self._foo.model = model
        self.failUnlessEqual(self._foo.model, model)
        self.failUnlessEqual(self._foo.old_model, False)
        self.failUnlessEqual(self._foo.new_model, False)

        # assigning a different model
        self._foo.reset()
        model2 = FooModel()
        self._foo.model = model2
        self.failUnlessEqual(self._foo.model, model2)
        self.failUnlessEqual(self._foo.old_model, model)
        self.failUnlessEqual(self._foo.new_model, model2)

        # disconnecting the model
        self._foo.reset()
        self._foo.model = None
        self.failUnlessEqual(self._foo.model, None)
        self.failUnlessEqual(self._foo.old_model, model2)
        self.failUnlessEqual(self._foo.new_model, None)

        
        # assigning a non supported model
        self._foo.reset()

        def set_model(controller, model):
            controller.model = model

        model = BarModel()
        self.failUnlessRaises(ModelNotSupported, set_model, self._foo, model)
        self.failUnlessEqual(self._foo.model, None)
        self.failUnlessEqual(self._foo.old_model, False)
        self.failUnlessEqual(self._foo.new_model, False)

    # TODO: good candidate for generic testing of custom Controllers
    def test_supported_models(self):
        self.assertEquals(type(self._foo.supported_models), tuple)
        for s in self._foo.supported_models:
            self.assertEquals(type(s), str)

        def set_model(controller, model):
            controller.model = model

        # assigning a supported model should work
        model = FooModel()
        set_model(self._foo, model)

        # assigning a non supported model should raise an exception
        model = BarModel()
        self.failUnlessRaises(ModelNotSupported, set_model, self._foo, model)

    # TODO: good candidate for generic testing of custom Controllers
    def test_handle_input(self):
        # TODO: should generate random InputEvents, send them to
        # Controller.handle_input and check that the result is a boolean
        pass
    test_handle_input.skip = "not implemented yet"

    def test_bindings_model_set_first(self):
        cfg = config.Config('no_file', MAPPINGS)
        backend = Backend(cfg)
        self._foo.backend = backend

        # connect a supported model
        model = FooModel()
        self._foo.model = model

        # adding bound attributes to the model
        # ... a model
        model.model_child1 = BarModel()
        self.failUnless(isinstance(self._foo.controller_child1,
                                   BarController))

        # FIXME: this is unfortunately not so easy; there is no notification
        # of attribute removal in the Observable/Observer protocol defined in
        # Elisa. However, this is not a very common use case.
        """
        delattr(model, "model_child1")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child1")
        """

        # ... an object which is not a model
        value = 123
        model.model_child1 = value
        self.failUnlessEqual(self._foo.controller_child1, value)

        model.model_child1 = "test"
        self.failUnlessEqual(self._foo.controller_child1, "test")

        # ... or even None
        model.model_child1 = None
        self.failUnlessEqual(self._foo.controller_child1, None)
       

    def test_bindings_not_bound_attributes(self):
        cfg = config.Config('no_file', MAPPINGS)
        backend = Backend(cfg)
        self._foo.backend = backend

        # connect a supported model
        model = FooModel()
        self._foo.model = model

        # adding not bound attributes to the model
        # remove them
        model.not_bound1 = 123
        model.not_bound2 = BarModel()

        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child1")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child2")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child3")

    def test_bindings_model_set_after(self):
        cfg = config.Config('no_file', MAPPINGS)
        backend = Backend(cfg)
        self._foo.backend = backend

        # create a supported model but does not connect it with the controller
        model = FooModel()

        # adding bound attributes to the model
        # ... a model
        model.model_child1 = BarModel()
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child1")
        # connect the model
        self._foo.model = model
        self.failUnless(isinstance(self._foo.controller_child1,
                                   BarController))


        # disconnect the model
        self._foo.model = None
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child1")


        # ... an object which is not a model
        model.model_child1 = "test"
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child1")

        # connect the model
        self._foo.model = model
        self.failUnlessEqual(self._foo.controller_child1, "test")



    def test_bindings_without_backend(self):
        # connect a supported model
        model = FooModel()
        self._foo.model = model

        # without a backend adding a child model should fail
        self.failUnlessRaises(Exception, setattr, model, "model_child1",
                              BarModel())

        # it should work as usual for everything else
        # ... an object which is not a model
        value = 123
        model.model_child1 = value
        self.failUnlessEqual(self._foo.controller_child1, value)

        model.model_child1 = "test"
        self.failUnlessEqual(self._foo.controller_child1, "test")

        # ... or even None
        model.model_child1 = None
        self.failUnlessEqual(self._foo.controller_child1, None)


    def test_multiple_bindings(self):
        cfg = config.Config('no_file', MAPPINGS)
        backend = Backend(cfg)
        self._foo.backend = backend

        # connect a supported model
        model = FooModel()
        self._foo.model = model

        # adding bound attributes to the model
        # ... a model
        model.model_child2 = BarModel()
        self.failUnless(isinstance(self._foo.controller_child2,
                                   BarController))
        self.failUnless(isinstance(self._foo.controller_child3,
                                   BarController))


        # disconnect the model
        self._foo.model = None
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child2")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "controller_child3")

