import rhythmdb, rb
import gobject, gtk, gconf, gnomevfs, gnome
from gettext import gettext as _

import xml.sax, xml.sax.handler
import urllib
import datetime
import zipfile

magnatune_partner_id = "zimmerman"

user_dir = gnome.user_dir_get()
magnatune_dir = user_dir + "rhythmbox/magnatune/"
magnatune_dir_uri = gnomevfs.URI(magnatune_dir)
magnatune_song_info_uri = gnomevfs.URI("http://magnatune.com/info/song_info.xml")
local_song_info_uri = gnomevfs.URI(magnatune_dir + "song_info.xml")
lc_uri = gnomevfs.URI(magnatune_dir + "info_last_changed")

################################################
# Class to add Magnatune catalog to the source #
################################################

class TrackListHandler(xml.sax.handler.ContentHandler):
	
	def __init__(self, db, entry_type):
		xml.sax.handler.ContentHandler.__init__(self)
		self._track = {} # temporary dictionary for track info
		self._db = db
		self._entry_type = entry_type
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name):
		if name == "Track":
			try:
				# add the track to the source
				entry = self._db.entry_new(self._entry_type, self._track['url'])
				date = datetime.date(int(self._track['launchdate'][0:4]), 1, 1).toordinal() # year is sometimes 0, so we use launchdate
				
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_ARTIST, self._track['artist'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_ALBUM, self._track['albumname'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_TITLE, self._track['trackname'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_TRACK_NUMBER, int(self._track['tracknum']))
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_DATE, date)
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_GENRE, self._track['mp3genre'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_DURATION, int(self._track['seconds']))
				# entry.data['sku'] = self._track['albumsku']
				
				self._db.commit()
			except Exception,e: # This happens on duplicate uris being added
				print _("Couldn't add %s - %s") % (self._track['artist'], self._track['trackname']) # TODO: This should be printed to debug
				print e
			
			self._track = {}
		elif name == "AllSongs":
			pass # end of the file
		else:
			self._track[name] = self._text
	
	def characters(self, content):
		self._text = self._text + content


################################################
# Main Magnatune Plugin Class                  #
################################################

class Magnatune(rb.Plugin):
	
	info_file = None
	remote_info_file = None
	
	#
	# Core methods
	#
	
	def __init__(self):
		rb.Plugin.__init__(self)
		
	def activate(self, shell):
		self.db = shell.get_property("db")
		self.entry_type = rhythmdb.entry_register_type("MagnatuneEntryType")
		self.source = gobject.new (MagnatuneSource, shell=shell, name=_("Magnatune"), entry_type=self.entry_type)
		shell.register_entry_type_for_source(self.source, self.entry_type)
		
		icon = gtk.gdk.pixbuf_new_from_xpm_data(magnatune_logo_xpm) # Include a flashy Magnatune logo for the source
		self.source.set_property("icon", icon)
		ev = self.source.get_entry_view()
		ev.connect_object("show_popup", self.show_popup_cb, self.source, 0)
		shell.append_source(self.source, None) # Add the source to the list
		
		self.parser = xml.sax.make_parser()
		self.parser.setContentHandler(TrackListHandler(self.db, self.entry_type))
		if check_info():
			gobject.idle_add(self.idle_download_info)
		else:
			gobject.idle_add(self.idle_load_info)
		gobject.timeout_add(6 * 60 * 60 * 1000, self.check_info_updates) # every 6 hours.
	
	def deactivate(self, shell):
		self.db.entry_delete_by_type(self.entry_type)
		self.db.commit()
		self.source.delete_thyself()
		self.source = None
	
	
	#
	# Callback/helper functions
	#
	
	def show_popup_cb(self, source, some_int, some_bool): # FIXME: find out what the int and bool are
		entry_view = source.get_entry_view()
		client = gconf.client_get_default()
		cc = {}
		cc['number'] = client.get_string("/apps/rhythmbox/plugins/magnatune/cc")
		cc['year'] = client.get_string("/apps/rhythmbox/plugins/magnatune/yy")
		cc['month'] = client.get_string("/apps/rhythmbox/plugins/magnatune/mm")
		name = client.get_string("/apps/rhythmbox/plugins/magnatune/name")
		email = client.get_string("/apps/rhythmbox/plugins/magnatune/email")
		#sku = entry_view.get_selected_entries()[0].data['sku'] # just use the sku for the first track selected.
		#attach action: buy_track(sku, amount, cc, name, email, format)
		#source.show_popup("/MagnatuneSourcePopup")
	
	def check_info_updates(self): # TODO: if possible, make it so that the updated file is downloaded first, then the entries are switched
		if check_info():
			self.db.entry_delete_by_type(self.entry_type)
			self.db.commit()
			gobject.idle_add(self.idle_download_info)
		return True
	
	def idle_load_info(self):
		if self.info_file == None:
			self.info_file = gnomevfs.open(local_song_info_uri)
		try:
			data = self.info_file.read(128 * 1024)
			self.parser.feed(data)
			return True
		except gnomevfs.EOFError:
			self.info_file.close()
			self.info_file = None
			return False
	
	def idle_download_info(self):
		if self.info_file == None:
			self.remote_info_file = gnomevfs.open(magnatune_song_info_uri)
			self.info_file = gnomevfs.create(local_song_info_uri, open_mode=gnomevfs.OPEN_WRITE)
		try:
			data = self.remote_info_file.read(64 * 1024)
			self.parser.feed(data)
			self.info_file.write(data)
			return True
		except gnomevfs.EOFError:
			self.remote_info_file.close()
			self.info_file.close()
			self.remote_info_file = None
			self.info_file = None
			return False


class MagnatuneSource(rb.BrowserSource):
	def __init__(self):
		rb.Source.__init__(self)

gobject.type_register(MagnatuneSource)

################################################
# Methods for downloading the song info        #
################################################

def check_info():
# returns whether or not info has changed
	if not gnomevfs.exists(magnatune_dir_uri):
		gnomevfs.make_directory(magnatune_dir_uri, 0755)
	if not gnomevfs.exists(lc_uri):
		t = gnomevfs.create(lc_uri, open_mode=gnomevfs.OPEN_WRITE)
		t.write("never") # there needs to be something in the file, otherwise it throws an exception when read from
		t.close()
	modified = str(gnomevfs.get_file_info(magnatune_song_info_uri).mtime)
	lc_file = gnomevfs.open(lc_uri)
	last_changed = lc_file.read(100) # file should be less than 100 chars
	lc_file.close()
	if not last_changed.strip() == modified.strip():
		lc_file = gnomevfs.open(lc_uri, open_mode=gnomevfs.OPEN_WRITE)
		lc_file.write(modified)
		lc_file.close()
		return True
	return False

################################################
# Purchasing code.                             #
################################################

class BuyAlbumHandler(xml.sax.handler.ContentHandler): # Class to download the track, etc.
	
	format_map =	{
			'ogg'		:	'URL_OGGZIP',
			'flac'		:	'URL_FLACZIP',
			'wav'		:	'URL_WAVZIP',
			'mp3-cbr'	:	'URL_128KMP3ZIP',
			'mp3-vbr'	:	'URL_VBRZIP'
			}
	
	def __init__(self, format):
		xml.sax.handler.ContentHandler.__init__(self)
		self._format_tag = format_map[format] # format of audio to download
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name):
		if name == "ERROR": # Something went wrong. Display error message to user.
			raise MagnatuneError(self._text)
		elif name == "DL_USERNAME":
			self.username = self._text
		elif name == "DL_PASSWORD":
			self.password = self._text
		elif name == self._format_tag:
			self.url = self._text
	
	def characters(self, content):
		self._text = self._text + content

