#
# $Id: simclasses.py,v 1.1.1.1 2003/03/22 05:15:49 dbelan2 Exp $
#
# This code was modified to be able to save the model file
# in a format understandable by the experiment environment.
# 
# $Log: simclasses.py,v $
# Revision 1.1.1.1  2003/03/22 05:15:49  dbelan2
# Initial import of all public_html on www.cs.mcgill.ca.
#
# Revision 1.7  2001/09/25 01:14:43  dbelan2
# - Output format changed.  Added 1 line:
#   self.model = model  to make it work in class def.
#
# Revision 1.6  2001/09/22 00:33:07  dbelan2
# Added support to generate code for relays.
#
# Revision 1.5  2001/09/20 19:30:31  dbelan2
# ^M removed.
#
# Revision 1.4  2001/09/20 18:00:21  dbelan2
# - now generates code to set constant values.
#
# Revision 1.3  2001/09/20 17:28:03  dbelan2
# Bug fix in export() code generation of names.
#
# Revision 1.2  2001/09/20 15:49:16  dbelan2
# Export method implemented.  Works but not fully tested yet.
#
#
#
#

from math import *

#import modelSorter
import tkMessageBox


class Model:
	def __init__(self, dc = None, file = None):
		self.objList = []
                self.tag2ObjMap = {}
                self.tabOrder = 0
                self.dc = dc
                self.namePrefix = 'Unit'
                self.fileName = None
                if file != None:
                        self.Open(file)

        def Add(self, objname, xpos, ypos):
                exec('temp = ' + objname + '(' + str(xpos) + ',' + str(ypos) + ')')
                self.AddObject(temp)
        
	def AddObject(self, obj):
                self.objList.append(obj)
		
                self.tabOrder = self.tabOrder + 1
                name = self.namePrefix + str(self.tabOrder)
                
                while name in self.tag2ObjMap.keys():
                        self.tabOrder = self.tabOrder + 1
                        name = self.namePrefix + str(self.tabOrder)
                        
                obj.tag = name
                self.tag2ObjMap[obj.tag] = obj
                
                if self.dc != None:
                        obj.Draw(self.dc)
        
        def Remove(self, obj):
                obj.Destroy()
                self.objList.remove(obj)
                del self.tag2ObjMap[obj.tag]
                
        def Draw(self):
                for obj in self.objList:
                        obj.Draw(self.dc)

        def Clear(self):
                for obj in self.objList:
                        obj.Destroy()

                self.objList = []
                self.tag2ObjMap = {}
                self.tabOrder = 0
                
        def Open(self, file):
                self.Clear()
                self.fileName = file

                execfile(file)
                if self.dc != None:
                        self.Draw()
        
        # geeft een 1 terug als sorteren mislukt, 2 als er geen filename is
        def Save(self, file = None, sort = 0):
                # save
                if file == None:
                        if self.fileName == None:
                                return 2
                        else:
                                file = open(self.fileName, 'w')
                                                
                self.fileName = file.name

                res = 0
                if sort == 1:
                        m = modelSorter.ModelSorter(self)
                        res = m.Sort()                        
        
                for obj in self.objList:
                        file.write(obj.SerializeString() + '\n')
                        file.write('self.AddObject(' + obj.tag + ')' + '\n')
                        file.write('self.SetTag(' + obj.tag + ',\'' + obj.tag + '\')' + '\n')


                for i in range(len(self.objList)):
                        current = self.objList[i]
                        file.write(current.tag + '.show_output = ' + str(current.show_output) + '\n')
                        file.write(current.tag + '.show_adjustment = ' + str(current.show_adjustment) + '\n')
                        for g in range(len(current.output_obj)):
                                file.write(current.tag + '.output_obj.append(' + current.output_obj[g].tag + ')\n')
                        for obj in current.input:
                                file.write(current.tag + '.input.append(' + obj.tag + ')\n')

                file.close()
                
                return res



	# code added - David

	blockid = 0

	def getNextIdStr(self):
		#global blockid
		
		str = 'block' + `self.blockid`
		self.blockid = self.blockid + 1
		return str


	def Export(self, file, sort = 0):

		#res = 0
                #if sort == 1:
                #        m = modelSorter.ModelSorter(self)
                #        res = m.Sort()                        

		# objToIdMap may not be needed, at least not
		# in this BD editor implementation because
		# obj already have a unique name that could be used
		# instead.
		objToIdMap = {}

		# give an id name to each object
		for obj in self.objList:
			objToIdMap[obj] = self.getNextIdStr()
		
		# generate code to create instances

		file.write('# Experiment Environment Input File\n\n')

		file.write('# code to create instances of classes\n')
		for obj in self.objList:

			# reinitialized in every iteratation to avoid
			# remembering old value and using it in
			# file.write stmts if no 'if stmt' was true.
			objName = 'UnknownClass'


			# note: in this implementation of BD editor Export
			# the class names correspond to the string generated
			# but this need not be the case.
			#
			# ex: in BD, an adder could be represented as
			# a class AdderBlock and it will still work,
			# as long as it generate a 'Adder' string.
			if isinstance(obj, Adder):
				objName = 'Adder'
			if isinstance(obj, Constant):
				objName = 'Constant'
			if isinstance(obj, Negator):
				objName = 'Negator'
			if isinstance(obj, Multiplier):
				objName = 'Multiplier'
			if isinstance(obj, Divider):
				objName = 'Divider'
			if isinstance(obj, Integrator):
				objName = 'Integrator'
			#if isinstance(obj, Output):
			#	objName = 'Output'
			if isinstance(obj, Relay):
				objName = 'Relay'


			file.write(objToIdMap[obj] + ' = ')
			file.write(objName + "('" + obj.tag + "')\n")

		# now add code to link the objects
		file.write('\n# code to link blocks\n')
		for obj in self.objList:
			file.write('\n# input/output of ' + obj.tag + '\n')
			for input in obj.input:
				file.write(objToIdMap[obj] + '.inputs.append(')
				file.write(objToIdMap[input] + ')\n')
			for output in obj.output_obj:
				file.write(objToIdMap[obj] + '.outputs.append(')
				file.write(objToIdMap[output] + ')\n')
			if isinstance(obj, Constant):
				file.write(objToIdMap[obj] + '.value = ' + `obj.value` + '  # set constant value\n')

		# now generate code for to put all the block
		# object into a Model object.
		file.write('\n# code to create a Model and add all blocks\n')
		file.write('model = Model()\n')
		for obj in self.objList:
			file.write('model.blocks.append(' + objToIdMap[obj] + ')\n')

		file.write('\nself.model = model\n')

		file.write('\n\n# end of input file\n')
		file.close()
		
	# end code added
	
	

        def SetTag(self, obj, tag):
                if obj.tag == tag:
                        return 0
                if tag in self.tag2ObjMap.keys():
                        # bestaat al
                        return 1
                
                self.tag2ObjMap[tag] = obj
                del self.tag2ObjMap[obj.tag]
                        
                # tags van grafische objecten hernoemen
                if self.dc != None:
                        self.dc.addtag_withtag(tag, obj.tag)
                        for item in self.dc.find_withtag(obj.tag):
                                self.dc.dtag(item, obj.tag)

                # tag verzetten
                obj.tag = tag
                return 0

        def Coord2obj(self, x, y):
                handle = self.dc.find_overlapping(x - 1, y - 1, x + 1, y + 1)
                for a in range(len(handle)):
                        owner_tag = self.dc.gettags(handle[a])
                        if owner_tag[0] != 'LINK':
                                return self.tag2ObjMap[owner_tag[0]]

