###############################################################
# Name:			 Pref.py
# Purpose:	 Preference management
# Author:		 Luca Allulli (webmaster@roma21.it)
# Created:	 2009-05-01
# Copyright: Luca Allulli (https://www.skeed.it/songpress)
# License:	 GNU GPL v2
##############################################################

import copy
import string
from xml.dom import minidom


class Property(object):
	"""Structure for properties"""
	
	def __getValue(self):
		return self.__value
		
	def __setValue(self, value):
		if self.validator != None:
			valid, value = self.validator(value)
			if valid:
				self.__value = value
		else:
			self.__value = value
		if self.gui != None:
			self.gui.Changed()

	value = property(__getValue, __setValue)
	
	def __init__(self, value, validator = None, gui = None):
		object.__init__(self)
		self.validator = validator
		self.gui = gui(self) if gui != None else None
		self.value = value
	
		
class PropertyDef(object):
	"""Structure for property definitions"""
	def __init__(self, name, ptype, init = None, validator = None, gui = None):
		object.__init__(self)
		self.name = name
		self.ptype = ptype
		self.init = init
		self.validator = validator
		self.gui = gui


class XmlSerializer(object):
	def __init__(self):
		object.__init__(self)
		self.idn = 0
		self.dom = minidom.Document()
		self.ids = {}
		
	def RegisterId(self, p):
		self.idn += 1
		self.ids[p] = self.idn
		return str(self.idn)
		
	def GetId(self, p):
		return str(self.ids[p])
		
	def Serialize(self, p):
		self.dom.appendChild(p.XmlSerialize(self))
				
class XmlDeserializer(object):
	def __init__(self, dom, classes):
		object.__init__(self)
		self.dom = dom
		self.ids = {}
		self.classes = dict((c.__name__, c) for c in classes)
		
	def RegisterId(self, id, p):
		self.ids[id] = p
		
	def GetById(self, p):
		return self.ids[p]
		
	def Serialize(self, p):
		self.dom.appendChild(p.XmlSerialize(self))
		
	def GetClass(self, name):
		return self.classes[name]
		
	def Deserialize(self):
		l = []
		for el in self.dom.childNodes:
			l.append(Preferences.XmlDeserialize(self, el))
		return l
		
		
class NameNotFoundException(Exception):
	def __init__(self, arg):
		Exception.__init__(self, arg)