def buy_track(sku, amount, cc, name, email, format): # http://magnatune.com/info/api#purchase
	client = gconf.client_get_default()
	url = "https://magnatune.com/buy/buy_dl_cc_xml?"
	url = url + urllib.urlencode({
					'id':	magnatune_partner_id,
					'sku':	sku,
					'amount': amount,
					'cc':	cc['number'],
					'yy':	cc['year'],
					'mm':	cc['month'],
					'name': name,
					'email':email
				})
	
	buy_album_handler = BuyAlbumHandler(format) # so we can get the url and auth info
	xml.sax.parse(url, buy_album_handler)
	audio_dl_uri = gnomevfs.URI(buy_album_handler.url.replace(" ", "%20")) # some parts of the returned url are escaped, some aren't. TODO: Properly quote just the filename part of the path
	audio_dl_uri.user_name = buy_album_handler.username
	audio_dl_uri.password = buy_album_handler.password
	
	# Download the album and unzip it into the library
	library_location = client.get_list("/apps/rhythmbox/library_locations")[0] # Just use the first library location
	to_file = gnomevfs.URI(library_location + "/" + audio_dl_uri.short_name)
	out_file = to_file.__str__()
	gnomevfs.xfer_uri(audio_dl_uri, to_file, xfer_options=gnomevfs.XFER_DEFAULT,
		error_mode=gnomevfs.XFER_ERROR_MODE_ABORT, overwrite_mode=gnomevfs.XFER_OVERWRITE_MODE_ABORT,
		progress_callback=progress_info_cb, data=0x1234) # this will take a LONG time.
	
	album = zipfile.ZipFile(out_file)
	for track in album.namelist():
		out = gnomevfs.open(gnomevfs.URI(library_location + "/" + track), open_mode=gnomevfs.OPEN_MODE_WRITE) # FIXME: directories will need to be created first
		out.write(album.read(track))
		out.close()
	album.close()
	gnomevfs.unlink(to_file)

class MagnatuneError(Exception):
	pass

