'''
Colin Gillespie (c.gillespie@ncl.ac.uk)
Last modified: 1/4/05

If want change the shape/colour/etc of a node or arrow
look at VisualiseNodeClasses
'''
'''
Small changes to stoichiometry and modifiers marked as # brett 20050702
Brett G. Olivier (bgoli@users.sourceforge.net)
'''
from __future__ import division, print_function
from __future__ import absolute_import
from __future__ import unicode_literals

import pydot
from .VisualiseNodeClasses import DotNodes, EdgeLines

class VisualiseModel(DotNodes, EdgeLines):
	"""The visualisation model class"""
	
	def	__init__(self, m):
		'''
		m is a libsbml getModel file
		initialise a few short-cuts here
		'''
		self.m = m
		self.py_dot_graph = pydot.Dot(type='digraph', graph_name='graphName', \
			rankdir = "LR")
		self.add_edge = self.py_dot_graph.add_edge
		self.add_node = self.py_dot_graph.add_node
		DotNodes.__init__(self, m)

	def __createCompartmentDict(self):
		"""creates a dictionary with all compartments as keys and a 
			pydot cluster as a value
		"""
		compart_dict = {}
		for i, co in enumerate(self.m.getListOfCompartments()):
			compart_dict[co.getId()] = [co.getOutside()]
			node_attribute = self.compartmentNode(i+1)
			compart_dict[co.getId()].append(pydot.Cluster(**node_attribute))
		return compart_dict
	
	def __organiseCompartments(self, compart_dict):
		"""organises the compartments, using outside"""
		for key in list(compart_dict.keys()):
			outside = compart_dict[key][0]
			if len(outside) ==0:
				self.py_dot_graph.add_subgraph(compart_dict[key][1])
			else:
				compart_dict[outside][1].add_subgraph(compart_dict[key][1])
		return self.py_dot_graph	

	def __isGraph2Big(self):
		'''Is the model too big'''
		if (self.m.getNumReactions() + self.m.getNumSpecies()) < 250:
			return 0

		dict1 = {'URL':'#', 'label':'There are too many reactions/ \
					species to be display'}
		node_attribute = self.compartmentNode(1, dict1)
		cluster = pydot.Cluster(**node_attribute)
		
		node_attribute = self.reactionNode(2, {'URL':'#'})
		node_attribute['label'] = '%d Reactions' % self.m.getNumReactions()
		cluster.add_node(pydot.Node('reactions', **node_attribute))
		
		node_attribute = self.specieNode(2, {'URL':'#'})
		node_attribute['label'] = '%d species' % self.m.getNumSpecies()
		cluster.add_node(pydot.Node('species', **node_attribute))
		
		self.py_dot_graph.add_subgraph(cluster)
		return 1
			
	def __isCompartments(self):
		'''Does the model have any compartments'''
		if self.m.getNumCompartments() > 0:
			return 0
		
		node_attribute = {'graph_name':'empty model', 'label':'empty model', \
				'fontsize':10, 'fontname':'Arial'}
		node_attribute['URL'] = '#'
		cluster = pydot.Cluster(**node_attribute)
			
		node_attribute = {'color':'moccasin', 'shape':'rectangle',
			'style':'filled', 'label':'0 species', 'fontsize':10}
		node_attribute['fontname'] = 'Arial'
		node_attribute['URL'] = '#'
		cluster.add_node(pydot.Node('species', **node_attribute))
	
		self.py_dot_graph.add_subgraph(cluster)
		return 1
	
	def __isSpecies(self):
		'''Does the model have any species'''
		if self.m.getNumSpecies() > 0:
			return 0
		
		node_attribute = self.compartmentNode(1)
		cluster = pydot.Cluster(**node_attribute)
		node_attribute = {'color':'moccasin', 'shape':'rectangle',
			'style':'filled', 'label':'0 species', 'fontsize':10}
		node_attribute['fontname'] = 'Arial'
		node_attribute['URL'] = '#'
		cluster.add_node(pydot.Node('species', **node_attribute))
		
		self.py_dot_graph.add_subgraph(cluster)
		return 1
	
	def __addReactionToCompartment(self, sp_ref):
		'''Have to put the reaction into a compartment so
		choose the first reactant and stick it that compartment
		If there are no reactants than get the first product
		'''
		for sp in self.m.getListOfSpecies():
			if sp.getId() == sp_ref.getSpecies():
				return 1
	
	def getPyDot(self):
		'''main method'''
		#If there are too many reactions
		
		if self.__isGraph2Big():
			return self.py_dot_graph
		
		if self.__isCompartments():
			return self.py_dot_graph
			
		if self.__isSpecies():
			return self.py_dot_graph

		

		compart_dict = self.__createCompartmentDict()
		#Add the species into a compartment
		for i, sp in enumerate(self.m.getListOfSpecies()):
			node_attribute = self.specieNode(i+1)
			compart_dict[sp.getCompartment()][1].add_node(
				pydot.Node(sp.getId(), **node_attribute))
	
		for i, reaction in enumerate(self.m.getListOfReactions()):
			node_attribute = self.reactionNode(i+1)
			for j, sp_ref in enumerate(reaction.getListOfReactants()):
				if j == 0 and self.__addReactionToCompartment(sp_ref):
					compart_dict[sp.getCompartment()][1].add_node(
						pydot.Node(reaction.getId(), **node_attribute))
				
				# stoc = int(sp_ref.getStoichiometry()) # brett 20050702
				stoc = sp_ref.getStoichiometry() 		# brett 20050702: allow for non-int stoich
				
				edge_attribute = self.edgeNode(reaction, stoc).reactant()
				edge = pydot.Edge(sp_ref.getSpecies(), 
					reaction.getId(), **edge_attribute)
				self.add_edge(edge)
				
			for j, sp_ref in enumerate(reaction.getListOfProducts()):
				if j == 0 and not reaction.getNumReactants() and \
					self.__addReactionToCompartment(sp_ref):
					compart_dict[sp.getCompartment()][1].add_node(
							pydot.Node(reaction.getId(), **node_attribute))
						
				#stoc = int(sp_ref.getStoichiometry()) 	# brett 20050702
				stoc = sp_ref.getStoichiometry() 		# brett 20050702: allow for non-int stoich
			
				edge_attribute = self.edgeNode(reaction, stoc).product()
				edge = pydot.Edge(reaction.getId(), 
						sp_ref.getSpecies(), **edge_attribute)
				self.add_edge(edge)
	
			for sp_ref in reaction.getListOfModifiers():
				edge_attribute = self.edgeNode(reaction).modifier()
				edge = pydot.Edge(sp_ref.getSpecies(), reaction.getId(), **edge_attribute)
				self.add_edge(edge)
				# perhaps modifiers should only go from species to reaction # brett 20050702
				# edge = pydot.Edge(reaction.getId(), sp_ref.getSpecies(), **edge_attribute) # brett 20050702
				# self.add_edge(edge) # brett 20050702

		return self.__organiseCompartments(compart_dict)
	
