import rox
from rox import g
from xml.dom import XML_NAMESPACE, Node
import type
import override
from override import FREE_NS

class Invalid(Exception):
	pass

def pair(vbox, label, widget):
	hbox = g.HBox(False, 0)
	vbox.pack_start(hbox, False, True, 0)
	hbox.pack_start(g.Label(label + ': '), False, True, 0)
	hbox.pack_start(widget, True, True, 0)

match_types = ['string', 'byte', 'big16', 'big32', 'little16', 'little32', 'host16', 'host32']

class Field:
	"MIME_Type.get_* functions return a list of these."
	def __init__(self, type, item = None):
		self.type = type
		if item is None:
			self.item = self.get_blank_item()
			self.user = True
			self.new = True
		else:
			if len(item) == 2:
				self.item = item[0]
			else:
				self.item = item[:-1]
			self.user = item[-1]
			self.new = False
		assert self.user in (True, False)
	
	def can_edit(self):
		return self.user
	
	def get_blank_item(self):
		raise Exception(_("Can't create fields of this type yet"))
	
	def __str__(self):
		return "<%s>" % str(self.item)

	def get_sub_fields(self):
		return []

	def add_subtree(self, model, iter, grey):
		return
	
	def delete(self):
		rox.alert(_('TODO'))
	
	def edit(self):
		"Returns True on success."
		if not hasattr(self, 'add_edit_widgets'):
			rox.alert(_('Sorry, MIME-Editor does not support editing fields of this type'))
			return
		box = rox.Dialog()
		self.box = box
		box.set_has_separator(False)
		vbox = g.VBox(False, 4)
		vbox.set_border_width(4)
		box.vbox.pack_start(vbox, True, True, 0)
		self.add_edit_widgets(vbox)
		box.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
		box.add_button(g.STOCK_OK, g.RESPONSE_OK)
		box.set_default_response(g.RESPONSE_OK)
		box.vbox.show_all()
		while 1:
			try:
				resp = box.run()
				if resp == int(g.RESPONSE_OK):
					try:
						self.commit_edit(box)
					except Invalid, e:
						rox.alert(str(e))
						continue
					box.destroy()
					return True
			except:
				rox.report_exception()
			box.destroy()
			return False
	
	def delete_from_node(self, type_node):
		raise Invalid(_("TODO: Can't delete/edit fields of this type yet"))

	def add_to_node(self, node):
		raise Invalid(_("TODO: Can't add/edit fields of this type yet"))

	def delete(self):
		assert self.user
		doc, node = override.get_override_type(self.type.get_name())
		self.delete_from_node(node)
		override.write_override(doc)

	def commit_edit(self, box):
		doc, node = override.get_override_type(self.type.get_name())
		if not self.new:
			self.delete_from_node(node)
		self.add_to_node(node)
		override.write_override(doc)
	
class Comment(Field):
	def get_blank_item(self):
		return (None, _('Unknown format'))

	def __str__(self):
		lang, data = self.item
		if lang:
			lang = '[' + lang + '] '
		else:
			lang = '(default) '
		return  lang + data

	def add_edit_widgets(self, vbox):
		vbox.pack_start(
			g.Label(_("Enter a brief description of the type, eg 'HTML Page'.\n"
				"Leave the language blank unless this is a translation, in \n"
				"which case enter the country code (eg 'fr').")),
				False, True, 0)

		self.lang = g.Entry()
		self.lang.set_text(self.item[0] or '')
		self.lang.set_activates_default(True)
		pair(vbox, _('Language'), self.lang)

		self.entry = g.Entry()
		self.entry.set_text(self.item[1])
		self.entry.grab_focus()
		self.entry.set_activates_default(True)
		pair(vbox, _('Description'), self.entry)

	def delete_from_node(self, node):
		lang = self.item[0] or ''
		for x in node.childNodes:
			if x.nodeType != Node.ELEMENT_NODE: continue
			if x.localName == 'comment' and x.namespaceURI == FREE_NS:
				if (x.getAttributeNS(XML_NAMESPACE, 'lang') or '') == lang and \
				    type.data(x) == self.item[1]:
					x.parentNode.removeChild(x)
					break
		else:
			raise Exception(_("Can't find this comment in Override.xml!"))
	
	def add_to_node(self, node):
		new_lang = self.lang.get_text()
		new = self.entry.get_text()
		if not new:
			raise Invalid(_("Comment can't be empty"))
		comment = node.ownerDocument.createElementNS(FREE_NS, 'comment')
		if new_lang:
			comment.setAttributeNS(XML_NAMESPACE, 'xml:lang', new_lang)
		data = node.ownerDocument.createTextNode(new)
		comment.appendChild(data)
		node.appendChild(comment)
	
