# -*- coding: UTF-8 -*-

import urllib
import nautilus

import eyeD3

import gtk
import gobject
import pango
#import gtk.glade

import sets

class RowState:
	NORMAL, MULTI, EDITED, DELETE = range(4)

class Columns: # LS-COLS
	ID, VALUE, STATE = range(3)

def get_mp3files(files):
	"""Return the subset of a list of files that are MP3 files."""
	mp3files = []

	for file in files:
		if file.get_uri_scheme() != 'file':
			continue
		filename = urllib.unquote(file.get_uri()[7:])
		if eyeD3.isMp3File(filename):
			mp3files.append(filename)

	return mp3files

def mp3files_get_frameids(files):
	"""Return the set of frame ids, present accross the files."""
	tag = eyeD3.Tag()
	frameids = sets.Set()
	for file in files:
		tag.link(file)
		for frame in tag.frames:
			frameids.add(frame.header.id)
	return frameids

def frame_get_text(frame):
	"""Return some kind of text represinting a Frame's data."""
	# TODO: try each method, move to next on exception?
	if frame.__class__ == list:
		# Tag.frames['XXXX'] returns a list of all the frames with that id
		try:
			return frame_get_text(frame[0])
		except IndexError:
			return None # TODO: return empty string?
	elif frame.__class__ == eyeD3.frames.TextFrame:
		return frame.text
	elif frame.__class__ == eyeD3.frames.DateFrame:
		return frame.getDate()
	elif frame.__class__ == eyeD3.frames.URLFrame:
		return frame.url
	elif frame.__class__ == eyeD3.frames.UserURLFrame:
		return frame.url
	elif frame.__class__ == eyeD3.frames.CommentFrame:
		return frame.comment
	else:
		return '(unknown frame type: %s: %s)' % (frame.__class__, frame)

def frame_description(id):
	"""Get the friendly name for a frame id."""
	if id in eyeD3.frameDesc:
		return eyeD3.frameDesc[id]
	
	if id in eyeD3.obsoleteFrames:
		return eyeD3.obsoleteFrames[id]
	
	return id

def treeview_draw_frameid_friendlyname(column, cell, model, iter):
	font_desc = pango.FontDescription()
	colour = None
	strikethrough = False
	text = frame_description(model.get_value(iter, Columns.ID))

	if model.get_value(iter, Columns.VALUE) in ['', None]:
		colour = 'grey'
	
	if model.get_value(iter, Columns.STATE) == RowState.MULTI:
		font_desc.set_style(pango.STYLE_ITALIC)
		colour = 'grey'
	elif model.get_value(iter, Columns.STATE) == RowState.DELETE:
		colour = 'red'
		strikethrough = True

	cell.set_property('font-desc', font_desc)
	cell.set_property('foreground', colour)
	cell.set_property('strikethrough', strikethrough)
	cell.set_property('text', text)

def treeview_draw_value(column, cell, model, iter):
	font_desc = pango.FontDescription()
	colour = None
	strikethrough = False
	text = model.get_value(iter, Columns.VALUE)

	if model.get_value(iter, Columns.STATE) == RowState.EDITED:
		font_desc.set_weight(pango.WEIGHT_BOLD)
	elif model.get_value(iter, Columns.STATE) == RowState.MULTI:
		font_desc.set_style(pango.STYLE_ITALIC)
		colour = 'grey'
		text = '(multiple values)'
	elif model.get_value(iter, Columns.STATE) == RowState.DELETE:
		colour = 'red'
		strikethrough = True

	cell.set_property('font-desc', font_desc)
	cell.set_property('foreground', colour)
	cell.set_property('strikethrough', strikethrough)
	cell.set_property('text', text)

#def frameset_contains_id(frameset, id):
#	for frames in frameset:
#		if frame.header.id == id:
#			return True
#	return false

def frame_values_equal_accross_mp3files(files, id):
	"""Returns true if the given tag's value is equal accross all the files"""
	tag = eyeD3.Tag()

	tag.link(files[0])
	reference = frame_get_text(tag.frames[id])

	for file in files[1:]:
		tag.link(file)
		if frame_get_text(tag.frames[id]) != reference:
			return False

	return True

def sort_frameid_by_description(treemodel, iter1, iter2):
	desc1 = frame_description(treemodel.get_value(iter1, Columns.ID))
	desc2 = frame_description(treemodel.get_value(iter2, Columns.ID))

	if desc1 < desc2:
		return -1
	elif desc2 < desc1:
		return 1
	else:
		return 0

