# -*- 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.
#
# Authors: Florian Boucault <florian@fluendo.com>
#          Benjamin Kampmann <benjamin@fluendo.com>

"""
Manage resource providers and allow to access resources specified by a
certain uri.
"""

from elisa.core.manager import Manager
from elisa.core.component import get_component_path

import re


class ResourceProviderNotFound(Exception):

    """
    Raised when the requested resource provider is not registered.
    """

    def __init__(self, path):
        Exception.__init__(self, 'Resource provider %s not registered' % path)


class NoMatchingResourceProvider(Exception):

    """
    Raised when no resource provider matches the given URI.
    """

    def __init__(self, uri):
        Exception.__init__(self, 'No resource provider found for URI %s' % uri)


class ResourceManager(Manager):

    """
    Provide access to resources through the use of C{ResourceProvider}
    components that make use of various data sources.
    """

    entry_point = 'elisa.core.components.resource_provider'

    def __init__(self):
        super(ResourceManager, self).__init__()

        # list of couples (compiled regular expression, resource provider)
        self._providers_uri_regexps = []

    def register_component(self, component):
        dfr = super(ResourceManager, self).register_component(component)

        def registered(result):
            if hasattr(component, 'supported_uri') and \
                                        len(component.supported_uri) is not 0:
                uri_regexp = re.compile(component.supported_uri)
                self._providers_uri_regexps.append((uri_regexp, component))

        dfr.addCallback(registered)
        return dfr

    def unregister_component(self, component):
        dfr = super(ResourceManager, self).unregister_component(component)

        def pop_regexp(result):
            # find the regular expression if in there
            for index, regexp_provider in \
                enumerate(self._providers_uri_regexps):
                regexp, provider = regexp_provider
                if provider is component:
                    # Found, remove it
                    self._providers_uri_regexps.pop(index)
                    break

        dfr.addCallback(pop_regexp)
        return dfr

    def get_resource_provider_by_path(self, path):
        """
        Return the registered resource provider that corresponds to the given
        component path.

        This method should not be called to gain direct access to a resource
        provider, it is meant to be called only by capabilities at
        initialization time (capabilities need a reference to their "parent"
        resource provider).

        @param path: the full component path for the resource provider
        @type path:  C{str}
        """
        for provider in self.components:
            if get_component_path(provider.__class__) == path:
                return provider
        raise ResourceProviderNotFound(path)

    def _get_resource_provider(self, uri):
        """
        Retrieve the ResourceProvider able to handle L{uri}.
        If multiple ResourceProviders support it, the first one found is
        returned.

        @param uri: location that will be passed to the resource provider
                    returned
        @type uri:  L{elisa.core.media_uri.MediaUri}

        @rtype:     L{elisa.core.components.resource_provider.ResourceProvider}

        @raise NoMatchingResourceProvider: in case no ResourceProvider has a
                                           matching regular expression
        """
        result = None
        for regexp, provider in self._providers_uri_regexps:
            match = regexp.match(unicode(uri))
            if match is not None:
                result = provider
                break

        if result is not None:
            self.debug("Using %r resource_provider to access %s", result.name,
                       uri)
            return result
        else:
            raise NoMatchingResourceProvider(uri)

    def _proxy(self, method_name, uri, *args, **kw):
        provider = self._get_resource_provider(uri)
        real_args = args + (kw,)
        self.debug("Calling %s.%s%r", provider.name, method_name, real_args)
        result = getattr(provider, method_name)(*args, **kw)
        return result

    
    def get(self, uri, context_model=None):
        """
        Return a resource that L{uri} is pointing to. A URI can point to
        any kind of resource. Resources are returned as models.

        The model that is returned does not always already contain all the
        resource. The deferred is fired when the resource loading is complete.

        @param uri:           URI pointing to the resource
        @type uri:            L{elisa.core.media_uri.MediaUri}
        @param context_model: the URI often comes from a certain context.
                              For example a URI pointing to a MusicAlbum can
                              come from a Model that could contain the album
                              cover or the album name. If the context_model is
                              provided the resource_provider should try to
                              reuse its data if possible.
        @type context_model:  L{elisa.core.components.model.Model}

        @return:              a new model and a deferred fired when the
                              model is fully loaded
        @rtype:               tuple of L{elisa.core.components.model.Model}
                              L{elisa.core.utils.defer.Deferred}

        @raise NoMatchingResourceProvider: in case no ResourceProvider has a
                                           matching regular expression for
                                           L{uri}
        """
        return self._proxy("get", uri, uri, context_model)

    def post(self, uri, **parameters):
        """
        Update the resource pointed by L{uri} with L{parameters}.

        @param uri:        URI pointing to the resource to update
        @type uri:         L{elisa.core.media_uri.MediaUri}
        @param parameters: parameters of the resource that should be updated

        @return:           a deferred fired when the parameters got posted
        @rtype:            L{elisa.core.utils.defer.Deferred}

        @raise NoMatchingResourceProvider: in case no ResourceProvider has a
                                           matching regular expression for
                                           L{uri}
        """
        return self._proxy("post", uri, uri, **parameters)

    def put(self, source_uri, container_uri, source_model=None, **kwargs):
        """
        Put one resource into another. Both resources are identified with URIs.

        @param source_uri:    URI pointing to the resource that should be put
                              into the other one
        @type source_uri:     L{elisa.core.media_uri.MediaUri}
        @param container_uri: URI pointing to the resource that should receive
                              the resource
        @type container_uri:  L{elisa.core.media_uri.MediaUri}
        @param source_model:  Often the resource behind the L{source_uri}
                              already exists as a Model. In order to avoid
                              retrieving the resource pointed by L{source_uri}
                              that model can be passed to the put request
        @type source_model:   L{elisa.core.components.model.Model}
        @param kwargs:        resource provider specific keyword options

        @return:              a deferred fired when the resource got put
        @rtype:               L{elisa.core.utils.defer.Deferred}

        @raise NoMatchingResourceProvider: in case no ResourceProvider has a
                                           matching regular expression for the
                                           L{container_uri}
        """
        return self._proxy("put", container_uri,
                                  source_uri,
                                  container_uri,
                                  source_model,
                                  **kwargs)

    def delete(self, uri):
        """
        Delete the resource that L{uri} is pointing to.

        @param uri: URI pointing to the resource that should be deleted
        @type uri:  L{elisa.core.media_uri.MediaUri}

        @return:    a deferred fired when the resource got deleted
        @rtype:     L{elisa.core.utils.defer.Deferred}

        @raise NoMatchingResourceProvider: in case no ResourceProvider has a
                                           matching regular expression for
                                           L{uri}
        """
        return self._proxy("delete", uri, uri)
