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

from elisa.core.media_uri import MediaUri
from elisa.core.log import Loggable
from elisa.core import common
from elisa.core.utils import defer, caching
from elisa.core.utils.cancellable_defer import CancelledError
from elisa.core.resource_manager import NoMatchingResourceProvider

from elisa.plugins.base.utils import get_and_cache_image
from elisa.plugins.base.models.audio import AlbumModel

from elisa.plugins.pigment.widgets.theme import Theme

from elisa.plugins.poblesec.base.list import GenericListViewMode

# FIXME: cover retrieval is hardcoded for lastfm covers
try:
    from elisa.plugins.lastfm.resource_provider import get_lastfm_albumgetinfo_url
except ImportError:
    get_lastfm_albumgetinfo_url = None

# FIXME: artist pictures retrievel has a strong dependency on the discogs
# plugin.
try:
    from elisa.plugins.discogs import discogs_api
except ImportError:
    discogs_api = None

import os.path
from urllib import quote


class AlreadyInQueueError(Exception):
    pass


class ArtistPictureRetriever(Loggable):
    def __init__(self):
        super(ArtistPictureRetriever, self).__init__()
        self.enabled = True
        self.cache = {}

    def _check_for_image_on_model(self, model, model_id):
        if hasattr(model, 'images') and model.images is not None and \
                len(model.images) > 0:
            image_uri = None
            for image in model.images:
                if len(image.references) > 0:
                    image_uri = image.references[1]
                    break

            if image_uri is None:
                return False

            self.cache[model_id] = image_uri
            model.image_uri = image_uri
            return True
        return False

    def get_image_uri(self, model):
        if discogs_api is None:
            # discogs is not available therefore no request is made
            # and model.image_uri is left untouched
            return defer.succeed(model)

        model_id = model.name
        if model_id.lower().startswith('the '):
            # Discogs doesn't like "The Beatles", it prefers "Beatles, The"!
            model_id = '%s, %s' % (model_id[4:].strip(), model_id[:3])
        model_id = quote(model_id.encode('utf-8'), safe='')
        path = u'/artist/%s' % model_id
        uri = discogs_api.generate_api_request(path)

        if model_id in self.cache:
            if self.cache[model_id] is not None:
                # We did a request and we still have the result in the cache
                model.image_uri = self.cache[model_id]
                return defer.succeed(model)
            # The request is already in the queue
            return defer.fail(AlreadyInQueueError(uri))

        if not self.enabled:
            # we are disabled
            self._check_for_image_on_model(model, model_id)
            return defer.succeed(model)

        self.cache[model_id] = None

        def got_image_uri(result, model, model_id):
            if result.images:
                image_uri = unicode(result.images[0].references[0])
                model.image_uri = image_uri
                self.cache[model_id] = image_uri
            else:
                self._check_for_image_on_model(model, model_id)
            return model

        def failed(failure, model_id):
            # if it was cancelled, we want to remove the model_id to try again
            # later
            if failure.type is CancelledError:
                self.cache.pop(model_id)
            elif self._check_for_image_on_model(model, model_id):
                return model

            self.debug('Failed to retrieve artist picture for %s: %s' % \
                       (model.name, failure))

        try:
            data_model, dfr = common.application.resource_manager.get(uri)
        except NoMatchingResourceProvider, e:
            self.enabled = False
            return defer.fail(e)

        dfr.addCallback(got_image_uri, model, model_id)
        dfr.addErrback(failed, model_id)
        return dfr

# FIXME: a global instance at this level does not prevent from having multiple
# instances, this should be made a singleton or an attribute of the
# application, or something...
artist_picture_retriever = ArtistPictureRetriever()


