# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 
#
# Copyright (C) 2006 - Gareth Murphy, Martin Szulecki
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

import rhythmdb
import os
import gtk
import gnomevfs
from urllib import unquote

from AmazonCoverArtSearch import AmazonCoverArtSearch
from Loader import Loader

ART_FOLDER = '~/.gnome2/rhythmbox/covers'

class CoverArtDatabase (object):
    def __init__ (self):
        self.loader = Loader()

    def create_search (self):
        return AmazonCoverArtSearch (self.loader)

    def build_art_cache_filename (self, album, artist, extension):
        art_folder = os.path.expanduser (ART_FOLDER)
        if not os.path.exists (art_folder):
            os.mkdir (art_folder)
        if extension is None:
            extension = "jpg"

        # FIXME: the following block of code is messy and needs to be redone ASAP
        return art_folder + '/%s - %s.%s' % (artist.replace ('/', '-'), album.replace ('/', '-'), extension)    

    def _build_image_location (self, track_location, image_filename) :
        """Helper function for find_local_filename"""
        location = str(gnomevfs.URI(track_location).parent.append_file_name(image_filename))
        assert gnomevfs.exists(location), "Do not exist: " + location
        return location
        

    def find_local_image (self, location) :
        """Given location of a music track, try and find some album art"""
        try :
            #Using gnomevfs functions (rather than os.listdir etc) which
            #should let this work with fileshares and local networks.
            if not gnomevfs.exists(location) :
                print "Track location did not exist: " + location
                return None

            image_list = []
            file_list = gnomevfs.open_directory(gnomevfs.URI(location).parent)
            for f in file_list :
                if f.name.split(os.path.extsep)[-1] in ["jpg", "jpeg","png"] :
                    #Do we have permission to read it?
                    if f.permissions & gnomevfs.PERM_USER_READ :
                        image_list.append(f.name)

            #What about if there is one and only one image? Return that...
            if len(image_list)==1 : return self._build_image_location(location, image_list[0])

            #First of all, look for track specific images using the track's base filename...
            track_filename = gnomevfs.URI(location).short_name
            for filename in image_list :
                if os.path.extsep.join(filename.split(os.path.extsep)[:-1]) \
                == os.path.extsep.join(track_filename.split(os.path.extsep)[:-1]) :
                    return self._build_image_location(location, filename)

            #That failed, so we are reduced to folder/directory specific files...
            #Special cases include "Cover.jpg" and variants, and ".folder.png"
            #which is used as a folder image by Gnome Nautilus
            for filename in image_list :
                if os.path.extsep.join(filename.split(os.path.extsep)[:-1]).lower() in ["cover",".folder","albumart"] :
                    return self._build_image_location(location, filename)

            #Should we return ANY image in the folder?
            return None
        except Exception, e :
            print e
            #Something went wrong...
            #I want the code to carry on the the next method, so catch the exception here.
            #TODO log it?  Error message?
            return None

    def get_pixbuf (self, db, entry, callback):
        if entry is None:
            callback (entry, None)
            return

        #########################################################################
        # Setup Artist/Album strings for online search, and set cache filenames #
        #########################################################################

        st_artist = db.entry_get (entry, rhythmdb.PROP_ARTIST)
        st_album = db.entry_get (entry, rhythmdb.PROP_ALBUM)

        # Handle special case
        if st_album == "":
            st_album = "Unknown"
        if st_artist == "":
            st_artist = "Unknown"

        # replace quote characters
        # don't replace single quote: could be important punctuation
        for char in ["\""]:
            st_artist = st_artist.replace (char, '')
            st_album = st_album.replace (char, '')

        # If unknown artist and album then can't use the cache or Amazon
        if st_album == "Unknown" and st_artist == "Unknown":
            art_location = None
            blist_location = None
            #print "ArtDisplay: Cannot lookup unknowns: %s / %s" % (st_artist, st_album)
        else :
            art_location = self.build_art_cache_filename (st_album, st_artist, "jpg")
            blist_location = self.build_art_cache_filename (st_album, st_artist, "rb-blist")

        ####################################
        # Method One - Rhythmbox Cache     #
        ####################################

        if os.path.exists(art_location):
            #print "ArtDisplay: Using cache: " + art_location
            pixbuf = gtk.gdk.pixbuf_new_from_file (art_location)    
            callback (entry, pixbuf)
            return
        else :
            #print "ArtDisplay: Not in cache: " + art_location
            pass

        ####################################
        # Method Two - Embedded in track   #
        ####################################

        #See bug 345305

        ################################################
        # Method Three - Image in same folder as track #
        ################################################
        
        image_location = self.find_local_image (db.entry_get (entry, rhythmdb.PROP_LOCATION))
        if image_location :
            buff_loader = gtk.gdk.PixbufLoader ()
            #Following will block until the file is read in...
            buff_loader.write (gnomevfs.read_entire_file (image_location))
            buff_loader.close ()
            callback (entry, buff_loader.get_pixbuf ())
            return
        del image_location

        ####################################
        # Method Four - Amazon             #
        ####################################

        if blist_location and art_location :
            # Check for unsuccessful previous image download to prevent overhead search
            if os.path.exists(blist_location):
                callback (entry, None)
            else:
                # Otherwise spawn (online) search-engine search
                se = self.create_search ()
                # This will handle the callback when it gets search results:
                se.search (db, entry, self.on_search_engine_results, callback)
 
    def on_search_engine_results (self, search_engine, entry, results, callback):
        if results is None:
            self._do_blacklist_and_callback (search_engine, callback)
            return

        # Get best match from results
        best_match = search_engine.get_best_match (results)

        if best_match is None:
            self._do_blacklist_and_callback (search_engine, callback)
            return

        # Attempt to download image for best match
        pic_url = str (best_match.ImageUrlLarge)
        self.loader.get_url (pic_url, self.on_image_data_received, search_engine, "large", callback, best_match)

    def _do_blacklist_and_callback (self, search_engine, callback):
        self._create_blacklist (search_engine.st_artist, search_engine.st_album)
        callback (search_engine.entry, None)

    def _create_blacklist (self, artist, album):
        location = self.build_art_cache_filename (album, artist, "rb-blist")
        f = file (location, 'w')
        f.close ()
        return location

    def _create_artwork (self, artist, album, image_data):
        location = self.build_art_cache_filename (album, artist, "jpg")
        f = file (location, 'wb')
        f.write (image_data)
        f.close ()
        return location

    def on_image_data_received (self, image_data, search_engine, image_version, callback, best_match):
        if image_data is None:
            res = search_engine.search_next ()
            if not res:
                self._do_blacklist_and_callback (search_engine, callback)
            return

        if len (image_data) < 1000:
            if image_version == "large" and best_match is not None:
                # Fallback and try to load medium one
                pic_url = str (best_match.ImageUrlMedium)
                self.loader.get_url (pic_url, self.on_image_data_received, search_engine, "medium", callback, best_match)
                return

            res = search_engine.search_next ()
            if not res:
                # only write the blist if there are no more queries to try
                self._do_blacklist_and_callback (search_engine, callback)

        else:
            location = self._create_artwork (search_engine.st_artist, search_engine.st_album, image_data)
            pixbuf = gtk.gdk.pixbuf_new_from_file (location)
            callback (search_engine.entry, pixbuf)
