#Created by dan december 2011.  www.danomagnum.com
from ttk import Label, Button#, Checkbutton, Style
from Tkinter import StringVar, LEFT, RIGHT, BOTH, Frame, X, RAISED, IntVar, Menu, Checkbutton, Y
import tkMessageBox
import tkSimpleDialog

TRUECOLOR = "green"
FALSECOLOR = "red"
LATCHCOLOR = "blue"
DEFAULTCOLOR = "black"
fix_scroll = lambda x:x
remove_row = lambda x:x

class func_and():
	#true if all parameters are true.  This would be the normal input block
	def __init__(self):
		pass
	def __name__(self):
		return "and"
	def on(self,*args):
		if all(args):
			return True
		else:
			return False
	def off(self,*args):
		pass
class func_or():
	#true if any parameters are true
	def __init__(self):
		pass
	def __name__(self):
		return "or"
	def on(self,*args):
		if any(args):
			return True
		else:
			return False
	def off(self,*args):
		pass

class func_not():
	#returns the opposite of the first term, ignoring the rest
	def __init__(self):
		pass
	def __name__(self):
		return "not"
	def on(self,*args):
		if args:
			return not args[0]
		else:
			return False
	def off(self,*args):
		pass

class func_add():
	#adds parameter 1 to parameter 2 and stores the value in parameter 3
	def __init__(self):
		pass
	def __name__(self):
		return "+"
	def on(self,*args):
		if args:
			#print '>', int(args[0]), '<'
			val = int(args[0])
			val += int(args[1])
			args[2].set(val)
			#print '>', int(args[2]), '<'
	def off(self,*args):
		pass

class func_gt():
	#true if parameter 1 is > parameter 2
	def __init__(self):
		pass
	def __name__(self):
		return ">"
	def on(self,*args):
		if len(args)>1:
			if args[0] > args[1]:
				return True
			else:
				return False
		else:
			return False
	def off(self,*args):
		pass

class func_set():
	#sets parameter 1 to parameter 2
	def __init__(self):
		pass
	def __name__(self):
		return "set"
	def on(self,*args):
		if args:
			args[0].set(args[1])
			return args[0]
	def off(self,*args):
		pass

class func_close():
	#this is the closest thing to a normal output where powered is on and unpowered is off.
	def __init__(self):
		pass
	def __name__(self):
		return "close"
	def on(self,*args):
		if args:
			args[0].set(True)
	def off(self,*args):
		if args:
			args[0].set(False)


functionlist = [func_close, func_set, func_gt, func_add, func_not, func_or, func_and]
itemlist = [] #need to fill this in to add stuff I guess


class value_binary():
	def __init__(self,value,name='Unnamed',input=False,description=""):
		self.latched = False
		self.input = input
		self.set(value)
		self.name=name
		self.displayvariable = StringVar()
		self.displayvalue = IntVar()
		self.display = []
		self.description = description

	def pickleprepare(self):
		self.displayvariable = None
		self.displayvalue = None
		self.display = []
		self.popup = None

	def postpickle(self):
		self.displayvariable = StringVar()
		self.displayvalue = IntVar()

	def delifparent(self,parent):
		displays = self.display[:]
		for d in displays:
			if d.master == parent:
				#I am a child of you, so I wll delete myself
				self.display.remove(d)
				d.destroy()
		if not self.display:
			del(self)

	def reset (self):
		if not self.input:
			self.set(False)
		else:
			pass #re-read input data

	def set(self,value):
		if self.latched:
			return False
		if value:
			self.value = True
		else:
			self.value = False
		return True

	def latchon(self):
		self.latched = True
		self.value = True
		self.display_update()

	def latchoff(self):
		self.latched = True
		self.value = False
		self.display_update()


	def unlatch(self):
		self.latched = False
		self.display_update()


	def status(self):
		return self.value

	def __nonzero__(self):#evaluates the decision block
		return self.value

	def __repr__(self):
		return str(self.value) + ' ' + self.name

	def __eq__(self,n):
		return self.value == n
	def __lt__(self,n):
		return self.value < n
	def __gt__(self,n):
		return self.value > n
	def __ge__(self,n):
		return self.value >= n
	def __le__(self,n):
		return self.value <= n
	def __trunc__(self):
		return self.value

	def __int__(self):
		return int(self.value)
	def toggle(self):
		if self.latched == False:
			self.value = not self.value
	def toggle_and_display(self):
		self.toggle()
		self.display_update()

	def display_new(self,parent):
		self.display_update()
		b = Checkbutton(parent,textvariable=self.displayvariable,command=self.toggle_and_display,variable=self.displayvalue,indicatoron=0)
		b.pack(side=LEFT)
		self.display.append(b)

		self.popup = Menu(parent,tearoff=0)
		self.popup.add_command(label="force on",command=self.latchon)
		self.popup.add_command(label="force off",command=self.latchoff)
		self.popup.add_command(label="remove force",command=self.unlatch)
		self.popup.add_separator()
		self.popup.add_command(label="properties",command=self.properties)
		self.popup.add_command(label="description",command=self.setdesc)
		b.bind("<Button-3>",self.popup_menu)

		return b


	def popup_menu(self,event):
		try: 
			self.popup.tk_popup(event.x_root,event.y_root,0)
		finally:
			self.popup.grab_release()


	def properties(self):
		ds = self.name + "\n" + str(self.value) + "\n"
		ds += "Force: "
		if self.latched:
			ds += "on"
		else:
			ds += "off"
		ds += "\nDescription:" + self.description
		tkMessageBox.showinfo(self.name,ds)


	def display_update(self):
		if self.display:
			for d in self.display:
				if self.latched:
					d.configure(foreground=LATCHCOLOR)
				else:
					d.configure(foreground=DEFAULTCOLOR)
		ds = self.name + "\n" + str(self.value)
		if self.description:
			ds += "\n" + self.description
		if self.displayvariable:
			self.displayvariable.set(ds)
			self.displayvalue.set(int(self.value))

	
	def setdesc(self):
		result = tkSimpleDialog.askstring(self.name,self.name,initialvalue=self.description)
		if result is not None:
			self.description = result
		self.display_update()
	


