# 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 twisted.trial import unittest
from elisa.core.tests.elisa_test_case import ElisaTestCase
from elisa.core import plugin_registry
from elisa.core import config
from elisa.core import plugin
from elisa.core.component import Component, UnMetDependency
from elisa.base_components.activity import Activity
import inspect, os, platform
import tempfile, shutil
from twisted.internet import defer

class SomeActivity(Activity):
    name = 'some_activity'

class DeferredInitializeActivity(Activity):
    name = 'deferred_initialize_activity'

    def initialize(self):
        return defer.succeed(None)

class SomePlugin(plugin.Plugin):
    name = 'test'
    components = {
            'some_activity': {'path': SomeActivity},
            'deferred_initialize_activity': {'path': DeferredInitializeActivity}
        }

class FooPlugin(plugin.Plugin):
    name = 'foo'

class MissingDepsPlugin(plugin.Plugin):
    name = 'missing_deps'
    plugin_dependencies = ['unexistent_plugin']
    components = {'missing_deps_component':
                   {'path': 'fake:MissingDepsComponent',
                    'component_dependencies': ['unexistent_plugin:component']},
                  'missing_external_deps_component':
                   {'path': 'fake:MissingDepsComponent',
                    'external_dependencies': ['fjezivfihahfue']}
                 }

SOME_PLUGIN="""\
from elisa.core import plugin
from elisa.base_components import activity

class SomeActivity(activity.Activity):
    name = 'some_activity'

class SomePlugin(plugin.Plugin):
    name = 'test'
    components = {'some_activity':{'path':SomeActivity}}
"""

FOO_PLUGIN="""\
from elisa.core import plugin

class FooPlugin(plugin.Plugin):
    name = 'foo'
"""