class TaggerPropertyPage(nautilus.PropertyPageProvider):
	def __init__(self):
		pass
	
	def taglist_altered(self, treemodel, path, iter = None):
		self.revertbutton.set_sensitive(True)

	def load_tags(self):
		"""Loads the tag list with tags from the selected mp3 files."""
		tls = self.tagliststore

		if tls.row_changed_signalid != None:
			tls.disconnect(tls.row_changed_signalid)
		if tls.row_deleted_signalid != None:
			tls.disconnect(tls.row_deleted_signalid)
		if tls.row_inserted_signalid != None:
			tls.disconnect(tls.row_inserted_signalid)

		tls.clear()

		tag = eyeD3.Tag()
		possible_frameids = mp3files_get_frameids(self.mp3files)
			
		for id in possible_frameids:
			if frame_values_equal_accross_mp3files(self.mp3files, id):
				tag.link(self.mp3files[0])
				value = frame_get_text(tag.frames[id])
				tls.insert_before(None, [id, value, RowState.NORMAL]) # LS-COLS
			else:
				tls.insert_before(None, [id, '', RowState.MULTI]) # LS-COLS
		
		self.fill_addcombo()
		
		tls.row_changed_signalid  = tls.connect('row-changed',  self.taglist_altered)
		tls.row_deleted_signalid  = tls.connect('row-deleted',  self.taglist_altered)
		tls.row_inserted_signalid = tls.connect('row-inserted', self.taglist_altered)
		self.revertbutton.set_sensitive(False)

	# <http://www.pygtk.org/pygtk2tutorial/sec-TreeModelInterface.html>
	# §14.2.6.4 "Managing Multiple Rows" says that this will work, despite the warning
	# TODO: use a gtk.TreeRowReference to be on the safe side
	def removebutton_clicked(self, button):
		button.set_sensitive(False)

		s = self.taglistview.get_selection()
		iter = self.tagliststore.get_iter_first()
		while iter != None:
			if s.iter_is_selected(iter):
				self.tagliststore.set_value(iter, Columns.STATE, RowState.DELETE)
			iter = self.tagliststore.iter_next(iter)

	def revertbutton_clicked(self, button):
		self.load_tags()

	def store_value_edited(self, crt, path, new_text):
		iter = self.tagliststore.get_iter(path)

		# fix for the bug: fast-double-click-fires-'edited'-signal
		# TODO: empty cell contents at start of editing
		if (new_text == '(multiple values)') and (self.tagliststore.get_value(iter, Columns.STATE) == RowState.MULTI):
			return
		if new_text == self.tagliststore.get_value(iter, Columns.VALUE):
			return
		self.tagliststore.set(iter, Columns.VALUE, new_text, Columns.STATE, RowState.EDITED)

		self.removebutton.set_sensitive(True)

	def fill_addcombo(self):
		"""Fills the Add combo box with a list of genres not already present."""
		# possible tags taken from <http://www.id3.org/id3v2.4.0-frames.txt>
		#§ 4.2.1
		possible = ['TIT2', 'TALB', 'TRCK', 'TPOS',
			#§ 4.2.2
			'TPE1', 'TPE4', 'TCOM',
			#§ 4.2.3
			'TCON']
		#possible = eyeD3.frameDesc
		
		present = []
		iter = self.tagliststore.get_iter_first()
		while iter != None:
			present.append(self.tagliststore.get_value(iter, Columns.ID))
			iter = self.tagliststore.iter_next(iter)
	
		result = {}
		for id in possible:
			if id not in present:
				result[id] = eyeD3.frameDesc[id]

		items = result.items()
		backitems = [[v[1],v[0]] for v in items]
		backitems.sort()
		result = [backitems[i][1] for i in range(0,len(backitems))]

		self.addcombostore.clear()
		for id in result:
			self.addcombostore.insert_before(None, [id, eyeD3.frameDesc[id]])
		
	def addcombo_changed(self, combobox):
		id = combobox.get_model().get_value(combobox.get_active_iter(), Columns.ID)

		iter = self.tagliststore.insert_before(None, [id, '', RowState.EDITED])

		sel = self.taglistview.get_selection()
		sel.unselect_all()
		sel.select_iter(iter)

		self.fill_addcombo()
	
	def taglist_selection_changed(self, treeselection):
		enable = False

		(model, rows) = treeselection.get_selected_rows()
		for path in rows:
			if model.get_value(model.get_iter(path), Columns.STATE) != RowState.DELETE:
				enable = True
				break

		self.removebutton.set_sensitive(enable)

	def get_property_pages(self, files):
		self.mp3files = get_mp3files(files)

		if len(self.mp3files) == 0:
			return
		
		property_label = gtk.Label('Tagger')
		property_label.show()

		if len(self.mp3files) < len(files):
			label = gtk.Label('Not all files were MP3 files.')
			label.show()
			self.widget = label
		else:
			self.tagliststore = gtk.ListStore(
				gobject.TYPE_STRING,  # frame_id
				gobject.TYPE_STRING,  # value
				gobject.TYPE_INT      # status
				) # when changing this, fixup ref:LS-COLS
			self.tagliststore.set_sort_func(Columns.ID, sort_frameid_by_description) # LS-COLS
			self.tagliststore.set_sort_column_id(Columns.ID, gtk.SORT_ASCENDING)
			self.tagliststore.row_changed_signalid = None
			self.tagliststore.row_deleted_signalid = None
			self.tagliststore.row_inserted_signalid = None

			# Set up widets. TODO: load widgets from .glade file
			#self.glade = gtk.glade.XML('/home/sam/src/nautilus-tagger/nautilus-tagger.glade')
			#self.widget = glade.get_widget('tagger_vbox')
			
			self.vbox = gtk.VBox(False, 0)
			self.vbox.show()

			self.scrolledwindow = gtk.ScrolledWindow()
			self.scrolledwindow.show()
			self.vbox.pack_start(self.scrolledwindow, True, True, 0)
		
			self.taglistview = gtk.TreeView(self.tagliststore)
			self.taglistview.set_enable_search(False)
			self.taglistview.show()
			self.taglistview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
			self.taglistview.get_selection().connect('changed', self.taglist_selection_changed)
			self.scrolledwindow.add(self.taglistview)
			
			# friendly name corresponding to frame id
			crt = gtk.CellRendererText()
			crt.set_property('ellipsize', pango.ELLIPSIZE_END)
			tvc = gtk.TreeViewColumn('Description', crt)
			tvc.set_resizable(True)
			tvc.set_cell_data_func(crt, treeview_draw_frameid_friendlyname)
			self.taglistview.append_column(tvc)
					
			# editable column for tag values
			crt = gtk.CellRendererText()
			crt.set_property('editable', True)
			crt.connect('edited', self.store_value_edited)
			tvc = gtk.TreeViewColumn('Value', crt)
			tvc.set_resizable(True)
			tvc.set_cell_data_func(crt, treeview_draw_value)
			self.taglistview.append_column(tvc)
			
			self.hbox = gtk.HBox(False, 0)
			self.hbox.show()
			self.vbox.pack_end(self.hbox, False, False, 8)

			self.hboxL = gtk.HBox()
			self.hboxL.set_spacing(8)
			self.hboxL.show()
			self.hbox.add(self.hboxL)

			self.hboxAdd = gtk.HBox()
			self.hboxAdd.show()
			self.hboxL.add(self.hboxAdd)

			self.addlabel = gtk.Label('Add:')
			self.addlabel.show()
			self.hboxAdd.pack_start(self.addlabel, False, False, 8)

			self.addcombostore = gtk.ListStore(
				gobject.TYPE_STRING, # id
				gobject.TYPE_STRING  # description
			)
			self.addcombo = gtk.ComboBox(self.addcombostore)
			crt = gtk.CellRendererText()
			self.addcombo.pack_start(crt, False)
			self.addcombo.add_attribute(crt, 'text', 1)
			self.addcombo.set_size_request(64, -1)
			self.addcombo.connect('changed', self.addcombo_changed)
			self.addcombo.show()
			self.hboxAdd.pack_start(self.addcombo, False, False, 0)

			self.removebutton = gtk.Button(None, gtk.STOCK_REMOVE)
			self.removebutton.connect('clicked', self.removebutton_clicked)
			self.removebutton.set_sensitive(False)
			self.removebutton.show()
			self.hboxL.pack_end(self.removebutton, False, False, 0)

			self.hbuttonboxR = gtk.HButtonBox()
			self.hbuttonboxR.set_layout(gtk.BUTTONBOX_END)
			self.hbuttonboxR.set_spacing(8)
			self.hbuttonboxR.show()
			self.hbox.add(self.hbuttonboxR)

			self.revertbutton = gtk.Button(None, gtk.STOCK_REVERT_TO_SAVED)
			self.revertbutton.connect('clicked', self.revertbutton_clicked)
			self.revertbutton.show()
			self.hbuttonboxR.add(self.revertbutton)

			self.savebutton = gtk.Button(None, gtk.STOCK_SAVE)
			self.savebutton.show()
			self.savebutton.set_sensitive(False)
			self.hbuttonboxR.add(self.savebutton)
			
			self.load_tags()

			self.widget = self.vbox

		return nautilus.PropertyPage("NautilusPython::tagger",
			property_label, self.widget),