class SimObj:
        def __init__(self, x, y):
                self.x = x
                self.y = y
                self.input = []
                self.maxInputs = 2
                self.output_value = 0
                self.output_handle = []
                self.output_handle2 = []
                self.output_obj = []
                self.show_output = 0
                self.show_output_handle1 = -1
                self.show_output_handle2 = -1
                self.show_adjustment = 0

        def Draw(self, dc):
                self.dc = dc
                self.DrawProper()
                while len(self.output_obj) > len(self.output_handle):
                        i = len(self.output_handle)
                        self.output_handle.append(self.dc.create_line(10, 10, 11, 11, tags = "LINK"))
                        self.output_handle2.append(self.dc.create_line(10, 10, 11, 11, tags = "LINK", arrow = "last"))
                        self.dc.lower(self.output_handle[i])
                        self.dc.lower(self.output_handle2[i])
                        channel = self.output_obj[i].input.index(self)
                        coord = self.output_obj[i].GetInputCoord(channel)
                        self.UpdateOutputLinkEnd(self.output_obj[i], coord[0], coord[1])

                self.UpdateOutputLinkStart()
                if self.show_output == 1 and self.show_output_handle1 == -1:
                        self.DrawShowOutput()

        def DrawShowOutput(self):
                coord = self.GetOutputCoord()
                self.show_output_handle1 = self.dc.create_polygon(coord[0], coord[1], coord[0] + 30, coord[1] + 30, coord[0] + 30, coord[1] + 40, coord[0] + 40, coord[1] + 40, coord[0] + 40, coord[1] + 30, coord[0] + 32, coord[1] + 30, tags = self.tag)
                adj_string = str(self.show_adjustment)
                if self.show_adjustment >= 0:
                        adj_string = '+' + adj_string
                self.show_output_handle2 = self.dc.create_text(coord[0] + 25, coord[1] + 40, text = adj_string, tags = self.tag)

        def ToggleShowOutput(self):
                if self.show_output == 1:
                        if self.show_output_handle1 != -1:
                                self.dc.delete(self.show_output_handle1)
                                self.dc.delete(self.show_output_handle2)
                                self.show_output = 0
                else:
                        self.DrawShowOutput()
                        self.show_output = 1

        def SetShowAdjustment(self, value):
                self.show_adjustment = value
                if self.show_output == 1:
                        adj_string = str(self.show_adjustment)
                        if self.show_adjustment >= 0:
                                adj_string = '+' + adj_string
                        self.dc.itemconfigure(self.show_output_handle2, text = adj_string)

        def Move(self, delta_x, delta_y):
                self.dc.move(self.tag, delta_x, delta_y)
                self.x = self.x + delta_x
                self.y = self.y + delta_y
                if len(self.output_obj) != 0:
                        self.UpdateOutputLinkStart()
                for i in range(len(self.input)):
                        coord = self.GetInputCoord(i)
                        self.input[i].UpdateOutputLinkEnd(self, coord[0], coord[1])

        def Destroy(self):
                try:
                        self.dc.delete(self.tag)
                except AttributeError:
                        pass
                self.DeleteAllOutputLink()
                for obj in self.input[:]:
                        obj.DeleteOutputLink(self)

        def GetInputValue(self, channel):
                return self.input[channel].output_value;

        def Process(self):
                pass

        def SerializeString(self):
                return self.tag + ' = ' + self.__class__.__name__ + '(' + str(self.x) + ', ' + str(self.y) + ')'

        def SetInput(self, obj):
                channel = len(self.input)
                self.input.append(obj)
                coord = self.GetInputCoord(channel)
                obj.UpdateOutputLinkEnd(self, coord[0], coord[1])
                return channel

        def DeleteInput(self, one_input):
                while 1:
                        try:
                                self.input.remove(one_input)
                        except ValueError:
                                return
                

        def SetOutputLink(self, another, link_handle):
                if another.maxInputs == len(another.input):
                        return
                self.output_obj.append(another)
                self.output_handle.append(link_handle)
                self.output_handle2.append(self.dc.create_line(10, 10, 11, 11, tags="LINK", arrow = "last"))
                self.dc.lower(self.output_handle[len(self.output_obj)-1])
                self.dc.lower(self.output_handle2[len(self.output_obj)-1])
                channel = another.SetInput(self)
                self.UpdateOutputLinkStart()
                self.UpdateOutputLinkEnd(another, another.GetInputCoord(channel)[0],another.GetInputCoord(channel)[1])
                return channel

        def DeleteAllOutputLink(self):
                for obj in self.output_obj[:]:
                        self.DeleteOutputLink(obj)

        def DeleteOutputLink(self, obj):
                i = 0
                while i < (len(self.output_obj)):
                        if self.output_obj[i] == obj:
                                self.output_obj[i].DeleteInput(self)
                                try:
                                        self.dc.delete(self.output_handle[i])
                                        self.dc.delete(self.output_handle2[i])
                                except AttributeError:
                                        # voor het geval er geen canvas is
                                        pass
                                self.output_obj.pop(i)
                                try:
                                        self.output_handle.pop(i)
                                        self.output_handle2.pop(i)
                                except IndexError:
                                        pass
                        else:
                                i = i + 1

        def UpdateOutputLinkStart(self):
                for i in range(len(self.output_obj)):
                        temp = self.dc.coords(self.output_handle[i])
                        temp[0] = self.GetOutputCoord()[0]
                        temp[1] = self.GetOutputCoord()[1]
                        self.dc.coords(self.output_handle[i], temp[0], temp[1], temp[2], temp[3])
                        self.dc.coords(self.output_handle2[i], temp[0], temp[1], (temp[2] - temp[0]) / 2 + temp[0], (temp[3] - temp[1]) /2 + temp[1])

        def UpdateOutputLinkEnd(self, obj, x, y):
                i = self.output_obj.index(obj)
                temp = self.dc.coords(self.output_handle[i])
                temp[2] = x
                temp[3] = y
                self.dc.coords(self.output_handle[i], temp[0], temp[1], temp[2], temp[3])
                self.dc.coords(self.output_handle2[i], temp[0], temp[1], (temp[2] - temp[0]) / 2 + temp[0], (temp[3] - temp[1]) /2 + temp[1])

        # To be overridden
        def DrawProper(self):
                pass

        def GetOutputCoord(self):
                pass

        def GetInputCoord(self, channel):
                pass

