# -*- coding: utf-8 -*-
#######################################################################################
#   Photo is a part of Plinn - http://plinn.org                                       #
#   Copyright (C) 2008 Benoît PIN <benoit.pin@ensmp.fr>                               #
#                                                                                     #
#   This program is free software; you can redistribute it and/or                     #
#   modify it under the terms of the GNU General Public License                       #
#   as published by the Free Software Foundation; either version 2                    #
#   of the License, or (at your option) any later version.                            #
#                                                                                     #
#   This program is distributed in the hope that it will be useful,                   #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of                    #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     #
#   GNU General Public License for more details.                                      #
#                                                                                     #
#   You should have received a copy of the GNU General Public License                 #
#   along with this program; if not, write to the Free Software                       #
#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
#######################################################################################
""" XMP generation utilities.



"""

from libxml2 import newNode, parseDoc, treeError
# prefix <-> namespaces mappings as defined in the official xmp documentation
from standards.xmp import namespaces as xmpNs2Prefix
from standards.xmp import prefix2Ns as xmpPrefix2Ns

TIFF_ORIENTATIONS = {1 : (0, False)
					,2 : (0, True)
					,3 : (180, False)
					,4 : (180, True)
					,5 : (90, True)
					,6 : (90, False)
					,7 : (270, True)
					,8 : (270, False)}

def _getRDFArrayValues(node, arrayType):
	values = []
	for element in iterElementChilds(node):
		if element.name == arrayType and element.ns().content == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' :
			for value in iterElementChilds(element):
				if value.name == 'li':
					values.append(value.content)
			return tuple(values)
	else :
		raise ValueError("No %s found" % arrayType )

def getBagValues(node):
	return _getRDFArrayValues(node, 'Bag')

def getSeqValues(node):
	return _getRDFArrayValues(node, 'Seq')


