import os
import rox
from rox import g, basedir
from xml.parsers import expat
from xml.dom import XML_NAMESPACE, Node
import fields
import override
from override import FREE_NS

types = {}

home_mime = os.path.join(basedir.xdg_data_home, 'mime')

def data(node):
	return ''.join([text.nodeValue for text in node.childNodes
			if text.nodeType == Node.TEXT_NODE])

def get_type(name):
	if name not in types:
		types[name] = MIME_Type(name)
	return types[name]

class MIME_Type:
	def __init__(self, name):
		assert name not in types
		self.media, self.subtype = name.split('/')

		self.comments = []
		self.globs = []
		self.magic = []
		self.tree_magic = []
		self.xml = []
		self.others = []
	
	def add_comment(self, lang, comment, user):
		self.comments.append((lang, comment, user))
	
	def add_xml(self, uri, name, user):
		self.xml.append((uri, name, user))
	
	def add_magic(self, prio, root, user):
		self.magic.append((prio, root, user))

	def add_tree_magic(self, prio, matches, user):
		self.tree_magic.append((prio, matches, user))
	
	def add_other(self, element, user):
		self.others.append((element, user))

	def add_glob(self, pattern, user):
		self.globs.append((pattern, user))

	def get_comment(self):
		best = None
		for lang, comment, user in self.comments:
			if not lang:
				return comment
			best = comment
		return best or self.get_name()
	
	def get_name(self):
		return self.media + '/' + self.subtype
	
	def make(self, klass, list):
		return [klass(self, item) for item in list]

	def get_comments(self): return self.make(fields.Comment, self.comments)
	def get_globs(self): return self.make(fields.Glob, self.globs)
	def get_magic(self): return self.make(fields.Magic, self.magic)
	def get_tree_magic(self): return self.make(fields.TreeMagic, self.tree_magic)
	def get_xml(self): return self.make(fields.XML, self.xml)
	def get_others(self): return self.make(fields.Other, self.others)

	def remove_user(self):
		for list in ['comments', 'globs', 'magic', 'xml', 'others']:
			setattr(self, list,
				[x for x in getattr(self, list) if not x[-1]])
	
class FieldParser:
	def __init__(self, type, attrs):
		self.type = type

	def start(self, element, attrs): pass
	def data(self, data): pass
	def end(self): pass

class CommentParser(FieldParser):
	def __init__(self, type, attrs, user):
		FieldParser.__init__(self, type, attrs)
		self.lang = attrs.get(XML_NAMESPACE + ' lang', None)
		self.comment = ''
		self.user = user
	
	def data(self, data):
		self.comment += data
	
	def end(self):
		self.type.add_comment(self.lang, self.comment, self.user)
		
class Match:
	def __init__(self, parent, user):
		self.parent = parent
		self.matches = []
		self.user = user

		self.offset = self.type = self.value = self.mask = None

	def write_xml(self, node):
		for m in self.matches:
			new = node.ownerDocument.createElementNS(FREE_NS, 'match')
			new.setAttributeNS(None, 'offset', m.offset)
			new.setAttributeNS(None, 'type', m.type)
			new.setAttributeNS(None, 'value', m.value)
			if m.mask:
				new.setAttributeNS(None, 'mask', m.mask)
			node.appendChild(new)
			m.write_xml(new)
	
	def equals_node(self, node):
		if self.parent:
			if node.localName != 'match' or node.namespaceURI != FREE_NS:
				return False
			def get(x, d):
				a = node.getAttributeNS(None, x)
				if a is None or a == '': return d
				return a
			offset = get('offset', '?')
			type = get('type', '?')
			value = get('value', '?')
			mask = get('mask', None)
			if self.offset != offset or self.type != type or \
			   self.value != value or self.mask != mask:
				return False

		kids = []
		for k in node.childNodes:
			if k.nodeType != Node.ELEMENT_NODE: continue
			if k.localName != 'match' or k.namespaceURI != FREE_NS: continue
			kids.append(k)

		if len(self.matches) != len(kids):
			return False

		for m, k in zip(self.matches, kids):
			if not m.equals_node(k): return False

		return True

class MagicParser(FieldParser):
	def __init__(self, type, attrs, user):
		FieldParser.__init__(self, type, attrs)
		self.prio = int(attrs.get('priority', 50))
		self.match = Match(None, user)
		self.user = user
	
	def start(self, element, attrs):
		new = Match(self.match, self.user)
		new.offset = attrs.get('offset', '?')
		new.type = attrs.get('type', '?')
		new.value = attrs.get('value', '?')
		new.mask = attrs.get('mask', None)
		self.match.matches.append(new)
		self.match = new
	
	def end(self):
		if self.match.parent:
			self.match = self.match.parent
		else:
			self.type.add_magic(self.prio, self.match, self.user)

class TreeMatch:
	def __init__(self, parent, user):
		self.parent = parent
		self.matches = []
		self.user = user

		self.path = self.type = self.match_case = self.executable = self.non_empty = self.mimetype = None
	
	def __str__(self):
		return "<%s: %s %s %s %s %s>" % (self.path, self.type, self.match_case, self.executable, self.non_empty, self.mimetype)
		