class Preferences(object):
	"""Class for structured property objects."""
	def __init__(self, parents = [], gui = None):
		self.__prefs = {}
		object.__init__(self)
		self.__parents = dict((x, x) for x in parents)
		self.__parentsByName = dict((x, x) for x in parents)
		self.__owner = None
		self.__CacheParents()
		self.__gui = gui
		for n, v in self.__GetAllPrefDef():
			f = v.init
			if f != None:
				self.__setattr__(n, f())
		
	prefDef = {}
	
	@classmethod
	def Register(cls, name, ptype, init = None, validator = None, gui = None):
		if not ('prefDef' in cls.__dict__):
			cls.prefDef = {}
		cls.prefDef[name] = PropertyDef(name, ptype, init, validator, gui)
		
	def __SetOwner(self, owner):
		self.__owner = owner
		self.__CacheParents()
		
	def __CacheParents(self):
		def Navigate(node, path):
			if node == None:
				return None
			if path == '..':
				return node.__owner
			if path in node.__prefs:
				return node.__prefs.value
			return None
		for n in self.__parentsByName:
			if type(n) == str:
				del self.__parents[self.__parentsByName[n]]
				val = reduce(Navigate, string.split(n, '/'), self)
				self.__parentsByName[n] = val
				self.__parents[val] = n		
		
	def __GetPrefByName(self, name):
		if name in self.__prefs:
			return self.__prefs[name]
		for p in self.__parents:
			try:
				return p.__GetPrefByName(name)
			except NameNotFoundException:
				pass
		raise NameNotFoundException(name)
	
	@classmethod
	def __GetPrefDefByName(cls, name):
		if name in cls.prefDef:
			return cls.prefDef[name]
		for b in cls.__bases__:
			try:
				return b.__GetPrefDefByName(name)
			except Exception:
				pass
		raise NameNotFoundException(name)
	
	def __getattr__(self, name):
		return self.__GetPrefByName(name).value
		
	def __setattr__(self, name, value):
		if name == '_Preferences__prefs':
			object.__setattr__(self, name, value)
			return
		try:
			pd = self.__GetPrefDefByName(name)
			assert isinstance(value, pd.ptype)
			if not name in self.__prefs:
				self.__prefs[name] = Property(value, pd.validator, pd.gui)
			else:
				self.__prefs[name].value = value
			if isinstance(value, Preferences):
				value.__SetOwner(self)
		except NameNotFoundException:
			object.__setattr__(self, name, value)
	
	def GetInherit(self, name):
		return not (name in self.__prefs)
		
	def SetInherit(self, name, inherit):
		if inherit:
			if name in self.__prefs:
				del self.__prefs[name]
		else:
			p = self.__GetPrefByName(name)
			self.__prefs[name] = Preferences.__DeepCopyPrefProp(p, None, self)
			
	def __GetAllPrefs(self):
		for n in self.__prefs:
			yield n, self.__prefs[n]
		for p in self.__parents:
			for n, v in p.__GetAllPrefs():
				yield n, v
	
	@classmethod
	def __GetAllPrefDef(cls):
		if 'prefDef' in cls.__dict__:	
			for n in cls.prefDef:
				yield n, cls.prefDef[n]
		for b in cls.__bases__:
			if isinstance(b, Preferences):
				for n, v in b.__GetAllPrefDef():
					yield n, v
	
	@staticmethod
	def __DeepCopyPrefProp(p, d, owner):
		q = Property(
			Preferences.__DeepCopyPref(p.value, d, owner) if isinstance(p.value, Preferences) else copy.deepcopy(p.value, d),
			copy.deepcopy(p.validator, d),
			copy.deepcopy(p.gui, d)
		)
		return q
	
	@staticmethod 
	def __DeepCopyPref(p, d, owner):
		c = Preferences(
			[pa for pa in p.__parentsByName],
			copy.deepcopy(p.__gui, d)
		)
		c.__owner = owner
		c.__CacheParents()
		for n in p.__prefs:
			c.__prefs[n] = Preferences.__DeepCopyPrefProp(p.__prefs[n], p)
		return c
		
	def XmlSerialize(self, xm):
		xd = xm.dom
		pref = xd.createElement("pref")
		pref.setAttribute("ptype", self.__class__.__name__)
		pref.setAttribute("id", xm.RegisterId(self))
		for n in self.__parentsByName:
			par = xd.createElement("parent")
			if type(n) == str:
				par.setAttribute("rel", n)
			else:
				par.setAttribute("id", xm.GetId(n))
			pref.appendChild(par)
		for n in self.__prefs:
			elem = xd.createElement("elem")
			elem.setAttribute("name", n)
			p = self.__prefs[n].value
			if isinstance(p, Preferences):
				elem.appendChild(p.XmlSerialize(xm))
			else:
				t = xd.createElement(type(p).__name__)
				t.appendChild(xd.createTextNode(str(p)))
				elem.appendChild(t)
			pref.appendChild(elem)
		return pref
		
	@staticmethod
	def XmlDeserialize(xm, pref):
		while pref.nodeName == '#text':
			pref = pref.nextSibling
		if pref.nodeName == 'pref':
			p = [c.getAttribute('rel') if c.hasAttribute('rel') else xm.GetById(c.getAttribute('id')) for c in pref.childNodes if c.nodeName == 'parent']
			c = xm.GetClass(pref.getAttribute('ptype'))
			assert issubclass(c, Preferences)
			pr = c(p)
			xm.RegisterId(pref.getAttribute('id'), pr)
			for c in pref.childNodes:
				if c.nodeName == 'elem':
					name = c.getAttribute('name')
					pr.__setattr__(name, pr.XmlDeserialize(xm, c.firstChild))
			return pr
		elif pref.nodeName == 'str':
			return str(pref.firstChild.nodeValue)
		elif pref.nodeName == 'int':
			return int(pref.firstChild.nodeValue)
			
			