class Glob(Field):
	def get_blank_item(self):
		return ''
	
	def __str__(self):
		return _("Match '%s'") % self.item
	
	def add_edit_widgets(self, vbox):
		vbox.pack_start(
			g.Label(_("Enter a glob pattern which matches files of this type.\n"
				"Special characters are:\n"
				"? - any one character\n"
				"* - zero or more characters\n"
				"[abc] - any character between the brackets\n"
				"Example: *.html matches all files ending in '.html'\n"
				"(or '.HTML', etc)")),
				False, True, 0)
		self.entry = g.Entry()
		self.entry.set_text(self.item)
		vbox.pack_start(self.entry, False, True, 0)
		self.entry.set_activates_default(True)
	
	def delete_from_node(self, node):
		for x in node.childNodes:
			if x.nodeType != Node.ELEMENT_NODE: continue
			if x.localName == 'glob' and x.namespaceURI == FREE_NS:
				if x.getAttributeNS(None, 'pattern') == self.item:
					x.parentNode.removeChild(x)
					break
		else:
			raise Exception(_("Can't find this pattern in Override.xml!"))
	
	def add_to_node(self, node):
		new = self.entry.get_text()
		if not new:
			raise Invalid(_("Pattern can't be empty"))
		glob = node.ownerDocument.createElementNS(FREE_NS, 'glob')
		glob.setAttributeNS(None, 'pattern', new)
		node.appendChild(glob)
	
class TreeMagic(Field):
	def __str__(self):
		prio, matches = self.item
		return _("Match with priority %d") % prio

class Magic(Field):
	def __init__(self, type, item = None):
		if item:
			prio, match, user = item
			if prio is None or prio is '':
				prio = 50
			else:
				prio = int(prio)
			item = (int(prio), match, user)
		Field.__init__(self, type, item)
		self.match = Match(self.type, (self, self.item[1], self.item[1].user))
	
	def get_blank_item(self):
		return (50, type.Match(None, True))

	def __str__(self):
		prio, match = self.item
		return _("Match with priority %d") % prio
	
	def add_sub_field(self):
		self.match.add_sub_field()
	
	def get_sub_fields(self):
		return self.match.get_sub_fields()

	def add_edit_widgets(self, vbox):
		vbox.pack_start(
			g.Label(_("The priority is from 0 (low) to 100 (high).\n"
				"High priority matches take precedence over low ones.\n"
				"To add matches to this group, select it and click on Add.")),
				False, True, 0)
		prio = self.item[0]
		self.adj = g.Adjustment(prio, lower = 0, upper = 100, step_incr = 1)
		spinner = g.SpinButton(self.adj, 1, 0)
		vbox.pack_start(spinner, False, True, 0)
		spinner.set_activates_default(True)

	def add_to_node(self, node, child_edit = False):
		prio, match = self.item

		if child_edit:
			new = prio
		else:
			new = int(self.adj.value)

		magic_node = node.ownerDocument.createElementNS(FREE_NS, 'magic')
		if new != 50:
			magic_node.setAttributeNS(None, 'priority', str(new))
		node.appendChild(magic_node)
		match.write_xml(magic_node)
	
	def equals_node(self, node):
		prio, match = self.item
		node_prio = node.getAttributeNS(None, 'priority')
		if node_prio is None or node_prio is '':
			node_prio = 50
		else:
			node_prio = int(node_prio)
		if node_prio != prio:
			return False
		return match.equals_node(node)
	
	def delete_from_node(self, node):
		for x in node.childNodes:
			if x.nodeType != Node.ELEMENT_NODE: continue
			if x.localName == 'magic' and x.namespaceURI == FREE_NS:
				if self.equals_node(x):
					x.parentNode.removeChild(x)
					break
		else:
			raise Exception(_("Can't find this magic in Override.xml!"))