class VisualiseReaction(DotNodes, EdgeLines):
	'''Used to visualise a single reaction'''
	
	def	__init__(self, m, re_num):
		'''
		m is libsbml.getModel 
		re_num is the reaction to be visualised
		'''
		
		self.m = m
		self.py_dot_graph = pydot.Dot(type='digraph', graph_name='graphName', \
			rankdir = "LR")
		self.add_edge = self.py_dot_graph.add_edge
		self.add_node = self.py_dot_graph.add_node
		DotNodes.__init__(self, m)
		
		self.re_num = re_num
		self.re = self.m.getReaction(re_num-1)
		
		DotNodes.__init__(self, m)

	def getPyDot(self):
		'''Returns a pyDot object'''
				
		#Create the reaction node
		node_attribute = self.reactionNode(self.re_num)
		self.add_node(pydot.Node(self.re.getId(), **node_attribute))
			
		types = ['Reactants', 'Products', 'Modifiers']
		for typ in types:
			for sp_ref in getattr(self.re, 'getListOf' + typ)():
				sp_ref_id = sp_ref.getSpecies()
				self.__findMatchingSpecies(sp_ref_id)
			
				#stoic = int(sp_ref.getStoichiometry())	# brett 20050702
				stoic = sp_ref.getStoichiometry()  		# brett 20050702: allow for non-int stoich
				
				#XXX: Should be .type not .reactant
				edge_attribute = self.edgeNode(self.re, stoic).reactant()
				if typ != 'Products':
					edge = pydot.Edge(sp_ref_id, self.re.getId(), **edge_attribute)
					self.add_edge(edge)
				if typ != 'Reactants':
					edge = pydot.Edge(self.re.getId(), sp_ref_id, **edge_attribute)
					self.add_edge(edge)
		return self.py_dot_graph			
				
	def __findMatchingSpecies(self, sp_ref_id):
		'''determines where the sp_ref is in listOfSpecies'''
		
		#TODO: make a species list, so we don't keep cycling through sp!
		for i, sp in enumerate(self.m.getListOfSpecies()):
			if sp.getId() == sp_ref_id:
				node_attribute = self.specieNode(i+1)
				self.add_node(pydot.Node(sp_ref_id, **node_attribute))
				break
		return 1

class VisualiseSpecies(DotNodes, EdgeLines):
	'''Visualise a single species'''
	
	def	__init__(self, m, sp_num):
		'''takes in a sbml and validates it'''
		self.m = m
		self.py_dot_graph = pydot.Dot(type='digraph', graph_name='graphName', \
			rankdir = "LR")
		self.add_edge = self.py_dot_graph.add_edge
		self.add_node = self.py_dot_graph.add_node
		DotNodes.__init__(self, m)
		
		self.sp_num = sp_num
		self.sp_id = self.m.getSpecies(sp_num-1).getId()
	
	def getPyDot(self):
		'''If you want to visualise a species'''
		#Create the species node
		node_attribute = self.specieNode(self.sp_num)
		self.add_node(pydot.Node(self.sp_id, **node_attribute))
				
		for i, reaction in enumerate(self.m.getListOfReactions()):
			self.__checkReactionTypesForSpecies(reaction, i)
		return self.py_dot_graph
			
	def __checkReactionTypesForSpecies(self, reaction, reaction_num):
		'''Checks all the species references to determine if the sp is 
		in this reaction
		'''
		types = ['Reactants', 'Products', 'Modifiers']
		for typ in types:
			for sp_ref in getattr(reaction, 'getListOf' + typ)():
				if sp_ref.getSpecies() == self.sp_id:
					node_attribute = self.reactionNode(reaction_num+1)
					r_id = reaction.getId()
					self.add_node(pydot.Node(r_id, **node_attribute))
					
					# stoic = int(sp_ref.getStoichiometry())# brett 20050702
					stoic = sp_ref.getStoichiometry()  		# brett 20050702: allow for non-int stoich
					
					#XXX: Change .modifier
					edge_attribute = self.edgeNode(reaction, stoic).reactant()
					if typ != 'Reactants':
						self.add_edge(pydot.Edge(r_id, self.sp_id, **edge_attribute))
					if typ != 'Products':
						self.add_edge(pydot.Edge(self.sp_id, r_id, **edge_attribute))
					return 1


	