def bound(mn,var,mx):
	return max(mn,min(mx,var))

class value_word(): #0-1023
	description = None
	def __init__(self,value,name='Unnamed',input=False,description=""):
		self.values = []
		self.input = input
		self.name=name
		self.set(value)
		#self.display = False
		self.display = []
		self.displayvariable = StringVar()
		self.display_update()
		self.description = description

	def pickleprepare(self):
		self.display = []
		self.displayvariable = None
		self.popup = None
		for v in self.values:
			v.pickleprepare()
	def postpickle(self):
		self.displayvariable = StringVar()

	def delifparent(self,parent):
		displays = self.display[:]
		for d in displays:
			if d.master == parent:
				#I am a child of you, so I wll delete myself
				self.display.remove(d)
				d.destroy()
		if not self.display:
			del(self)


	def set(self,value):
		value = bound(0,value,1023)
		binstring = bin(value)[2:] #string starts with 0bxxxx
		if self.values:
			i = 0
			for digit in binstring[::-1]:
				self.values[i].set(int(digit))
				i += 1
			while i < 10:
				self.values[i].set(False)
				i += 1
			self.value = value
			#return True
		else:
			self.value = value
			self.values = []
			i = 0
			for digit in binstring[::-1]:
				self.values.append(value_binary(int(digit),self.name + '/' + str(i)))
				i += 1
			while i < 10:
				self.values.append(value_binary(False,self.name + '/' + str(i)))
				i += 1

	def reset (self):
		if not self.input:
			map(lambda x:x.reset(),self.values)
			self.set(0)
		else:
			pass #re-read input data

	def status(self):
		return self.value

	def bits(self):
		return self.values

	def __nonzero__(self):#evaluates the decision block
		return self.value

	def __repr__(self):
		return str(self.value) + ' ' + self.name

	def __eq__(self,n):
		return self.value == int(n)
	def __lt__(self,n):
		return self.value < int(n)
	def __gt__(self,n):
		return self.value > int(n)
	def __ge__(self,n):
		return self.value >= int(n)
	def __le__(self,n):
		return self.value <= int(n)
	def __trunc__(self):
		return self.value

	def __int__(self):
		return self.value

	def __add__(self,n):
		return self.value + int(n)
	def __sub__(self,n):
		return self.value - int(n)
	def __iadd__(self,n):
		self.value = self.value + int(n)
		return self
	def __isub__(self,n):
		self.value = self.value - int(n)
		return self
	def __int__(self):
		return int(self.value)

	def bits_display_new(self,parent):
		#labs = []
		frm = Frame(parent,borderwidth=1)
		for bit in self.values:
			#labs.append(bit.display_new(frm))
			bit.display_new(frm)
		frm.pack(side=LEFT)
		#return labs
	def bits_display_update(self):
		for bit in self.values:
			bit.display_update()

	def display_new(self,parent):
		self.display_update()
		l = Label(parent,textvariable=self.displayvariable,borderwidth=2,relief=RAISED)
		l.pack(side=LEFT)
		self.display.append(l)

		self.popup = Menu(parent,tearoff=0)
		self.popup.add_command(label="Value",command=self.prompt_for_value)
		self.popup.add_command(label="Description",command=self.setdesc)
		l.bind("<Button-3>",self.popup_menu)


		return l

	def popup_menu(self,event):
		try: 
			self.popup.tk_popup(event.x_root,event.y_root,0)
		finally:
			self.popup.grab_release()
	def display_update(self):
		ds = self.name + '\n' + str(self.value)
		if self.description:
			ds  += '\n' + self.description
		self.displayvariable.set(ds)
	
	def prompt_for_value(self):
		result = tkSimpleDialog.askinteger(self.name,self.name,initialvalue=self.value,minvalue=0,maxvalue=1023)
		if result is not None:
			self.set(result)
		self.display_update()
		
	def setdesc(self):
		result = tkSimpleDialog.askstring(self.name,self.name,initialvalue=self.description)
		if result is not None:
			self.description = result
		self.display_update()
	

