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


"""
Media data access support
"""


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

from elisa.core import common, component, media_uri
from elisa.core.utils import network
from elisa.core.application import ComponentsLoadedMessage
from elisa.base_components.media_provider import MediaProvider
from elisa.extern import upnp_content_directory
from twisted.internet import defer, threads

plugin_registry = common.application.plugin_registry

name = 'coherence:coherence_device_message'
CoherenceDeviceMessage = plugin_registry.get_component_class(name)

name = 'base:local_network_location_message'
LocalNetworkLocationMessage = plugin_registry.get_component_class(name)

class UPnPMedia(MediaProvider):
    """
    """
    
    name = "upnp_media"

    def __init__(self):
        MediaProvider.__init__(self)
        self.cache = {}
        self.device_cache = {}

    def initialize(self):
        MediaProvider.initialize(self)
        common.application.bus.register(self._bus_message_received,
                                        ComponentsLoadedMessage)

    def _bus_message_received(self, msg, sender):
        import louie
        
        msg = CoherenceDeviceMessage('Coherence.UPnP.ControlPoint.MediaServer.detected',
                                     self._detected_media_server,
                                     louie.Any)
        common.application.bus.send_message(msg)

        msg = CoherenceDeviceMessage('Coherence.UPnP.ControlPoint.MediaServer.removed',
                                     self._removed_media_server,
                                     louie.Any)
        common.application.bus.send_message(msg)

    def _service_in_db(self, control_url):
        control_urls = [ v[0] for v in self.device_cache.values() ]
        return control_url in control_urls

    def _get_upnp_target(self, uri):
        new_uri = media_uri.MediaUri(uri)
        if uri.scheme == 'upnp':
            new_uri.scheme = 'http'
        return new_uri

    def _get_control_url(self, upnp_uri):
        url = media_uri.MediaUri(upnp_uri)
        url.fragment = u''
        return url

    def _cache_enabled(self):
        return True

    def _detected_media_server(self, client, usn):
        device = client.device
        friendly_name = device.get_friendly_name()

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

        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):
                #service.subscribe()
                control_url = service.get_control_url()
                if not self._service_in_db(control_url):
                    self.device_cache[usn] = (control_url, friendly_name)
                    dfr = self._browse(client, 0, requested_count=0,
                                       friendly_name=friendly_name,
                                       parent_id=-1)
                    dfr.addCallback(self._build_menus, friendly_name,
                                    control_url, 0)
                else:
                    self._build_menus(None, friendly_name, control_url, 0)

    def _removed_media_server(self, usn):
        if usn in self.device_cache:
            control_url, friendly_name = self.device_cache[usn]
            del self.device_cache[usn]
            self.info("Device %s disappeared", friendly_name)
            uri = "upnp:%s#0" % control_url[5:]
            action_type = LocalNetworkLocationMessage.ActionType.LOCATION_REMOVED
            msg = LocalNetworkLocationMessage(action_type,
                                                   str(friendly_name),
                                                   'upnp', uri)
            common.application.bus.send_message(msg)

    def _browse(self, client, container_id, starting_index=0,
                requested_count=0, friendly_name="", parent_id=None):
        """ Create an UPnP client and return a Deferred
        """
        try:
            client = client.content_directory
        except:
            pass
        control_url = client.url

        self.debug("Browsing %r", control_url)

        if hasattr(client, 'new_browse'):
            browse = client.new_browse
        else:
            browse = client.browse
            
        dfr = browse(container_id, starting_index=starting_index,
                     requested_count=requested_count)
        dfr.addCallback(self._add_to_cache, container_id, control_url,
                        friendly_name, client, parent_id)
        return dfr

    def _add_to_cache(self, results, container_id, control_url, friendly_name,
                      client, parent_id, child=None):
        if not child:
            root = upnp_content_directory.buildHierarchy(results['items'],
                                                         container_id,
                                                         friendly_name, False)
            self.store_in_cache(control_url, container_id, friendly_name,
                                root, client)
            if container_id != 0:
                root.parentID = parent_id

        else:
            root = child



        self.store_in_cache(control_url, root.id, friendly_name, root,client)
        for child in root.children:
            if isinstance(child, upnp_content_directory.Folder):
                self._add_to_cache(None, container_id, control_url,
                                   friendly_name, client, parent_id, child=child)
            else:
