# -*- 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.

"""
Metadata parsing of medias using metadata_provider components
"""

__maintainer__ = 'Philippe Normand <philippe@fluendo.com>'
__maintainer2__ = 'Benjamin Kampmann <benjamin@fluendo.com>'

from elisa.core import common
from elisa.core import manager
from elisa.base_components.metadata_provider import MetadataError
from twisted.internet import defer, task, threads
from twisted.python import failure

class MetadataManager(manager.Manager):
    """
    This manager is responsible for media metadata parsing. It creates
    and handles the L{elisa.base_components.metadata_provider.MetadataProvider}
    components.
    """
    def __init__(self):
        manager.Manager.__init__(self)
        self._sorted_providers = []

    def register_component(self, provider):
        manager.Manager.register_component(self, provider)

        # insertion sort: sorted insertion in the list of already sorted
        # MetadataProviders; the key is the rank
        rank = provider.get_rank()
        for i in range(len(self._sorted_providers)):
            current_rank = self._sorted_providers[i].get_rank()
            if rank <= current_rank:
                self._sorted_providers.insert(i, provider)
                return

        self._sorted_providers.append(provider)

    def unregister_component(self, provider):
        manager.Manager.unregister_component(self, provider)

        self._sorted_providers.remove(provider)

    def stop(self):
        manager.Manager.stop(self)

        self._sorted_providers = []

    def get_metadata(self, metadata, low_priority=False):
        """ Retrieve the metadata of the media located at given URI.

        Metadata is returned as a dictionnary mapping keys depending
        on the content type (example: artist, album, track for 'audio'
        content-type) to actual metadata values.
        If you do a request for metadata, the value of this has to be a None
        Object.

        @param metadata:     a dicitionary containing the already known
                             metadata and empty values for the requested tags
        @type metadata:      dict
        @rtype:              L{twisted.internet.defer.Deferred}
        """
        self.debug("Looking for metadata %s in providers %s",
                metadata, self._sorted_providers)

        if self._components == []:
            # FIXME: we don't have an exception here
            self.warning("No metadata provider available")
            return defer.fail(Exception('No metadata provider available'))

        def get_metadata_done(result, provider):
            self.log('done using metadata provider %s', provider.name)

            return result
        
        def get_metadata_failure(failure, provider):
            failure.trap(MetadataError)

            # MetadataErrors are not critical, just log them
            self.debug('error using metadata provider %s: %s',
                    provider.name, failure.getBriefTraceback())

        def get_metadata_critical_failure(failure, provider):
            # log the error and continue
            path = common.application.log_failure(failure)
            self.warning('critical failure using metadata provider %s; full output at %s',
                         provider.name, path)
            return None

        def metadata_providers_iter(metadata):
            for provider in self._sorted_providers:
                if None not in metadata.values():
                    # all metadata keys filled
                    break
                
                if not provider.able_to_handle(metadata):
                    self.log('%s not able to handle %r',
                            provider.name, metadata)
                    continue

                self.log('Using metadata provider %s', provider.name)
                dfr = None
                try:
                    dfr = provider.get_metadata(metadata, low_priority)
                except:
                    # some (possibly externally developed) provider is
                    # completely borked and raises an exception instead of
                    # returning a failure
                    error = failure.Failure()
                    dfr = defer.fail(error)

                dfr.addCallback(get_metadata_done, provider)
                dfr.addErrback(get_metadata_failure, provider)
                dfr.addErrback(get_metadata_critical_failure, provider) 
                
                yield dfr

        def metadata_providers_iter_done(iterator, metadata):
            self.log('got metadata %r' % metadata)

            return metadata

        dfr = task.coiterate(metadata_providers_iter(metadata))
        dfr.addCallback(metadata_providers_iter_done, metadata)

        return dfr