class AlbumCoverRetrieval(Loggable):
    def __init__(self):
        super(AlbumCoverRetrieval, self).__init__()
        self.cache = {}
        self.enabled = True

    def _check_for_cover_on_model(self, model, model_id):
        # check for the generic cover and set the best reference as cover_uri if
        # available
        if hasattr(model, 'cover') and model.cover is not None and \
                len(model.cover.references) > 0:
            cover_uri = model.cover.references[-1]
            self.cache[model_id] = cover_uri
            model.cover_uri = cover_uri
            return True
        return False

    def get_cover_uri(self, model, artist):
        """
        called by L{elisa.plugins.poblesec.music_library.AlbumsViewMode}
        to retrieve the cover of the album through LastFM (hardcoded)

        @param model: the album for which we want to retrieve a cover
        @type model:  L{elisa.plugins.base.models.audio.AlbumModel}
        @param artist: the artist of this album
        @type artist:  C{unicode}

        @return:         a deferred (with the image URI)
        @rtype:          L{elisa.core.utils.defer.Deferred}
        """
        if get_lastfm_albumgetinfo_url is None:
            # lastfm is not available therefore no request is made
            # and model.image_uri is left untouched
            return defer.succeed(model)

        # Fixed potential bug here :
        # former model_id was only model.name 
        model_id = unicode(model.name) + unicode(artist)

        uri = get_lastfm_albumgetinfo_url(unicode(artist), 
                                          unicode(model.name))

        if model_id in self.cache:
            if self.cache[model_id] is not None:
                # We did a request and we still have the result in the cache
                model.cover_uri = self.cache[model_id]
                return defer.succeed(model)
            # The request is already in the queue
            return defer.fail(AlreadyInQueueError(model_id))

        def got_local_coverart(cover_uri):
            if cover_uri is None:
                return self._lastfm_lookup(model, model_id, artist, uri)
            else:
                cover_uri = unicode(cover_uri)
                self.cache[model_id] = cover_uri
                model.cover_uri = cover_uri
                return model

        dfr = model.get_local_coverart()
        dfr.addCallback(got_local_coverart)
        return dfr

    def _lastfm_lookup(self, model, model_id, artist, uri):
        if not self.enabled:
            # we are disabled
            self._check_for_cover_on_model(model, model_id)
            return defer.succeed(model)

        self.cache[model_id] = None

        def got_cover_uri(result, model, model_id):

            if result.cover:
                cover_uri = unicode(result.cover.references[-1])
                self.cache[model_id] = cover_uri
                model.cover_uri = cover_uri
            else:
                # fallback check on the model
                self._check_for_cover_on_model(model, model_id)
            return model

        def failed(failure, model_id):
            # if it was cancelled, we want to remove the model_id to try again
            # later
            if failure.type is CancelledError:
                self.cache.pop(model_id)
            elif self._check_for_cover_on_model(model, model_id):
                return model

            self.debug('Failed to retrieve album cover for %s: %s' % \
                       (model.name, failure))

        try:
            data_model, dfr = common.application.resource_manager.get(uri)
        except NoMatchingResourceProvider, e:
            self.enabled = False
            return defer.fail(e)

        dfr.addCallback(got_cover_uri, model, model_id)
        dfr.addErrback(failed, model_id)
        return dfr

# FIXME: a global instance at this level does not prevent from having multiple
# instances, this should be made a singleton or an attribute of the
# application, or something...
album_cover_retriever = AlbumCoverRetrieval()


class ArtistsViewMode(GenericListViewMode):

    """
    Implementation of the common view modes API.
    """

    def get_label(self, item):
        return defer.succeed(item.name)

    def get_default_image(self, item):
        resource = 'elisa.plugins.poblesec.glyphs.small.artist'
        return resource

    def get_image(self, item, theme):
        def got_image_uri(result_model):
            try:
                if result_model.image_uri is None:
                    return None
                return get_and_cache_image(MediaUri(result_model.image_uri))
            except AttributeError:
                return None

        def _failure(failure):
            failure.trap(AlreadyInQueueError)

        if hasattr(item, 'image_uri') and item.image_uri is not None:
            image_deferred = defer.succeed(item)
        else:
            image_deferred = artist_picture_retriever.get_image_uri(item)
        image_deferred.addCallbacks(got_image_uri, _failure)
        return image_deferred

    def get_preview_image(self, item, theme):
        try:
            thumbnail_file = caching.get_cached_image_path(item.image_uri)
            if not os.path.exists(thumbnail_file):
                return None
            return thumbnail_file
        except AttributeError:
            return None

    def get_contextual_background(self, item):
        return defer.succeed(self.get_preview_image(item, None))