##                 for url in child.urls.keys():
##                     self.store_in_cache(url,None,
##                                         friendly_name, child,client)
                self.store_in_cache(control_url, child.id, friendly_name,
                                    child,client)
        return root.children

    def _get_cached_item_with_id(self, id):
        for item_id, (uri, item, client) in self.cache.iteritems():
            if item.id == id:
                return (uri, item.name, item)
        return (None, None, None)

    def store_in_cache(self, control_url, container_id, friendly_name, data,
                       client=None):
        if container_id is not None:
            uri = "%s#%s" % (control_url, container_id)
        else:
            uri = control_url
        #data.parentID = container_id
        #if uri not in self.cache or (len(data.children) > len(self.cache[uri][1].children) and self.cache[uri][1].children != data.children):
        self.debug("Storing %s (%s) to cache", uri, data.name)#, data.parentID)
        self.cache[uri] = (friendly_name, data,client)

    def get_from_cache(self, control_url, container_id):
        uri = "%s#%s" % (control_url, container_id)
        return self.cache.get(uri,(None,None,None))

    def _build_menus(self, results, friendly_name, control_url, container_id):
        """
        Build a menu according browsing results and dispatch it to the plugins
        """
        upnp_uri = "upnp:%s#%s" % (control_url[5:], container_id)
        #new_device.emit(str(friendly_name), upnp_uri, None)
        action_type = LocalNetworkLocationMessage.ActionType.LOCATION_ADDED
        msg = LocalNetworkLocationMessage(action_type, str(friendly_name),
                                               'upnp', upnp_uri,
                                               theme_icon='network_share_icon')
        common.application.bus.send_message(msg)

    def scannable_uri_schemes__get(self):
        # TODO: remove this method, when we have a http media_provider
        #       => grant scan access to the media_scanner
        return {}

    def supported_uri_schemes__get(self):
        return {'upnp': 0}

    def _blocking_get_media_type(self, uri):
        file_type = 'directory'
        mime_type = ''
        name, container, dummy = self.cache.get(str(self._get_upnp_target(uri)),
                                                (None,None, None))
        if isinstance(container, upnp_content_directory.Item):
            file_type = container.get_media_type()
                        
        media_type = {'file_type': file_type, 'mime_type': mime_type}
        return media_type

    def get_real_uri(self, uri):
        real_uri = None
        name, container, dummy = self.cache.get(str(self._get_upnp_target(uri)),
                                                (None,None, None))
        if isinstance(container, upnp_content_directory.Item):
            real_uri = media_uri.MediaUri(self._get_best_uri(container))
        return real_uri
    

    def _blocking_is_directory(self, uri):
        is_dir = True
        upnp_uri = self._get_upnp_target(uri)
        friendly_name, folder, client = self.cache.get(upnp_uri,(None,None,
                                                                 None))
        if folder:
            is_dir = isinstance(folder, upnp.content_directory.Folder)
        return is_dir
    
    def has_children_with_types(self, uri, media_types):
        dfr = self.get_direct_children(uri, [])

        def check_media_type(media_type):
            file_type = media_type.get('file_type')
            return file_type in media_types

        def final(result):
            has_children = False
            for dummy, r in result:
                if r:
                    has_children = True
                    break
            return has_children

        def check_children(result):
            dfrs = []
            for child in result:
                d = self.get_media_type(child[0])
                d.addCallback(check_media_type)
                dfrs.append(d)
            dfr2 = defer.DeferredList(dfrs)
            dfr2.addCallback(final)
            return dfr2

        dfr.addCallback(check_children)
        return dfr


    def get_direct_children(self, uri, children):
        dfr = defer.Deferred()
        uri = self._get_upnp_target(uri)
        control_url = self._get_control_url(uri)
        friendly_name, folder, client = self.cache.get(str(uri),(None,None,None))

        if folder and isinstance(folder, upnp_content_directory.Folder):
            if folder.children:
                folder_children = folder.children
                self._process_children(folder_children, control_url, children)
                dfr.callback(children)
            else:
                dfr2 = self._browse(client, folder.id,
                                    friendly_name=folder.name,
                                    parent_id=folder.parentID)
                dfr2.addCallback(self._process_children, control_url, children,
                                 dfr)
        else:
            dfr.callback(children)
        return dfr

    def _process_children(self, folder_children, control_url, children,
                          dfr=None):
        for child in folder_children:
            child_uri = "upnp:%s#%s" % (control_url[5:], child.id)
            is_dir = isinstance(child, upnp_content_directory.Folder)
            
            if is_dir:
                child_type = 'directory'
            else:
                child_type = 'file'

            child_uri = media_uri.MediaUri(child_uri)
            if isinstance(child.name, str):
                child.name = child.name.decode('utf-8')
            child_uri.label = child.name.encode('utf-8')
            children.append((child_uri,{}))

        if dfr:
            dfr.callback(children)

        return children

    def _get_best_uri(self, item):
        my_address = network.get_host_address()
        child_uri = None
        for uri, protocolInfo in item.urls_by_protocol.iteritems():
            remote_protocol,remote_network,remote_content_format,dummy = protocolInfo.split(':')
            if remote_network == my_address:
                child_uri = uri
                break

        if not child_uri:
            urls = item.get_urls()
            keys = filter(lambda u: not u.startswith('file'), urls.keys())
            keys.sort()
            child_uri = keys[0]
        return child_uri

    def open(self, uri, mode=None):
        http_uri = media_uri.MediaUri(uri)
        http_uri.scheme = 'http'
        return common.application.media_manager.open(http_uri, mode=mode)