class TreeMagicParser(FieldParser):
	def __init__(self, type, attrs, user):
		FieldParser.__init__(self, type, attrs)
		self.prio = int(attrs.get('priority', 50))
		self.parent = None
		self.match = self
		self.matches = []
		self.user = user
	
	def start(self, element, attrs):
		new = TreeMatch(self.match, self.user)

		new.path = attrs.get('path', '?')
		new.type = attrs.get('type', '?')
		new.match_case = attrs.get('match-case', '?')
		new.executable = attrs.get('executable', '?')
		new.non_empty = attrs.get('non-empty', '?')
		new.mimetype = attrs.get('mimetype', '?')

		self.match.matches.append(new)
		self.match = new
	
	def end(self):
		if self.match.parent:
			self.match = self.match.parent
		else:
			self.type.add_tree_magic(self.prio, self.matches, self.user)

	def __str__(self):
		return "treemagic: " + '\n'.join([str(m) for m in self.matches])
		
class Scanner:
	def __init__(self):
		self.level = 0
		self.type = None
		self.handler = None
	
	def parse(self, path, user):
		parser = expat.ParserCreate(namespace_separator = ' ')
		parser.StartElementHandler = self.start
		parser.EndElementHandler = self.end
		parser.CharacterDataHandler = self.data
		self.user = user
		parser.ParseFile(file(path))
		
	def start(self, element, attrs):
		self.level += 1
		if self.level == 1:
			assert element == FREE_NS + ' mime-info'
		elif self.level == 2:
			assert element == FREE_NS + ' mime-type'
			self.type = get_type(attrs['type'])
		elif self.level == 3:
			if element == FREE_NS + ' comment':
				self.handler = CommentParser(self.type, attrs,
								self.user)
			elif element == FREE_NS + ' glob':
				self.type.add_glob(attrs['pattern'], self.user)
			elif element == FREE_NS + ' magic':
				self.handler = MagicParser(self.type, attrs,
								self.user)
			elif element == FREE_NS + ' root-XML':
				self.type.add_xml(attrs['namespaceURI'],
						  attrs['localName'],
						  self.user)
			elif element == FREE_NS + ' treemagic':
				self.handler = TreeMagicParser(self.type, attrs, self.user)
			else:
				self.type.add_other(element, self.user)
		else:
			assert self.handler, "Unknown element <%s>" % element
			self.handler.start(element, attrs)
	
	def end(self, element):
		if self.handler:
			self.handler.end()
		self.level -=1
		if self.level == 1:
			self.type = None
		elif self.level == 2:
			self.handler = None
	
	def data(self, data):
		if self.handler:
			self.handler.data(data)

def scan_file(path, user):
	if not path.endswith('.xml'): return
	if not os.path.exists(path):
		return
	scanner = Scanner()
	try:
		scanner.parse(path, user)
	except:
		rox.report_exception()

def add_type(name, comment, glob):
	doc = override.get_override()

	root = doc.documentElement
	type_node = doc.createElementNS(FREE_NS, 'mime-type')
	root.appendChild(type_node)

	type_node.setAttributeNS(None, 'type', name)
	
	f = doc.createElementNS(FREE_NS, 'comment')
	f.appendChild(doc.createTextNode(comment))
	type_node.appendChild(f)
	
	if glob:
		f = doc.createElementNS(FREE_NS, 'glob')
		f.setAttributeNS(None, 'pattern', glob)
		type_node.appendChild(f)
	
	override.write_override(doc)
	import __main__
	__main__.box.show_type(name)

def delete_type(name):
	doc = override.get_override()
	removed = False
	for c in doc.documentElement.childNodes:
		if c.nodeType != Node.ELEMENT_NODE: continue
		if c.nodeName != 'mime-type': continue
		if c.namespaceURI != FREE_NS: continue
		if c.getAttributeNS(None, 'type') != name: continue
		doc.documentElement.removeChild(c)
		removed = True
	if not removed:
		rox.alert(_("No user-provided information about this type -- can't remove anything"))
		return
	if not rox.confirm(_("Really remove all user-specified information about type %s?") % name,
			   g.STOCK_DELETE):
		return
	override.write_override(doc)

# Type names defined even without Override.xml
system_types = None

def init():
	"(Re)read the database."
	global types, system_types
	if system_types is None:
		types = {}
		for mime_dir in basedir.load_data_paths('mime'):
			packages_dir = os.path.join(mime_dir, 'packages')
			if not os.path.isdir(packages_dir):
				continue
			packages = os.listdir(packages_dir)
			packages.sort()
			for package in packages:
				if package == 'Override.xml' and mime_dir == home_mime: continue
				scan_file(os.path.join(packages_dir, package), False)
		system_types = types.keys()
	else:
		for t in types.keys():
			if t not in system_types:
				del types[t]
		for t in types.values():
			t.remove_user()
	scan_file(override.user_override, True)
