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


from elisa.core import common
from elisa.core.log import Loggable
from elisa.core.utils import misc

from twisted.internet import defer
from twisted.python import reflect

class ComponentError(Exception):
    """
    Generic exception raised when Component related error
    happens. Specialized exceptions inherit from it.

    @ivar component_name: the name of the Component which issued the error
    @type component_name: string
    @ivar error_message:  human (or developer) readable explanation of the error
    @type error_message:  string
    """

    def __init__(self, component_name, error_message=None):
        Exception.__init__(self)
        self.component_name = component_name
        self.error_message = error_message

    def __str__(self):
        return "In component %s: %s" % (self.component_name, self.error_message)

class UnsupportedPlatform(ComponentError):
    pass

class InitializeFailure(ComponentError):
    pass

# deprecate this later
UnSupportedPlatform = UnsupportedPlatform

def get_component_path(component_class):
    qual = reflect.qual(component_class)

    parts = qual.split('.')
    module = '.'.join(parts[:-1])
    klass = parts[-1]

    qual = '%s:%s' % (module, klass)
    
    return qual

def get_component_config_section_name(component_class):
    section_name = get_component_path(component_class)

    # strip elisa.plugins.
    if section_name.startswith('elisa.plugins.'):
        section_name = section_name[len('elisa.plugins.'):]

    return section_name

class Component(Loggable):

    default_config = {}
    config_doc = {}
    _name = None

    # compute Component.path using a descriptor (the alternative is using a
    # metaclass but we are already using some metaclasses in our object
    # hierarchy so for the moment a descriptor is easier).
    class PathDescriptor(object):
        def __get__(self, instance, klass):
            try:
                return klass._path
            except AttributeError:
                klass._path = get_component_path(klass)
                return klass._path
    
    path = PathDescriptor()
    del PathDescriptor

    @classmethod
    def create(cls, config_section=None, **kwargs):
        """
        Create and initialize the component.

        A component should always be created by calling C{Component.create} and
        not by instantiating the component class directly.

        @param config_section: the configuration section to set for the
                               component
        @type config_section: L{elisa.core.config.Config}
        @return: an instance of the component
        @rtype: L{elisa.core.component.Component} or a subclass
        """
        if config_section is None and cls.default_config:
            # get the config from the application
            config = common.application.config
            section_name = get_component_config_section_name(cls)
            config_section = common.application.config.get_section(section_name)
            
            if config_section is None:
                # there's no section for this component, create one
                config.set_section(section_name,
                        cls.default_config, doc=cls.config_doc)
                config_section = config.get_section(section_name)

        for key, value in cls.default_config.iteritems():
            config_section.setdefault(key, value)

        instance = cls()
        instance.config = config_section

        return instance.initialize(**kwargs)

    def initialize(self, **kwargs):
        """
        Initialize the component.

        This method is called by C{Component.create} to finish the
        initialization of a component.

        @return: a deferred called when a component is fully initialized
        @rtype: L{twisted.internet.defer.Deferred}
        """
        return defer.succeed(self)

    def clean(self):
        """
        Clean the component.

        This method is called when a component is not needed anymore to clean
        its state.

        @return: a deferred called when the component has finished cleaning its
                 state
        @rtype: L{twisted.internet.defer.Deferred}
        """
        return defer.succeed(self)

    def name__get(self):
        if not self._name:
            self._name = misc.un_camelify(self.__class__.__name__)
        return self._name

    def name__set(self, value):
        self._name = value

    name = property(fget=name__get, fset=name__set)
