#!/usr/bin/python

#
# GVGlue
# Generate dot files for latter use with graphviz
# Public Domain
# Written by Sebastien Tricaud <toady@gscore.org> 2007
# $Id: gvglue.py 310 2007-08-03 00:02:28Z str $
#
# WARNING: this code is hackish! don't write serious stuff
# on top of it
#
#
# Example:
#
# from gvglue import *
#
# if __name__ == "__main__":
# 	gvg = GvGlue()
#	
# 	radin = gvg.newItem("Picsou")
# 	maladroit = gvg.newItem("Donald", radin)
# 	fils1 = gvg.newItem("Riri", maladroit)
# 	fils2 = gvg.newItem("Fifi", maladroit)
# 	fils3 = gvg.newItem("Loulou", maladroit)
#
# 	gvg.subgraph_properties_defaults_add(radin)
# 	gvg.subgraph_properties_defaults_add(maladroit)
# 	gvg.subgraph_property_add(maladroit, "color", "#FF3300")
#
# 	mangedufromage = gvg.newItem("Souris")
# 	malentendant = gvg.newItem("Mickey", mangedufromage)
# 	safemmequihabitedansuneautremaison = gvg.newItem("Minnie", mangedufromage)
#
# 	gvg.subgraph_properties_defaults_add(mangedufromage)
#
# 	gvg.newLink(fils1, fils2)
# 	gvg.newLink(fils1, fils2)
# 	gvg.newLink(fils1, fils3)
# 	gvg.newLink(fils2, malentendant)
#
# 	gvg.terminate()
#
# 	for line in gvg.get():
# 		print line
#



import re

class GvGlue:

        data = []
	_id_ = 0
	_styles_ = {}
	_names_ = {}

        def __init__(self, properties=None):
		self.max_line_width = 10
		self.max_arrow_width = 2
		self.line_factor = 1
		self.arrow_factor = 0.5
		
		if ( properties ):
			self.data.append("digraph G {\n%s\n" % properties)
		else:
			self.data.append("digraph G {\nrankdir=LR;\nbgcolor=\"transparent\";\n")

        def append(self, string):
		self.data.append(string)

	def __id_string_get(self, myid):
		return "/* GvGlueID:%s */" % myid

	def __id_extract(self, string):
		return re.findall(r"\d+",string)[0]

	def __id_append(self):
		self._id_ += 1
		self.append(self.__id_string_get(self._id_))
		return self._id_

	def __id_insert(self, index):
		self._id_ += 1
		self.data.insert(index, self.__id_string_get(self._id_))
		return self._id_

	#
	# Returns the string and the index position of the node
	#
	def __id_data_get(self, myid):
		string = self.__id_string_get(myid)
		di = self.data.index(string)
		di = di + 1
		return self.data[di],di

	def name_to_id(self, name):
		return self._names_[name]

	def subgraph_append_last(self, subgraphname, label):
		# We are looking whether there is already a parent
		di = self.data.index("subgraph cluster_%s {\n" % subgraphname)
		recursive_indice = 0
		offset = di + 1
		for runner in self.data[di+1:]:
			if re.match("subgraph cluster_",runner):
				recursive_indice = recursive_indice + 1
			if recursive_indice == 0 and re.match("}\n", runner):
				iid = self.__id_insert(offset)
				self.data.insert(offset+1, "%d [label=\"%s\"];" % (iid, label))
				return iid
			if recursive_indice > 0 and re.match("}\n", runner):
				recursive_indice = recursive_indice - 1
			
			offset = offset + 1

	def subgraph_property_get(self, subgraphname, prop):
		di = self.data.index("subgraph cluster_%s {\n" % subgraphname)
		for runner in self.data[di+1:]:
			# Because we don't want to investigate to far away in the tree
			if not re.match("subgraph cluster_", runner) or not re.match("}"):
				(key, value) = runner.split("=")
				if ( key == prop ):
					# eeek! I should take some time to learn python
					return value.strip("\"").replace("\";","")
		# I must throw an exeption here
		return -1


	#
	# node_property_add and subgraph_property_add must be merged at some point
	#
	def node_property_add(self, nodeid, propertyname, value):
		string,di = self.__id_data_get(nodeid)
		# Hack! I should investigate why sometime -1 works and some other -2
		if re.match(string[len(string)-1], "]"):
			mystr = string[:len(string)-1] + ",%s=\"%s\"" % (propertyname, value) + "];"
		else:
			mystr = string[:len(string)-2] + ",%s=\"%s\"" % (propertyname, value) + "];"
		self.data.pop(di)
		self.data.insert(di, mystr)
		
	def subgraph_property_add(self, subgraphid, propertyname, value, getstr = None):
