# -*- 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.
#
# Author: Philippe Normand <philippe@fluendo.com>

"""
Resource provider able to list UPnP services.
"""

from twisted.internet import defer, task

from coherence import __version_info__ as coherence_version
from coherence.base import Coherence

if coherence_version < (0, 5, 8):
    import louie
else:
    import coherence.extern.louie as louie

from elisa.core import common
from elisa.core import media_uri
from elisa.core.components.resource_provider import ResourceProvider

from elisa.core.components.model import Model

from elisa.plugins.base.models.network import NetworkServiceModel
from elisa.plugins.base.messages.network import NetworkServiceAppearedMessage
from elisa.plugins.base.messages.network import NetworkServiceDisappearedMessage

class CoherenceResource(ResourceProvider):

    """
    The Coherence resource_provider is in charge of detecting UPnP
    services available on the local network. It currently only detects
    MediaServers.

    When a new service is detected a L{NetworkServiceAppearedMessage}
    message is sent over the bus. If a service disappears, a
    L{NetworkServiceDisappearedMessage} is sent.

    This resource_provider depends on the
    L{elisa.plugins.coherence.coherence_service} Service so as to
    correctly connect to the signals sent by Coherence when a new
    service is detected/removed.
    """

    supported_uri = 'coherence://.*'

    _known_media_servers = {}

    def initialize(self):
        coherence_config = {'logmode': 'none',
                            'controlpoint': 'yes',
                            'plugins': {}}
        try:
            coherence_instance = Coherence(coherence_config)
        except:
            return defer.fail()

        louie.connect(self._detected_media_server,
                    'Coherence.UPnP.ControlPoint.MediaServer.detected', louie.Any)
        louie.connect(self._removed_media_server,
                'Coherence.UPnP.ControlPoint.MediaServer.removed', louie.Any)

        return defer.succeed(self)

    def _detected_media_server(self, client, udn):
        device = client.device

        self.info('Browsing services on device %s' % device.friendly_name)
        self.debug('Device USN is: %r', device.usn)
        self.debug('Device UDN is: %r', udn)

        services = device.get_services()
        self.info("Device has %s services" % len(services))
        for service in services:
            self.info("Service type: %s" % service.get_type())
            cds1 = "urn:schemas-upnp-org:service:ContentDirectory:1"
            cds2 = "urn:schemas-upnp-org:service:ContentDirectory:2"
            if service.get_type() in (cds1, cds2):
                self.info("Adding service %r", service)

                # fill a model, wrap it in a Message and send the message into the wild
                model = self._model_from_service(service)
                msg = NetworkServiceAppearedMessage(model)
                common.application.bus.send_message(msg)

                self._known_media_servers[unicode(model.elisa_uri)] = (udn, service)

    def _removed_media_server(self, udn=None):
        if udn:
            for elisa_uri in self._known_media_servers.keys():
                known_udn, service = self._known_media_servers[elisa_uri]
                if known_udn == udn:
                    self.info("Removing service %r", service)
                    del self._known_media_servers[elisa_uri]

            msg = NetworkServiceDisappearedMessage(udn)
            common.application.bus.send_message(msg)

    def _model_from_service(self, service):
        device = service.get_device()

        # strip "uuid:"
        service_uuid = service.get_sid()[5:]
        uri = media_uri.MediaUri('upnp://%s?sid=%s' % (device.get_uuid(), service_uuid))

        model = NetworkServiceModel()
        model.uid = 'uuid:%s' % device.get_uuid()
        model.name = device.friendly_name
        model.type = service.get_id()
        model.elisa_uri = uri
        return model

    def _get_media_servers(self, model):

        def load_models(model):
            for service_uri, (udn, service) in self._known_media_servers.iteritems():
                m = self._model_from_service(service)
                model.media_servers.append(m)
                yield model

        dfr = task.coiterate(load_models(model))
        dfr.addCallback(lambda gen: model)
        return dfr

    def get(self, uri, context_model=None):
        """
        Get a list of specific UPnP services. Right now only media_servers are
        known.

        C{coherence://media_servers} -> list of
        L{elisa.plugins.base.network.NetworkServiceModel} wrapped in the
        I{media_servers} attribute of a L{Model} instance.

        @todo: handle C{coherence://media_renderers}
        """
        model = None
        dfr = None

        if uri.host == 'media_servers':
            model = Model()
            model.media_servers = []
            dfr = self._get_media_servers(model)

        if not dfr:
            dfr = defer.fail(NotImplementedError())

        return model, dfr