######################################################################        

class TriangleObj(SimObj):

        def __init__(self, x, y):
                SimObj.__init__(self, x, y)

        def GetInputCoord(self, channel):
                if channel==0:
                        return [self.x, self.y + 15]
                else:
                        return [self.x, self.y + 45]

	def GetOutputCoord(self):
                return [self.x + 60, self.y + 30]

class Constant(SimObj):

        def __init__(self, x, y, value=0):
                SimObj.__init__(self, x, y)
                self.maxInputs = 0
                self.output_value = self.value = value

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_oval(x, y, x + 40, y + 40, fill = "lightblue", outline = "blue", tags = self.tag)
                self.text_handle = self.dc.create_text(x + 22, y + 20, text = str(self.value), fill = "red", tags = self.tag)

        def SetValue(self, value):
                self.value = value
                self.dc.itemconfigure(self.text_handle, text = str(self.value))

        def GetOutputCoord(self):
                return [self.x + 20, self.y + 20]

        def GetInputCoord(self, channel):
                return [self.x + 20, self.y + 20]

        def Process(self):
                self.output_value = self.value;

        def SerializeString(self):
                return self.tag + ' = ' + self.__class__.__name__ + '(' + str(self.x) + ', ' + str(self.y) + ', ' + str(self.value) + ')'