def createRDFAlt(surrounded, defaultText, rootIndex):
	"""
	returns (as libxml2 node):
	<surrounded>
	   <rdf:Alt>
	      <rdf:li xml:lang="x-default">defaultText</rdf:li>
	   </rdf:Alt>
	<surrounded>
	"""
	docNs = rootIndex.getDocumentNs()
	rdfPrefix = docNs['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
	normalizedPrefix, name = surrounded.split(':')
	ns = xmpPrefix2Ns[normalizedPrefix]
	actualPrefix = docNs[ns]
	
	surrounded = newNode('%s:%s' % (actualPrefix, name))
	alt = newNode('%s:Alt' % rdfPrefix)
	li = newNode('%s:li' % rdfPrefix)
	li.newProp('xml:lang', 'x-default')
	li.setContent(defaultText)
	
	reduce(lambda a, b: a.addChild(b), (surrounded, alt, li))
	
	return surrounded


def createRDFBag(surrounded, values, rootIndex):
	"""
	returns (as libxml2 node):
	<surrounded>
       <rdf:Bag>
          <rdf:li>values[0]</rdf:li>
			...
          <rdf:li>values[n]</rdf:li>
       </rdf:Bag>
    <surrounded>
    """
	return _createRDFArray(surrounded, values, False, rootIndex)

def createRDFSeq(surrounded, values, rootIndex):
	"""
	returns (as libxml2 node):
	<surrounded>
       <rdf:Seq>
          <rdf:li>values[0]</rdf:li>
			...
          <rdf:li>values[n]</rdf:li>
       </rdf:Seq>
    <surrounded>
    """
	return _createRDFArray(surrounded, values, True, rootIndex)

def _createRDFArray(surrounded, values, ordered, rootIndex):
	docNs = rootIndex.getDocumentNs()
	rdfPrefix = docNs['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
	normalizedPrefix, name = surrounded.split(':')
	ns = xmpPrefix2Ns[normalizedPrefix]
	actualPrefix = docNs[ns]
	
	
	surrounded = newNode('%s:%s' % (actualPrefix, name))
	if ordered is True :
		array = newNode('%s:Seq' % rdfPrefix)
	elif ordered is False :
		array = newNode('%s:Bag' % rdfPrefix)
	else :
		raise ValueError("'ordered' parameter must be a boolean value")
	
	surrounded.addChild(array)
	
	for v in values :
		li = newNode('%s:li' % rdfPrefix)
		li.setContent(v)
		array.addChild(li)
	
	return surrounded

def createEmptyXmpDoc() :
	emptyDocument = """
<x:xmpmeta xmlns:x='adobe:ns:meta/'>
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description/>
  </rdf:RDF>
</x:xmpmeta>
	"""
	d = parseDoc(emptyDocument)
	return d

def getPathIndex(doc) :
	root = doc.getRootElement()
	index = PathIndex(root)
	return index


class PathIndex :
	"""\
	Class used to provide a convenient tree access to xmp properties by paths.
	Issues about namespaces and prefixes are normalized during the object
	instanciation. Ns prefixes used to access elements are those recommended in the
	official xmp documentation from Adobe.
	"""
	
	def __init__(self, element, parent=None) :
		self.unique = True
		self.element = element
		self.parent = parent
		
		elementNs = element.ns().content
		elementPrefix = element.ns().name
		recommendedPrefix = xmpNs2Prefix.get(elementNs, elementPrefix)

		self.name = '%s:%s' % (recommendedPrefix, element.name)
		self.namespace = elementNs
		self.prefix = elementPrefix
		self._index = {}
		
		for prop in iterElementProperties(element) :
			self.addChildIndex(prop)
		
		for child in iterElementChilds(element) :
			self.addChildIndex(child)
		
		if self.parent is None:
			self.nsDeclarations = self._namespaceDeclarations()
		
	def addChildIndex(self, child) :
		ns = child.ns()
		if not ns :
			return
			
		childNs = ns.content
		childPrefix = ns.name
		childRecommendedPrefix = xmpNs2Prefix.get(childNs, childPrefix)
		childName = '%s:%s' % (childRecommendedPrefix, child.name)

		if not self._index.has_key(childName) :
			self._index[childName] = PathIndex(child, parent=self)
		else :
			childIndex = self._index[childName]
			childIndex.unique = False
			for prop in iterElementProperties(child) :
				childIndex.addChildIndex(prop)

			for c in iterElementChilds(child) :
				childIndex.addChildIndex(c)

		self._index[childName].parent = self
		return self._index[childName]
	
	def _namespaceDeclarations(self) :
		"""\
		returns ns / prefix pairs as found in xmp packet
		"""
		namespaces = {}
		namespaces[self.namespace] = self.prefix
		for child in self._index.values() :
			for namespace, prefix in child._namespaceDeclarations().items() :
				if namespaces.has_key(namespace) :
					assert namespaces[namespace] == prefix, \
							"using several prefix for the same namespace is forbidden "\
							"in this implementation"
				else :
					namespaces[namespace] = prefix
		return namespaces
	
	def getDocumentNs(self) :
		root = self.getRootIndex()
		return root.nsDeclarations
	
	def exists(self, path) :
		o = self
		for part in path.split('/') :
			if o._index.has_key(part) :
				o = o._index[part]
			else :
				return False
		return True
	
	def __getitem__(self, path) :
		o = self
		try :
			for part in path.split('/') :
				if part == '.' :
					continue
				elif part == '..' :
					o = o.parent
				o = o._index[part]
		except ValueError :
			raise KeyError, path
		return o
	
	def get(self, path, default=None) :
		try :
			return self[path]
		except KeyError :
			return default
	
	def getRootIndex(self) :
		root = self
		while root.parent is not None :
			root = root.parent
		return root
	
	def createChildAndIndex(self, name, rdfType, nsDeclarationElement) :
		recommandedPrefix, name = name.split(':', 1)

		if rdfType == 'prop' :
			try :
				node = self.element.newProp(name, '')
			except treeError :
				raise ValueError, (self.element, name)
		else :
			node = newNode(name)
			self.element.addChild(node)
		
		# bind namespace to new node
		uri = xmpPrefix2Ns[recommandedPrefix]
		docNamespaces = self.getDocumentNs()
		if not docNamespaces.has_key(uri) :
			try :
				ns = nsDeclarationElement.newNs(uri, recommandedPrefix)
			except treeError :
				raise ValueError, (uri, prefix, self.element, list(nsDeclarationElement.nsDefs()))
			docNamespaces[uri] = recommandedPrefix
		else :
			actualPrefix = docNamespaces[uri]
			try :
				ns = self.element.searchNs(None, actualPrefix)
			except treeError:
				# cas d'un xmp verbeux : le nouvel élément n'est pas ajouté
				# dans le rdf:Description du ns correspondant
				# (après tout, ce n'est pas une obligation)
				# => on ajoute le ns
				ns = nsDeclarationElement.newNs(uri, actualPrefix)
			
		
		node.setNs(ns)
		return self.addChildIndex(node)

	def getOrCreate(self, path, rdfType, preferedNsDeclaration='rdf:RDF/rdf:Description') :
		parts = path.split('/')

		if not parts :
			return self

		name = parts[-1]
		parts = parts[:-1]
		root = self.getRootIndex()
		nsDeclarationElement = root[preferedNsDeclaration].element
		
		parent = self
		for p in parts :
		 	child = parent._index.get(p, None)
			if child is None :
				child = parent.createChildAndIndex(p, None, nsDeclarationElement)
			parent = child
		
	 	child = parent._index.get(name, None)
		if child is None :
			child = parent.createChildAndIndex(name, rdfType, nsDeclarationElement)

		return child
		
	def __str__(self) :
		out = []
		pr = out.append
		path = [self.name]
		parent = self.parent
		while parent :
			path.append(parent.name)
			parent = parent.parent
		path.reverse()
		path = '/'.join(path)
		pr(path)
		pr(self.name)
		pr(self.namespace)
		pr(str(self.unique))
		pr('-------')
		
		for child in self._index.values() :
			pr(str(child))

		return '\n'.join(out)

def iterElementChilds(parent) :
	child  = parent.children
	while child :
		if child.type == 'element' :
			yield child
		child = child.next

def iterElementProperties(element) :
	prop = element.properties
	while prop :
		if prop.type == 'attribute' :
			yield prop
		prop = prop.next
