# 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.frontend import Frontend
from elisa.base_components.model import Model
from elisa.base_components.controller import Controller
from elisa.base_components.view import View
from elisa.base_components.view import ControllerNotSupported

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",)

    def __init__(self):
        Controller.__init__(self)
        self.path = "base_test:foo_controller"

class BarController(Controller):

    supported_models = ("base_test:bar_model",)

    def __init__(self):
        Controller.__init__(self)
        self.path = "base_test:bar_controller"

class FooView(View):

    supported_controllers = ("base_test:foo_controller",)
    bindings = (("controller_child1", "view_child1"),
                ("controller_child2", ["view_child2", "view_child3"]))

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

    def reset(self):
        self.old_controller = False
        self.new_controller = False
        self.old_frontend = False
        self.new_frontend = False

    def controller_changed(self, old_controller, new_controller):
        self.old_controller = old_controller
        self.new_controller = new_controller

    def frontend_changed(self, old_frontend, new_frontend):
        self.old_frontend = old_frontend
        self.new_frontend = new_frontend

class BarView(FooView):

    supported_controllers = ("base_test:bar_controller",)

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

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 TestView(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 = FooView()
        self._foo.initialize()
        self._bar = BarView()
        self._bar.initialize()

    def test_frontend_changed(self):
        # no frontend assigned
        self.failUnlessEqual(self._foo.frontend, None)

        cfg = config.Config('no_file', MAPPINGS)
        frontend = Frontend(cfg)

        # gives a frontend to foo
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, None)
        self.failUnlessEqual(self._foo.new_frontend, frontend)

        # ... or None
        self._foo.reset()
        self._foo.frontend = None
        self.failUnlessEqual(self._foo.frontend, None)
        self.failUnlessEqual(self._foo.old_frontend, frontend)
        self.failUnlessEqual(self._foo.new_frontend, None)

        # ... or again a real frontend
        self._foo.reset()
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, None)
        self.failUnlessEqual(self._foo.new_frontend, frontend)

        # ... twice the same
        self._foo.reset()
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, False)
        self.failUnlessEqual(self._foo.new_frontend, False)

    def test_frontend_inheritance(self):
        # no frontend assigned
        self.failUnlessEqual(self._foo.frontend, None)
        self.failUnlessEqual(self._bar.frontend, None)

        frontend = Frontend()

        # gives a frontend to foo
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, None)
        self.failUnlessEqual(self._foo.new_frontend, frontend)
 
        # frontend must be passed to view's children
        # ... whether it is a real frontend
        self._bar.parent = self._foo
        self.failUnlessEqual(self._bar.frontend, frontend)
        self.failUnlessEqual(self._bar.old_frontend, None)
        self.failUnlessEqual(self._bar.new_frontend, frontend)

        # ... or None
        self._foo.reset()
        self._bar.reset()
        self._foo.frontend = None
        self.failUnlessEqual(self._foo.frontend, None)
        self.failUnlessEqual(self._foo.old_frontend, frontend)
        self.failUnlessEqual(self._foo.new_frontend, None)
        self.failUnlessEqual(self._bar.frontend, None)
        self.failUnlessEqual(self._bar.old_frontend, frontend)
        self.failUnlessEqual(self._bar.new_frontend, None)

        # ... or again a real frontend
        self._foo.reset()
        self._bar.reset()
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, None)
        self.failUnlessEqual(self._foo.new_frontend, frontend)
        self.failUnlessEqual(self._bar.frontend, frontend)
        self.failUnlessEqual(self._bar.old_frontend, None)
        self.failUnlessEqual(self._bar.new_frontend, frontend)

        # ... twice the same
        self._foo.reset()
        self._bar.reset()
        self._foo.frontend = frontend
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, False)
        self.failUnlessEqual(self._foo.new_frontend, False)
        self.failUnlessEqual(self._bar.frontend, frontend)
        self.failUnlessEqual(self._bar.old_frontend, False)
        self.failUnlessEqual(self._bar.new_frontend, False)


        # changing the frontend of a view which has a parent
        # with a different frontend is impossible
        def set_frontend(view, frontend):
            view.frontend = frontend

        self._foo.reset()
        self._bar.reset()
        self.failUnlessRaises(Exception, reset_frontend, self._bar, Frontend())
        self.failUnlessRaises(Exception, reset_frontend, self._bar, None)

        # ... and nothing should change
        self.failUnlessEqual(self._foo.frontend, frontend)
        self.failUnlessEqual(self._foo.old_frontend, False)
        self.failUnlessEqual(self._foo.new_frontend, False)
        self.failUnlessEqual(self._bar.frontend, frontend)
        self.failUnlessEqual(self._bar.old_frontend, False)
        self.failUnlessEqual(self._bar.new_frontend, False)

    test_frontend_inheritance.skip = "should be implemented for bound views"

        
    def test_controller_changed(self):
        # tests with a supported controller
        controller = FooController()
        self._foo.controller = controller
        self.failUnlessEqual(self._foo.controller, controller)
        self.failUnlessEqual(self._foo.old_controller, None)
        self.failUnlessEqual(self._foo.new_controller, controller)

        # re-assigning the same controller should not do anything
        self._foo.reset()
        self._foo.controller = controller
        self.failUnlessEqual(self._foo.controller, controller)
        self.failUnlessEqual(self._foo.old_controller, False)
        self.failUnlessEqual(self._foo.new_controller, False)

        # assigning a different controller
        self._foo.reset()
        controller2 = FooController()
        self._foo.controller = controller2
        self.failUnlessEqual(self._foo.controller, controller2)
        self.failUnlessEqual(self._foo.old_controller, controller)
        self.failUnlessEqual(self._foo.new_controller, controller2)

        # disconnecting the controller
        self._foo.reset()
        self._foo.controller = None
        self.failUnlessEqual(self._foo.controller, None)
        self.failUnlessEqual(self._foo.old_controller, controller2)
        self.failUnlessEqual(self._foo.new_controller, None)

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

        def set_controller(view, controller):
            view.controller = controller

        controller = BarController()
        self.failUnlessRaises(ControllerNotSupported, set_controller, self._foo, controller)
        self.failUnlessEqual(self._foo.controller, None)
        self.failUnlessEqual(self._foo.old_controller, False)
        self.failUnlessEqual(self._foo.new_controller, False)

    # TODO: good candidate for generic testing of custom Views
    def test_supported_controllers(self):
        self.assertEquals(type(self._foo.supported_controllers), tuple)
        for s in self._foo.supported_controllers:
            self.assertEquals(type(s), str)

        def set_controller(view, controller):
            view.controller = controller

        # assigning a supported controller should work
        controller = FooController()
        set_controller(self._foo, controller)

        # assigning a non supported controller should raise an exception
        controller = BarController()
        self.failUnlessRaises(ControllerNotSupported, set_controller, self._foo, controller)


    def test_bindings_controller_set_first(self):
        cfg = config.Config('no_file', MAPPINGS)
        frontend = Frontend(cfg)
        self._foo.frontend = frontend

        # connect a supported controller itself connected to a model
        controller = FooController()
        controller.initialize()
        model = FooModel()
        controller.model = model
        self._foo.controller = controller

        # adding bound attributes to the controller
        # ... a controller
        child_controller = BarController()
        child_controller.initialize()
        child_controller.model = BarModel()

        controller.controller_child1 = child_controller
        self.failUnless(isinstance(self._foo.view_child1,
                                   BarView))

        # 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(controller, "controller_child1")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child1")
        """

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

        controller.controller_child1 = "test"
        self.failUnlessEqual(self._foo.view_child1, "test")

        # ... or even None
        controller.controller_child1 = None
        self.failUnlessEqual(self._foo.view_child1, None)
       

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

        # connect a supported controller
        controller = FooController()
        self._foo.controller = controller

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

        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child1")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child2")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child3")

    def test_bindings_controller_set_after(self):
        cfg = config.Config('no_file', MAPPINGS)
        frontend = Frontend(cfg)
        self._foo.frontend = frontend

        # create a supported controller but does not connect it with the view
        # yet
        controller = FooController()
        controller.initialize()
        model = FooModel()
        controller.model = model

        # adding bound attributes to the controller
        # ... a controller
        child_controller = BarController()
        child_controller.initialize()
        child_controller.model = BarModel()

        controller.controller_child1 = child_controller
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child1")
        # connect the controller
        self._foo.controller = controller
        self.failUnless(isinstance(self._foo.view_child1,
                                   BarView))


        # disconnect the controller
        self._foo.controller = None
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child1")


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

        # connect the controller
        self._foo.controller = controller
        self.failUnlessEqual(self._foo.view_child1, "test")



    def test_bindings_without_frontend(self):
        # connect a supported controller itself connected to a model
        controller = FooController()
        controller.initialize()
        model = FooModel()
        controller.model = model
        self._foo.controller = controller

        # without a frontend adding a child controller should fail
        self.failUnlessRaises(Exception, setattr, controller, "controller_child1",
                              BarController())

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

        controller.controller_child1 = "test"
        self.failUnlessEqual(self._foo.view_child1, "test")

        # ... or even None
        controller.controller_child1 = None
        self.failUnlessEqual(self._foo.view_child1, None)


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

        # connect a supported controller
        controller = FooController()
        self._foo.controller = controller

        # adding bound attributes to the controller
        # ... a controller
        child_controller = BarController()
        child_controller.initialize()
        child_controller.model = BarModel()

        controller.controller_child2 = child_controller
        self.failUnless(isinstance(self._foo.view_child2,
                                   BarView))
        self.failUnless(isinstance(self._foo.view_child3,
                                   BarView))


        # disconnect the controller
        self._foo.controller = None
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child2")
        self.failUnlessRaises(AttributeError, getattr, self._foo,
                              "view_child3")

