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

"""
DAAP Resource Provider
"""

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

from elisa.plugins.daap.daap_connection import DaapConnection

from elisa.plugins.daap.models import DaapDatabaseListModel, \
        DaapServerInfoModel, DaapSongListModel, DaapPlaylistListModel, \
        DaapPlaylistModel, DaapArtistModel, DaapArtistListModel, DaapSongModel, \
        DaapAlbumModel

from elisa.plugins.base.models.media import PlayableModel

from twisted.internet import task

class DaapResourceProvider(ResourceProvider):

    supported_uri = '^daap://.*'

    def __init__(self):
        super(DaapResourceProvider, self).__init__()
        self._connections = {}
        self._cache = {}

    def _store_in_cache(self, model, uri):
        self._cache[uri.host] = model
        return model

    def _get_artists(self, uri, connection, model):
        if uri.host not in self._cache:
            databases_uri = uri.join('databases')
            m, dfr = self.get(databases_uri)
            dfr.addCallback(self._parse_databases, connection, uri, model)
            dfr.addCallback(self._store_in_cache, uri)
        else:
            artists = self._cache[uri.host]
            dfr = defer.succeed(artists)
        return dfr

    def _get_cached_artist(self, uri, model, artist_name):

        def iterate_artists():
            for artist_model in self._cache[uri.host].artists:
                if artist_model.name == artist_name:
                    model.albums = artist_model.albums
                    break
                yield artist_model
            yield model

        if uri.host not in self._cache:
            root_uri = media_uri.MediaUri(uri)
            root_uri.path = ""
            m, dfr = self.get(root_uri)
            dfr.addCallback(lambda r: task.coiterate(iterate_artists()))
        else:
            dfr = task.coiterate(iterate_artists())
        dfr.addCallback(lambda generator: model)
        return dfr

    def _get_cached_album(self, uri, model, album_name):

        def iterate_albums():
            for album_model in self._cache[uri.host].albums:
                if album_model.name == album_name:
                    model.tracks = album_model.tracks
                    break
                yield album_model
            yield model

        dfr = task.coiterate(iterate_albums())
        dfr.addCallback(lambda generator: model)
        return dfr

    def _parse_artists(self, connection, uri, songs_model, artists_list_model):

        def parse():
            artists = {}
            albums = {}
            artists_list_model.albums = []
            for dummy, (song_list_model, db_uri) in songs_model:
                for song_model in song_list_model.songs:
                    artist_name = song_model.artist
                    album_name = song_model.album
                    if None in (artist_name, album_name):
                        continue
                    if artist_name not in artists:
                        artist = DaapArtistModel()
                        artist.elisa_uri = uri.join("artist/%s" % media_uri.quote(artist_name))
                        artist.name = artist_name

                        artists[artist_name] = artist
                        artists_list_model.artists.append(artist)
                    else:
                        artist = artists[artist_name]

                    if artist_name not in albums:
                        albums[artist_name] = {}

                    if album_name not in albums[artist_name]:
                        album = DaapAlbumModel()
                        album.album = album_name
                        # FIXME: needed for controller:
                        album.name = album_name
                        album.elisa_uri = uri.join("album/%s" % media_uri.quote(album_name))
                        album.artist = artist_name
                        artist.albums.append(album)
                        albums[artist_name][album_name] = album
                        artists_list_model.albums.append(album)
                    else:
                        album = albums[artist_name][album_name]

                    # FIXME: this playable_model can no longer be playable if
                    #        the user session expires
                    playable_model = self._get_playable_model(connection, db_uri,
                                                              song_model)
                    song_model.playable_model = playable_model

                    album.tracks.append(song_model)

                    yield song_model
                yield song_list_model
            yield artists_list_model

        dfr = task.coiterate(parse())
        dfr.addCallback(lambda generator: artists_list_model)
        return dfr

    def _get_playable_model(self, connection, db_uri, song_model):
        model = PlayableModel()
        playable_uri = db_uri.join(unicode(song_model.song_id))
        playable_uri.scheme = 'http'
        playable_uri.set_param('session-id', connection.session_id)
        playable_uri.del_param('meta')
        playable_uri.del_param('revision-id')
        model.uri = playable_uri
        model.title = song_model.name
        return model

    def _parse_databases(self, databases, connection, uri, model):

        dfrs = []
        for db in databases.databases:
            db_uri = uri.join("databases/%s/items" % db.database_id)
            m, dfr = self.get(db_uri)
            dfr.addCallback(lambda m: (m, db_uri))
            dfrs.append(dfr)


        dfr = defer.DeferredList(dfrs)
        dfr.addCallback(lambda songs_models: self._parse_artists(connection, uri,
                                                                 songs_models, model))
        return dfr

    def get(self, uri, context_model=None):
        """
        This method allows you to get a model for your uri. The
        C{context_model} is always ignored. The model has to fit to
        the given URI (see list below). If there is no port given in
        the URI this method uses the default port 3689.

        If there is no open connection to the given combination of server+port,
        this method creates a
        L{elisa.plugins.daap.daap_connection.DaapConnection}, tries to login
        and does the request afterwards.

        You always have to wait for request to be finished because it could
        raise a L{elisa.plugins.daap.daap_connection.LoginFailed} to inform you
        that it needs different login credentials. You should retry again with
        another password.

        For each response you do, the path is used to fetch the data on the
        given server. For each one of them the C{DaapConnection} is fetching
        all the information specified in the corresponding model (including
        filling all the lists). The different models you get for the different
        paths that you can have in the URI are::

            /server-info
                    L{elisa.plugins.daap.models.DaapServerInfoModel}
            /databases
                    L{elisa.plugins.daap.models.DaapDatabaseListModel}
            /databases/<id>/items
                    L{elisa.plugins.daap.models.DaapSongListModel}
            /databases/<id>/containers
                    L{elisa.plugins.daap.models.DaapPlaylistListModel}
            /databases/<id>/containers/<id>/items
                    L{elisa.plugins.daap.models.DaapPlaylistModel}

            /artists
                    L{elisa.plugins.daap.models.DaapArtistListModel}
            /artist/name
                    L[elisa.plugins.daap.models.DaapArtistModel}
            /album/name
                    L{elisa.plugins.daap.models.DaapAlbumModel}

        FIXME: the uri to the media itself is missing :(
        """
        port = uri.port or 3689
        key = (uri.host, port)

        if not key in self._connections:
            # create a new connection because there is none yet
            self._connections[key] = DaapConnection(uri.host, port)

        connection = self._connections[key]

        filename = uri.filename or 'artists'
        dfr = None
        model = None
        if filename == 'items':
            if 'container' in uri.path:
                # question for Playlists
                model = DaapPlaylistModel()
            else:
                model = DaapSongListModel()
        elif filename == 'databases':
            model = DaapDatabaseListModel()
        elif filename == 'server-info':
            model = DaapServerInfoModel()
        elif filename == 'containers':
            model = DaapPlaylistListModel()
        elif filename == 'artists':
            model = DaapArtistListModel()
            dfr = self._get_artists(uri, connection, model)
        elif uri.path.startswith('/artist'):
            artist_name = media_uri.unquote(uri.path.split('/')[-1])
            model = DaapArtistModel()
            model.name = artist_name
            dfr = self._get_cached_artist(uri, model, artist_name)
        elif uri.path.startswith('/album'):
            album_name = media_uri.unquote(uri.path.split('/')[-1])
            model = DaapAlbumModel()
            model.album = album_name
            # FIXME: needed for controller:
            model.name = album_name
            dfr = self._get_cached_album(uri, model, album_name)

        if not model:
            song_model = DaapSongModel()
            song_model.song_id = uri.filename
            db_uri = uri.parent
            model = self._get_playable_model(connection, db_uri, song_model)

        if dfr is None:

            if connection.revision_id:
                # we are logged in. try to request directly
                dfr = connection.request(uri, model)
            else:
                # not yet logged in
                def login_done(data):
                    return connection.request(uri, model)

                # do we have a password?
                password = None
                if uri.password:
                    password = uri.password

                dfr = connection.login(password)
                dfr.addCallback(login_done)

        return model, dfr