#		print "subgraphid = %d, propertyname = %s, value = %s" % (subgraphid,propertyname,value)
		try:
			di = self.data.index("subgraph cluster_%s {\n" % str(subgraphid))
			value_changed = 0
			for runner in self.data[di+1:]:
				if runner.find("subgraph_"):
					break
# 				if runner.find("}"):
# 					break
				if runner.find("%s=" % propertyname) != -1:
# 					print propertyname + "="
					i = self.data.index(runner)
					self.data.pop(di + i)
					mystr = "%s=\"%s\";" % (propertyname, value)
#					print mystr
					if not getstr:
						self.data.insert(di+i, mystr)
					value_changed = 1
					break

			if not value_changed:
				mystr = "%s=\"%s\";" % (propertyname, value)
# 				print mystr
				if not getstr:
					self.data.insert(di+1, mystr)

			return mystr

		except:
			# If we catch an exception, this is because
			# the item is not a parent but a simple node
			try:
				for runner in self.data:
					if runner.find("%d [label=" % subgraphid) != -1:
						i = self.data.index(runner)
						mystr = runner[:len(runner)-1] + ",%s=\"%s\"" % (propertyname, value) + "];"
						self.data.pop(i)
						if not getstr:
							self.data.insert(i, mystr)
				return mystr
			#
			# When nothing exists (such as when adding default properties)
			#
			except:
				return -1

	#
	# Decides wether it is a node or a subgraph
	#
	def property_add(self, id, property, value):
		string,di = self.__id_data_get(id)
		if re.match("subgraph cluster_", string):
			self.subgraph_property_add(id, property, value)
		else:
			self.node_property_add(id, property, value)

	# Adds a plain property which is not a key=val
	def subgraph_property_plain_add(self, subgraphid, propertyname, value):
		di = self.data.index("subgraph cluster_%s {\n" % subgraphid)
		offset = di+1
		for runner in self.data[di+1:]:
			if runner.find("=") == -1:
				self.data.insert(offset, "%s [%s];" % (propertyname, value))
				return
				
			offset += 1
	
	def subgraph_properties_defaults_style_add(self, subgraphid):
		self.property_add(subgraphid, "shape", "ellipse")
		self.property_add(subgraphid, "style", "filled")
		self.property_add(subgraphid, "color", "#d7a9e3")
		self.subgraph_property_plain_add(subgraphid, "node", "style=filled,color=yellow")

	def properties_style_add(self, stylename, propertyname, value):

		if not self._styles_.has_key(stylename):
			self._styles_[stylename] = []

		data = [str(propertyname),str(value)]
		self._styles_[stylename].append(data)


	def properties_style_debug(self, stylename):
		for array in self._styles_[str(stylename)]:
			print "DEBUG:" + str(array)

	# TODO
	def properties_style_del(self, stylename, propertyname):
		pass
	
	def properties_style_get(self, id):
 		return self._styles_[id]

	def properties_style_apply(self, stylename, id):
		data = self._styles_[stylename]
		for p,v in data:
			self.property_add(id, p, v)

	def property_key_extract(self, property):
		key, val = property.split("=")
		return key

	def property_value_extract(self, property):
		key, value = property.split("=")
		return value.strip("\"").replace("\";","")

	def terminate(self):
		self.append("}")


        def get(self):
		return self.data

	def output(self):
		for line in self.get():
			print line

	def finish(self):
		self.terminate()
		self.output()

	# Adds an item to a parent
	# if the parent is not one yet, transform the node
	# as a parent.
	# _FIXME_: When properties have been added to a node, 
	# take them for the cluster
        def newItem(self, name, parent=None, distinct=None, retname=None):
		if not parent :
			if distinct:
				for runner in self.data:
					if runner.find("[label=\"%s\"];" % name) != -1:
						i = self.data.index(runner)
						a = re.findall(r"\d+",self.data[i])
						if retname:
							if not self._names_.has_key(name):
								self._names_[name] = []
								self._names_[name] = a[0]
							return name
						else:
							return a[0]

			# This is a simple case: no parents, no problems ;)
			iid = self.__id_append()
			self.append("%d [label=\"%s\"];" % (iid, name))
			if retname:
				if not self._names_.has_key(name):
					self._names_[name] = []
					self._names_[name] = iid
				return name
			else:
				return iid
		else:
			try:
				if distinct:
					for runner in self.data:
						if runner.find("[label=\"%s\"];" % name) != -1:
							i = self.data.index(runner)
							a = re.findall(r"\d+",self.data[i])
							if retname:
								if not self._names_.has_key(name):
									self._names_[name] = []
									self._names_[name] = a[0]
								return name
							else:
								return a[0]

				if retname:
					if not self._names_.has_key(name):
						self._names_[name] = []
						self._names_[name] = self.subgraph_append_last(parent,name)

					return name
				else:
					return self.subgraph_append_last(parent,name)
			except:
				# This is a simple node we should transform as a parent
				try:
					string = self.__id_string_get(parent)
					di = self.data.index(string)
					di = di + 1
					label = self.data[di]
					# Eeek! I have to learn python someday
					(g1, label, g2) = label.split("\"")
					self.data.popx(di)
					self.data.insert(di, "subgraph cluster_%s {\n" % parent)
					self.data.insert(di+1, "label=\"%s\";" % label)
					iid = self.__id_insert(di+2)
					self.data.insert(di+3, "%d [label=\"%s\"];" % (iid, name))
					self.data.insert(di+4, "}\n")
					if retname:
						if not self._names_.has_key(name):
							self._names_[name] = []
							self._names_[name] = iid
						return name
					else:
						return iid
				except:
#					print "We can't transform the node " + name + " as a parent"
					return -1

        def newItemStyle(self, name, style, parent=None, distinct=None):
		i = self.newItem(name, parent, distinct)
		self.properties_style_apply(style, i)
		return i

	def newLink(self, itemA, itemB, label=None): 
		# First of all, we try to locate existing connections
		for runner in self.data:
			# Case #1: We find itemA -> itemB and we increase both arrow and line
			if runner.find("%d->%d" % (int(itemA),int(itemB))) != -1:
				i          = self.data.index(runner)
				numbers    = re.findall(r"\d+",runner)
				source     = int(numbers[0])
				target     = int(numbers[1])
				linewidth  = int(numbers[2])
				arrowwidth = int(numbers[3])

				if linewidth < self.max_line_width:
					linewidth = linewidth + self.line_factor
				if arrowwidth < self.max_arrow_width:
					arrowwidth = arrowwidth + self.arrow_factor

				self.data.pop(i)
				if label:
					self.data.insert(i,
							 "%s->%s [style=\"setlinewidth(%s)\",arrowsize=\"%s\",label=\"%s\"]"
							 % (source,target,linewidth,arrowwidth,label))
					return self.__id_extract(self.data[i-1])
				else:
					self.data.insert(i,
							 "%s->%s [style=\"setlinewidth(%s)\",arrowsize=\"%s\"]"
							 % (source,target,linewidth,arrowwidth))
					return self.__id_extract(self.data[i-1])
					
				return 0
			# Case #2: Just like #1 but for a double arrow. Actually the code should be merged at some point
			if runner.find("%d<->%d" % (int(itemA),int(itemB))) != -1:
				i          = self.data.index(runner)
				numbers    = re.findall(r"\d+",runner)
				source     = int(numbers[0])
				target     = int(numbers[1])
				linewidth  = int(numbers[2])
				arrowwidth = int(numbers[3])

				if linewidth < self.max_line_width:
					linewidth = linewidth + self.line_factor
				if arrowwidth < self.max_arrow_width:
					arrowwidth = arrowwidth + self.arrow_factor

				self.data.pop(i)
				if label:
					self.data.insert(i,
							 "%s<->%s [style=\"setlinewidth(%s)\",arrowsize=\"%s\",label=\"%s\"]"
							 % (source,target,linewidth,arrowwidth,label))
					return self.__id_extract(self.data[i-1])
				else:
					self.data.insert(i,
							 "%s<->%s [style=\"setlinewidth(%s)\",arrowsize=\"%s\"]"
							 % (source,target,linewidth,arrowwidth))
					return self.__id_extract(self.data[i-1])

				return 0

		# Latest Case: none of the above cases were found, so we add a regular node
		if label:
			iid = self.__id_append()
			self.data.insert(len(self.data), str(itemA) + "->" + str(itemB) + " [style=\"setlinewidth(1)\",arrowsize=\"1\",label=\"%s\"]" % label)
			return iid
		else:
			iid = self.__id_append()
			self.data.insert(len(self.data), str(itemA) + "->" + str(itemB) + " [style=\"setlinewidth(1)\",arrowsize=\"1\"]")
			return iid
		