class Match(Field):
	# Note, this is a different class to type.Match!
	def get_blank_item(self):
		match = type.Match(None, True)
		match.type = 'string'
		match.offset = '0'
		match.value = ''
		match.mask = None
		return [None, match]
	
	def add_sub_field(self):
		new = Match(self.type)
		new.item[0] = self.item[0]
		new.item[1].parent = self.item[1]
		new.edit()
	
	def get_sub_fields(self):
		magic, match = self.item
		return [Match(self.type, (magic, m, m.user)) for m in match.matches]

	def add_edit_widgets(self, vbox):
		magic, match = self.item

		vbox.pack_start(
			g.Label(_("Offset is the position in the file to check at. It can be a\n"
				"single number, or a range in the form 'first:last'\n"
				"To add sub-matches to this one, select it and click on Add.")),
				False, True, 0)

		menu = g.Menu()
		for t in match_types:
			  item = g.MenuItem(t)
			  menu.append(item)
		self.match_type = g.OptionMenu()
		self.match_type.set_menu(menu)
		self.match_type.set_history(match_types.index(match.type))
		pair(vbox, 'Type', self.match_type)

		self.offset = g.Entry()
		pair(vbox, 'Offset(s)', self.offset)
		self.offset.set_activates_default(True)
		self.offset.set_text(match.offset)
		self.offset.grab_focus()

		self.value = g.Entry()
		pair(vbox, 'Value', self.value)
		self.value.set_text(match.value)
		self.value.set_activates_default(True)

		self.mask = g.Entry()
		pair(vbox, 'Mask', self.mask)
		self.mask.set_text(match.mask or '')
		self.mask.set_activates_default(True)
	
	def delete_from_node(self, node):
		magic, match = self.item
		assert match.parent

		magic.delete_from_node(node)

		match.parent.matches.remove(match)
		
		magic.add_to_node(node, child_edit = True)

	def commit_edit(self, box):
		magic, match = self.item

		offset = self.offset.get_text()
		value = self.value.get_text()

		if not offset or not value:
			raise Invalid(_('Missing offset or value'))

		doc, node = override.get_override_type(self.type.get_name())
		magic.delete_from_node(node)

		match.type = match_types[self.match_type.get_history()]
		match.offset = offset
		match.value = value
		match.mask = self.mask.get_text() or None
		
		if match not in match.parent.matches:
			match.parent.matches.append(match)

		magic.add_to_node(node, child_edit = True)
		override.write_override(doc)
	
	def __str__(self):
		magic, m = self.item
		text = '%s at %s = %s' % (m.type, m.offset, m.value)
		if m.mask:
			text += ' masked with ' + m.mask
		return text

class XML(Field):
	def get_blank_item(self):
		return (_('http://example.com'), _('documentElement'))

	def __str__(self):
		return _("<%s> with namespace '%s'") % (self.item[1], self.item[0])

	def add_edit_widgets(self, vbox):
		vbox.pack_start(
			g.Label(_("Enter the namespace URI and element name of the root element.")),
				False, True, 0)

		self.ns = g.Entry()
		self.ns.set_text(self.item[0])
		self.ns.set_activates_default(True)
		pair(vbox, _('Namespace'), self.ns)

		self.entry = g.Entry()
		self.entry.set_text(self.item[1])
		self.entry.set_activates_default(True)
		pair(vbox, _('Local name'), self.entry)

	def delete_from_node(self, node):
		ns = self.item[0]
		localName = self.item[1]
		for x in node.childNodes:
			if x.nodeType != Node.ELEMENT_NODE: continue
			if x.localName == 'root-XML' and x.namespaceURI == FREE_NS:
				if (x.getAttributeNS(None, 'namespaceURI') == ns and \
				    x.getAttributeNS(None, 'localName') == localName):
					x.parentNode.removeChild(x)
					break
		else:
			raise Exception(_("Can't find this root-XML in Override.xml!"))
	
	def add_to_node(self, node):
		new_ns = self.ns.get_text()
		new = self.entry.get_text()
		if (not new) and (not new_ns):
			raise Invalid(_("Name and namespace can't both be empty"))
		if ' ' in new or ' ' in new_ns:
			raise Invalid(_("Name and namespace can't contain spaces"))
		xmlroot = node.ownerDocument.createElementNS(FREE_NS, 'root-XML')
		xmlroot.setAttributeNS(None, 'namespaceURI', new_ns)
		xmlroot.setAttributeNS(None, 'localName', new)
		node.appendChild(xmlroot)

class Other(Field):
	def can_edit(self): return False