class AlbumsViewMode(GenericListViewMode):

    """
    Implementation of the common view modes API.
    """

    def get_label(self, item):
        return defer.succeed(item.name)

    def get_default_image(self, item):
        resource = 'elisa.plugins.poblesec.glyphs.small.music'
        return resource

    def get_sublabel(self, item):
        return item.get_artist_name()

    def get_image(self, item, theme):
        def got_artist(artist):
            return album_cover_retriever.get_cover_uri(item, artist)

        def got_image_uri(result_model):
            try:
                if result_model.cover_uri is None:
                    return None
                return get_and_cache_image(MediaUri(result_model.cover_uri))
            except AttributeError:
                return None

        def _failure(failure):
            failure.trap(AlreadyInQueueError)

        if hasattr(item, 'cover_uri') and item.cover_uri is not None:
            image_deferred = defer.succeed(item)
        else:
            image_deferred = item.get_artist_name()
            image_deferred.addCallback(got_artist)
        image_deferred.addCallbacks(got_image_uri, _failure)
        return image_deferred

    def get_preview_image(self, item, theme):
        try:
            if not item.cover_uri:
                return None
            cover_uri = MediaUri(item.cover_uri)
            if cover_uri.scheme == 'file':
                return cover_uri.path
            else:
                thumbnail_file = caching.get_cached_image_path(item.cover_uri)
                if not os.path.exists(thumbnail_file):
                    return None
                return thumbnail_file
        except AttributeError:
            return None

    def get_contextual_background(self, item):
        return self.get_image(item, Theme.get_default())

class TracksViewMode(GenericListViewMode):

    """
    Implementation of the common view modes API.
    """

    trash_letters = " .,-_"

    def get_label(self, item):
        title = item.title.strip(self.trash_letters)
        return defer.succeed(title)

    def get_sublabel(self, item):
        raise NotImplementedError()

    def get_default_image(self, item):
        resource = 'elisa.plugins.poblesec.glyphs.small.music'
        return resource

    def get_image(self, item, theme):

        def got_image_uri(result_model):
            try:
                if result_model.cover_uri is None:
                    return None
                item.cover_uri = result_model.cover_uri
                return get_and_cache_image(MediaUri(result_model.cover_uri))
            except AttributeError:
                return None

        def _failure(failure):
            failure.trap(AlreadyInQueueError)

        if hasattr(item, 'cover_uri') and item.cover_uri is not None:
            image_deferred = defer.succeed(item)
            image_deferred.addCallbacks(got_image_uri, _failure)
            return image_deferred
        else:
            def got_artist(artist, album):
                return album_cover_retriever.get_cover_uri(album, artist)

            def got_album(album):
                if hasattr(album, 'cover_uri') and album.cover_uri is not None:
                    return defer.succeed(album)
                elif album:
                    dfr = album.get_artist_name()
                    dfr.addCallback(got_artist, album)
                    return dfr

            album_deferred = item.get_album()
            album_deferred.addCallback(got_album)
            album_deferred.addCallbacks(got_image_uri, _failure)
            return album_deferred

    def get_preview_image(self, item, theme):
        try:
            thumbnail_file = caching.get_cached_image_path(item.cover_uri)
            if not os.path.exists(thumbnail_file):
                return None
            return thumbnail_file
        except AttributeError:
            return None

    def get_contextual_background(self, item):
        return defer.succeed(self.get_preview_image(item, None))