def progress_info_cb(info, data):
	assert data == 0x1234
	try:
		print "%s: %f %%\r" % (info.target_name,
			info.bytes_copied/float(info.bytes_total)*100),
	except Exception, ex: # Sometimes the method throws an exception, for no apparent reason
		pass
	return True

################################################
# Magnatune Logo.                              #
################################################

# (converted from http://www.magnatune.com/favicon.ico)

magnatune_logo_xpm = [
"32 32 4 1",
" 	c None", #Original colours:
".	c None", #FFFFFF
"+	c #303030", #C0C0C0 
"@	c #000000", #808080
"................................",
"................................",
"................................",
"................................",
"................................",
"................................",
"............++@@@@++............",
"..........+@@@@@@@@@@+..........",
".........+@@@+....+@@@+.........",
"........+@@+...++...+@@+........",
".......+@@+....@@....+@@+.......",
".......@@+.....@@.....+@@.......",
"......+@@......@@......@@+......",
"......+@+......@@......+@+......",
"......@@...@@..@@..@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......+@+..@@+.@@.+@@..+@+......",
"......+@@..@@+.@@.+@@..@@+......",
".......@@+.@@+.@@.+@@.+@@.......",
".......+@@+.+..+...+.+@@+.......",
"........+@@+........+@@+........",
".........+@@@+....+@@@+.........",
"..........+@@@@@@@@@@+..........",
"............++@@@@++............",
"................................",
"................................",
"................................",
"................................",
"................................",
"................................"
]

###
### preferences, ugly and gross. Someone else who knows what they're doing should probably fix this. Should probably be glade too.
###

#	def create_configure_dialog(self): # return a gtk dialog with configure options
#		if self._preferences == None:
#			client = gconf.client_get_default()
#			self._preferences = gtk.Dialog(title=_("Magnatune Preferences"), flags=gtk.DIALOG_MODAL)
#			
#			label = gtk.Label("<b>Purchase Information</b>")
#			self._preferences.vbox.pack_start(label, False, False, 0)
#			label.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Name"))
#			entry = gtk.Entry()
#			self.setup_entry(entry, "name")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(entry, False, False, 0)
#			label.show()
#			entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("E-mail Address"))
#			entry = gtk.Entry()
#			self.setup_entry(entry, "email")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(entry, False, False, 0)
#			label.show()
#			entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			button = gtk.CheckButton(_("Remember Credit Card Information"))
#			credit_entry = gtk.Entry(max=16)
#			month_entry = gtk.Entry(max=2)
#			year_entry = gtk.Entry(max=4)
#			button.connect("toggled", self.check_toggle, (credit_entry, month_entry, year_entry))
#			set = client.get_bool("/apps/rhythmbox/plugins/magnatune/forget")
#			if set is not None:
#				button.set_active(set)
#			self._preferences.vbox.pack_start(button, False, False, 0)
#			button.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Credit Card Number"))
#			# entry has already been created
#			self.setup_entry(credit_entry, "cc_num")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(credit_entry, False, False, 0)
#			label.show()
#			credit_entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Expiration: mm/yy "))
#			# entries already created
#			sep = gtk.Label(" / ")
#			self.setup_entry(month_entry, "cc_mm")
#			self.setup_entry(year_entry, "cc_yy")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(month_entry, False, False, 0)
#			hbox.pack_start(sep, False, False, 0)
#			hbox.pack_start(year_entry, False, False, 0)
#			label.show()
#			month_entry.show()
#			sep.show()
#			year_entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			button = gtk.Button(stock=gtk.STOCK_CLOSE)
#			button.connect("clicked", self.close_clicked, None)
#			self._preferences.action_area.pack_end(button, True, True, 0)
#			button.show()
#			
#		self._preferences.show()
#		return self._preferences
#
#	def check_toggle(self, widget, data=None):
#		active = not widget.get_active() # this method gets called before the widget changes
#		client = gconf.client_get_default()
#		client.set_bool("/apps/rhythmbox/plugins/magnatune/forget", active)
#		if active:
#			for entry in data:
#				entry.set_text("")
#				entry.set_sensitive(False)
#			for field in ('cc_num', 'cc_mm', 'cc_yy'):
#				client.unset("/apps/rhythmbox/plugins/magnatune/" + field)
#		else:
#			for entry in data:
#				entry.set_sensitive(True)
#	
#	def pref_changed(self, widget, gdk_event, data=None):
#		client = gconf.client_get_default()
#		client.set_string("/apps/rhythmbox/plugins/magnatune/" + data, widget.get_text())
#	
#	def close_clicked(self, widget, data=None):
#		self._preferences.hide()
#	
#	def setup_entry(self, entry, data):
#		client = gconf.client_get_default()
#		text = client.get_string("/apps/rhythmbox/plugins/magnatune/" + data)
#		if text is not None:
#			entry.set_text(text)
#		entry.connect("focus-out-event", self.pref_changed, data)