def create_binary_array(length,name):
	array = []
	for i in xrange(length):
		assignedname = str(name) + str(i)
		array.append(value_binary(False, assignedname))
	return array

def display_binary_array(array,parent):
	#labs = []
	for bit in array:
		#labs.append(bit.display_new(parent))
		bit.display_new(parent)
	#return labs

class block():
	def __init__(self,function = func_close,parent=None,output=False,rung=None):
		self.function = function()
		self.arguments = []
		self.display = None
		self.frm2 = None
		self.rung=rung
		self.popupparent = None
		if parent:
			self.display_new(parent)
		if output:
			self.display_update_output()
		else:
			self.display_update()

	def pickleprepare(self):
		for a in self.arguments:
			a.pickleprepare()
		self.display = None
		self.frm2 = None
		self.frm = None
		self.popup = None
		self.removeargsmenu = None
		self.popupparent = None
	def postpickle(self):
		for a in self.arguments:
			a.postpickle()

	def delifparent(self,parent):
		if self.frm.master == parent:
			#I am a child of you, so I wll delete myself
			if self.frm2:
				self.frm2.destroy()
			for a in self.arguments:
				a.delifparent(self.frm)

	def status(self):
		return self.function.on(*self.arguments)

	def run(self,on=True):
		if on:
			self.function.on(*self.arguments)
		else:
			self.function.off(*self.arguments)
		
	def __nonzero__(self):#evaluates the decision block
		return self.status()

	def display_new(self,parent):
		#labs = []
		frm = Frame(parent,border=2,relief=RAISED,pady=5)
		frm2 = Frame(frm,border=1,relief=RAISED,)
		frm2.pack(fill=X)
		l = Label(frm2,text=self.function.__name__())
		l.pack()

		for bit in self.arguments:
			#labs.append(bit.display_new(frm))
			bit.display_new(frm)
		frm.pack(side=LEFT)
		self.display = frm
		#return labs

		self.popupparent = parent
		self.popup = Menu(self.popupparent,tearoff=0,postcommand=self.updatemenu)
		addmenu = Menu(self.popup,tearoff=0)
		for item in itemlist:
			addmenu.add_command(label=item.name,command=self.additem(item))
		self.popup.add_cascade(label="add argument",menu=addmenu)
		self.popup.add_command(label="add output")
		self.popup.add_separator()
		self.popup.add_command(label="remove block",command=self.delblock)
		self.popup.add_separator()

		self.updatemenu()
		self.frm = frm
		frm.bind("<Button-3>",self.popup_menu)
		fix_scroll()

	def updatemenu(self):
		
		self.removeargsmenu = Menu(self.popup,tearoff=0)
		for item in self.arguments:
			self.removeargsmenu.add_command(label=item.name,command=self.removearg(item))
		self.popup.delete(5)
		self.popup.add_cascade(label="remove argument",menu=self.removeargsmenu,)

	def removearg(self,item):
		return lambda:self.removearg2(item)
	def removearg2(self,item):
		item.delifparent(self.frm)
		self.arguments.remove(item)

	def additem(self,item):
		return lambda:self.additem2(item)

	def additem2(self,item):
		self.arguments.append(item)
		self.removeargsmenu.add_command(label=item.name,command=self.removearg(item))
		item.display_new(self.frm)
		fix_scroll()
	def delblock(self):
		self.rung.removeblock(self)
		for a in self.arguments:
			a.delifparent(self.frm)
		if self.frm2:
			self.frm2.destroy()
		self.frm.destroy()


	def popup_menu(self,event):
		try: 
			self.popup.tk_popup(event.x_root,event.y_root,0)
		finally:
			self.popup.grab_release()


	def display_update(self):
		if self.display:
			if self.status():
				self.display.configure(bg=TRUECOLOR)
			else:
				self.display.configure(bg=FALSECOLOR)
		for bit in self.arguments:
				bit.display_update()

	def display_update_output(self):
		for bit in self.arguments:
			bit.display_update()