class Relay(SimObj):

        def __init__(self, x, y):
                SimObj.__init__(self, x, y)
                self.maxInputs = 1

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_oval(x, y, x + 4, y + 4, fill = "black", outline = "black", tags = self.tag)

        def GetOutputCoord(self):
                return [self.x + 2, self.y + 2]

        def GetInputCoord(self, channel):
                return [self.x + 2, self.y + 2]

        def Process(self):
                if self.input[0] != None:
                        self.output_value = self.GetInputValue(0)

class Integrator(TriangleObj):

        def __init__(self, x, y):
                TriangleObj.__init__(self, x, y)
                self.step_size = 0

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_polygon(x, y, x + 60, y + 30, x, y + 60, fill = "lightblue", outline = "blue", tags = self.tag)
                self.dc.create_text(x + 20, y + 28, text = "I", fill = "purple", tags = self.tag)

        def Process(self):
                if self.input[0] != None:
                        self.output_value = self.GetInputValue(0) * self.step_size + self.output_value

        def GetInitValue(self):
                if self.input[1] != None and self.input[1].__class__.__name__ == 'Constant':
                        self.output_value = self.GetInputValue(1)

class Negator(TriangleObj):

        def __init__(self, x, y):
                TriangleObj.__init__(self, x, y)
                self.maxInputs = 1

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_polygon(x, y, x + 60, y + 30, x, y + 60, fill = "lightblue", outline = "blue", tags = self.tag)
                self.dc.create_text(x + 20, y + 28, text = "-", fill = "purple", tags = self.tag)

        def Process(self):
                if self.input[0] != None:
                        self.output_value = -self.GetInputValue(0)

class Multiplier(TriangleObj):

        def __init__(self, x, y):
                TriangleObj.__init__(self, x, y)

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_polygon(x, y, x + 60, y + 30, x, y + 60, fill = "lightblue", outline = "blue", tags = self.tag)
                self.dc.create_text(x + 20, y + 28, text = "X", fill = "purple", tags = self.tag)

        def Process(self):
                if self.input[0] != None and self.input[1] != None:
                        self.output_value = self.GetInputValue(0) * self.GetInputValue(1)

class Divider(TriangleObj):

        def __init__(self, x, y):
                TriangleObj.__init__(self, x, y)

        def DrawProper(self):
                x = self.x
                y = self.y
                self.dc.create_polygon(x, y, x + 60, y + 30, x, y + 60, fill = "lightblue", outline = "blue", tags = self.tag)
                self.dc.create_text(x + 20, y + 28, text = "/", fill = "purple", tags = self.tag)

        def Process(self):
                if self.input[0] != None and self.input[1] != None:
                        self.output_value = self.GetInputValue(0) / self.GetInputValue(1)

class Adder(TriangleObj):

    def __init__(self, x, y):
        TriangleObj.__init__(self, x, y)

    def DrawProper(self):
        x = self.x
        y = self.y
        self.dc.create_polygon(x, y, x + 60, y + 30, x, y + 60, fill = "lightblue", outline = "blue", tags = self.tag)
        self.dc.create_text(x + 20, y + 28, text = "+", fill = "purple", tags = self.tag)

        def Process(self):
                if self.input[0] != None and self.input[1] != None:
                        self.output_value = self.GetInputValue(0) + self.GetInputValue(1)