class TestPluginRegistry(ElisaTestCase):


    def setUp(self):
        ElisaTestCase.setUp(self)
        self._plugins_dir = tempfile.mkdtemp()

    def tearDown(self):
        ElisaTestCase.tearDown(self)
        shutil.rmtree(self._plugins_dir)


    def test_plugin_registration(self):
        """ Check plugin classes registering/unregistering works
        """
        manager = plugin_registry.PluginRegistry(config.Config('empty.conf'))

        self.assertEquals(manager.plugins, {})
        self.assertEquals(manager.plugin_classes, {})

        manager.register_plugin(SomePlugin)
        self.assertEquals(manager.plugin_classes, {'test': SomePlugin})

        manager.unregister_plugin(SomePlugin)
        self.assertEquals(manager.plugin_classes, {})

    def test_plugin_loading(self):
        """ Check the plugins declared in the config are loaded
        """
        
        if platform.system() == 'Windows':
            raise unittest.SkipTest("Does not works under windows, need investigation")
         
        conf = config.Config('empty.conf')
        os.environ['ELISA_PLUGIN_PATH'] = self._plugins_dir
        section = {'plugins':['test', 'foo']}
        conf.set_section('plugin_registry', section)

        init = os.path.join(self._plugins_dir, '__init__.py')
        open(init,'w').close()


        # write plugins code to files
        f = open(os.path.join(self._plugins_dir, 'foo.py'),'w')
        f.write(FOO_PLUGIN)
        f.close()

        f = open(os.path.join(self._plugins_dir, 'some.py'),'w')
        f.write(SOME_PLUGIN)
        f.close()

        manager = plugin_registry.PluginRegistry(conf)

        # load plugins from files
        manager.load_plugins()

        self.assertEquals(manager.plugins, {})
        plugins = manager.plugin_classes.keys()
        self.failUnless('test' in plugins)
        self.failUnless('foo' in plugins)

        test_plugin = manager.plugin_classes['test']
        foo_plugin = manager.plugin_classes['foo']
        self.assertEquals(test_plugin.__name__, 'SomePlugin')
        self.assertEquals(foo_plugin.__name__, 'FooPlugin')

    test_plugin_loading.skip = "The plugin_registry can no longer load plugins from python modules"

    def test_plugin_instantiation(self):
        """ Check Plugin instances are lazily referenced by the manager
        """
        conf = config.Config('empty.conf')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        plugin1 = manager.get_plugin_with_name('test')
        plugin2 = manager.get_plugin_with_name('test')

        self.failUnless(isinstance(plugin1, SomePlugin))
        self.failUnless(plugin1 == plugin2)

    def test_plugin_instantiation_failures(self):
        """ Test several error cases of plugin instantiation.
        """
        conf = config.Config('empty.conf')
        manager = plugin_registry.PluginRegistry(conf)

        manager.register_plugin(MissingDepsPlugin)
        plugin = manager.get_plugin_with_name('missing_deps')

        self.failUnless(isinstance(plugin, MissingDepsPlugin))

        self.failUnlessRaises(UnMetDependency,
                              manager.check_interplugin_dependencies, plugin)

        self.failUnlessRaises(UnMetDependency,
                              manager.check_intercomponent_dependencies,
                              plugin, 'missing_deps_component')

    def test_component_creation(self):
        """ Check Components are correctly instantiated by the manager
        """
        conf = config.Config('empty.conf')
        conf.set_option('activities', ['test:some_activity',],
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        activity = manager.create_component('test:some_activity')
        self.failUnless(isinstance(activity, Activity))
        self.failUnless(isinstance(activity, SomeActivity))

    def test_component_creation_deferred_initialize(self):
        """Check components that return a deferred from Component.initialize()
        """
        conf = config.Config('empty.conf')
        conf.set_option('activities', ['test:deferred_initialize_activity',],
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        def create_component_done(activity):
            self.failUnless(isinstance(activity, Activity))
            self.failUnless(isinstance(activity, DeferredInitializeActivity))

        dfr = manager.create_component('test:deferred_initialize_activity')
        dfr.addCallback(create_component_done)

        return dfr

    def test_component_creation_failures(self):
        conf = config.Config('empty.conf')
        manager = plugin_registry.PluginRegistry(conf)

        manager.register_plugin(MissingDepsPlugin)

        self.failUnlessRaises(UnMetDependency,
                              manager.create_component,
                             'missing_deps:missing_external_deps_component')

    def test_multiple_component_creation(self):
        """ Check the manager can create multiple instance of the same component
        """
        conf_activities = ['test:some_activity:1',
                           'test:some_activity:2']
        conf = config.Config('empty.conf')
        conf.set_option('activities', conf_activities,
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        activities = []
        index = 1
        for activity_path in conf_activities:
            component = manager.create_component(activity_path)
            self.assertEqual(component.id, index)
            self.failUnless(isinstance(component, Activity))
            self.failUnless(isinstance(component, SomeActivity))
            index += 1
            activities.append(component)

        self.assertEqual(len(activities), 2)

    def test_create_component(self):
        """
        """
        conf = config.Config('empty.conf')
        conf.set_option('activities', ['test:some_activity:1',
                                       'test:some_activity:2',
                                       ],
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        act = manager.create_component('test:some_activity')
        self.failUnless(act)
        self.failUnless(isinstance(act, Activity))
        self.failUnless(isinstance(act, SomeActivity))

    def test_get_component_class(self):
        """
        """
        conf = config.Config('empty.conf')
        conf.set_option('activities', ['test:some_activity:1',
                                       'test:some_activity:2',
                                       ],
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)
        manager.register_plugin(SomePlugin)

        class App:
            pass

        app = App()
        app.plugin_registry = manager

        from elisa.core import common
        common.set_application(app)

        c = plugin_registry.get_component_class('test:some_activity')
        self.failUnless(c)
        self.failUnless(issubclass(c, Activity))
        self.assertEqual(c, SomeActivity)


    def test_split_component_path(self):
        """
        """
        conf = config.Config('empty.conf')
        conf.set_option('activities', ['test:some_activity:1',
                                       'test:some_activity:2',
                                       ],
                        'plugin_registry')
        manager = plugin_registry.PluginRegistry(conf)

        self.assertEqual((), manager._split_component_path('Invalid'))
        self.assertEqual(('A', 'B', 0, 'A:B'), manager._split_component_path('A:B'))
        self.assertEqual(('A', 'B', 1, 'A:B:1'), manager._split_component_path('A:B:1'))
        self.assertEqual((), manager._split_component_path('A:B:c'))