class rung():
	def __init__(self):
		self.inputs = []
		self.frminput = None
		self.frmoutput = None
		self.outputs = []
	def __del__(self):
		#print "deleting row"
		for i in self.inputs:
			i.delifparent(self.frminput)
		for o in self.outputs:
			o.delifparent(self.frmoutput)

		self.frminput.destroy()
		self.frmoutput.destroy()
		self.frm.destroy()
	def run(self):
		conds = map(lambda x:x.status(),self.inputs)
		#print conds
		if all(conds) or not self.inputs:
			if self.frminput:
				self.frminput.configure(bg=TRUECOLOR)
			#print "running"
			map(lambda x:x.run(),self.outputs)
		else:
			if self.frminput:
				self.frminput.configure(bg=FALSECOLOR)
			map(lambda x:x.run(on=False),self.outputs)
			
	def pickleprepare(self):

		for i in self.inputs:
			i.pickleprepare()
		for o in self.outputs:
			o.pickleprepare()

		self.frm.destroy()
		self.frminput = None
		self.frmoutput = None
		self.display = None
		self.popup = None
		self.inputmenu = None
		self.outputmenu = None
		self.frm = None

	def postpickle(self):
		for i in self.inputs:
			i.postpickle()
		for o in self.outputs:
			i.postpickle()

	def removeblock(self,child):
		if child in self.inputs:
			self.inputs.remove(child)
		elif child in self.outputs:
			self.outputs.remove(child)

	def display_new(self,parent):
		#labs = []
		frm = Frame(parent,border=2,relief=RAISED)
		frmminheight = Frame(frm,height=20,width=1)
		frmminheight.pack(side=LEFT)
		self.frminput = Frame(frm,border=1,relief=RAISED)
		self.frmoutput = Frame(frm,border=1,relief=RAISED)
		self.frminput.pack(side=LEFT,fill=Y)
		self.frmoutput.pack(side=RIGHT,fill=Y)

		for inputblock in self.inputs:
			inputblock.display_new(self.frminput)
		for outputblock in self.outputs:
			outputblock.display_new(self.frmoutput)
		frm.pack(fill=X,expand=1)
		self.display = frm

		self.popup = Menu(parent,tearoff=0)
		self.inputmenu = Menu(self.popup,tearoff=0)
		self.outputmenu = Menu(self.popup,tearoff=0)
		for fnct in functionlist:
			self.inputmenu.add_command(label=fnct.__name__,command=self.addinput(fnct))
			self.outputmenu.add_command(label=fnct.__name__,command=self.addoutput(fnct))
		self.popup.add_cascade(label="add input",menu=self.inputmenu)
		self.popup.add_cascade(label="add output",menu=self.outputmenu)
		self.popup.add_separator()
		self.popup.add_command(label="remove rung",command=self.removerowclick)
		self.popup.add_command(label="new rung")
		self.popup.add_command(label="test",command=self.test)
		frm.bind("<Button-3>",self.popup_menu)
		self.frminput.bind("<Button-3>",self.popup_menu_input)
		fix_scroll()
		self.frm = frm

	def test(self):
		for i in self. inputs:
			i.test(self.frminput)

	def removerowclick(self):
		remove_row(self)

	def addinput(self,fnct):
		return lambda:self.inputs.append(block(fnct,parent=self.frminput,rung=self))

	def addoutput(self,fnct):
		return lambda:self.outputs.append(block(fnct,parent=self.frmoutput,output=True))

	def popup_menu(self,event):
		try: 
			self.popup.tk_popup(event.x_root,event.y_root,0)
		finally:
			self.popup.grab_release()
	def popup_menu_input(self,event):
		try: 
			self.inputmenu.tk_popup(event.x_root,event.y_root,0)
		finally:
			self.popup.grab_release()

	def display_update(self):
		for inputblock in self.inputs:
			inputblock.display_update()
		for outputblock in self.outputs:
			outputblock.display_update_output()



def closeblock(bit):
	b = block()
	b.arguments.append(bit)
	return b


if __name__ == '__main__':
	I1 = create_binary_array(10,'I1:0/')

	b1 = block()
	b1.function = func_and
	b1.arguments.append(I1[0])
	b1.arguments.append(I1[1])
	I1[0].set(True)
	I1[1].set(True)

	#print b1.status(), b1.arguments

	a = value_word(0)
	#print a

	out1 = block()
	out1.function = func_add
	out1.arguments.append(a)
	out1.arguments.append(1)

	out1.run()
	#print a, 'test'

	r = rung()
	r.inputs += [b1]
	r.outputs += [out1]

	r.run()
	#print a

	I1[0].set(False)
	#print b1.status()
	r.run()
	#print a




