# -*- coding: utf-8 -*-
# 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.

"""
Component instances management
"""


__maintainer__ = 'Philippe Normand <philippe@fluendo.com>'

from elisa.core import log
from elisa.core.component import ComponentError, InitializeFailure, UnMetDependency
from elisa.core.plugin_registry import ComponentNotFound
from elisa.core.utils import classinit
from elisa.core import common
from twisted.internet import defer, task

class AlreadyRegistered(Exception):
    pass

class CannotUnregister(Exception):
    pass

class Manager(log.Loggable):
    """ A Manager is a Component container

    Components can be registered and unregistered from the
    Manager.

    Optionnally the manager can implement start/stop methods if it
    needs to handle any kind of loop (example: media sources scanning,
    input events polling, etc). start/stop methods are called by the
    parent object (application).

    @ivar _components: Components currently registered in the manager
    @type _components: L{elisa.core.component.Component} list
    """

    # Allows property fget/fset/fdel/doc overriding
    __metaclass__ = classinit.ClassInitMeta
    __classinit__ = classinit.build_properties

    def __init__(self):
        """ Initialize the _components instance variable and the
        Manager as a Loggable object.
        """
        log.Loggable.__init__(self)
        self.debug("Creating")

        self._components = []

    def initialize(self):
        """
        This function is called when the application has initialized completely
        """
        return self._load_providers()

    def _load_providers(self):
        """ Ask the plugin registry to create all the providers
        components defined in section 'general' of the config
        """
        # FIXME: not a very clean way to deduce *_providers from *_manager
        providers_type = self.name[:-8] + "_providers"
        application = common.application
        providers_names = application.config.get_option(providers_type,
                                                        section='general',
                                                        default=[])

        plugin_registry = application.plugin_registry

        def load_providers_iter(loaded):
            def create_component_done(component):
                self.log("Loaded provider %s" % component.name)
                self.register_component(component)
                loaded.append(component)

                return component

            def create_component_failure(failure, provider_name):
                if failure.type == InitializeFailure:
                    self.warning("Initializing %s failed: %s." %
                                 (provider_name, failure.value) )
                elif failure.type == UnMetDependency:
                    self.warning("Missing dependency %s to create %s" %
                                    (failure.value, provider_name))
                else:
                    failure_path = application.log_failure(failure)
                    self.warning("Creating provider %s failed. A full traceback"\
                             " can be found at %s" %
                                (provider_name, failure_path) )
                # continue
                return

            for provider_name in providers_names:
                res = defer.maybeDeferred(plugin_registry.create_component,
                        provider_name)
                res.addCallback(create_component_done)
                res.addErrback(create_component_failure, provider_name)

                yield res

        def load_providers_iter_done(iter, loaded):
            self.debug("Loaded %s %s" % (len(loaded), providers_type))
        
            return self

        loaded = []
        res_dfr = task.coiterate(load_providers_iter(loaded))
        res_dfr.addCallback(load_providers_iter_done, loaded)

        return res_dfr

    def start(self):
        """ Start a loop or something to initialize the Manager. Can
        also look for new components to register in this method.

        Does nothing by default, override if needed.
        """

    def stop(self):
        """ Stop clean and remove all registered components.

            @rtype: L{twisted.internet.defer.DeferredList}
        """
        deferreds = []
        for component in self._components:
            deferreds.append(defer.maybeDeferred(component.clean))
        self._components = []

        return defer.DeferredList(deferreds)

    def component_registered(self, component):
        """ Check if a component is registed in the Manager or not

        @param component: the Component to register
        @type component:  L{elisa.core.component.Component}
        @rtype:           bool
        """
        return component in self._components

    def register_component(self, component):
        """ Register a new Component

        Store a new Component in our components list. Returns the
        result of the operation. If the component is already
        registered, don't register it twice.

        @param component: the Component to register
        @type component:  L{elisa.core.component.Component}

        @raise AlreadyRegistered : when the component has already been registered
        """

        if not self.component_registered(component):
            self.debug("Registering component %s" % component.path)
            self._components.append(component)
        else:
            self.debug("Component %s already registered" % component.path)
            raise AlreadyRegistered()


    def unregister_component(self, component):
        """ Unregister a component from the Manager

        Remove the Component instance from our components list if it's
        there. Returns the result of the operation.

        @param component: the Component to register
        @type component:  L{elisa.core.component.Component}

        @raise CannotUnregister : raised when the component cannot be removed
        """

        if self.component_registered(component):
            self.debug("Unregistering component %s" % component.path)
            self._components.remove(component)
        else:
            self.debug("Impossible to unregister component %s" % component.path)
            raise CannotUnregister()
