#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, BooleanVar
import tkMessageBox
import tkSimpleDialog
import block_functions

TRUECOLOR = "green"
FALSECOLOR = "red"
LATCHCOLOR = "blue"
DEFAULTCOLOR = "black"
fix_scroll = lambda x:x
remove_row = lambda x:x


functionlist = block_functions.BLOCK_FUNCTION_LIST
itemlist = [] #need to fill this in to add stuff I guess

class IO():
	def __init__(self,value,name,typedesc,input=False,description=""):
		self.latched = False
		self.input = input
		self.set(value)
		self.name=name
		self.displayvariable = StringVar()

		self.typedesc = typedesc
		if self.typedesc == 'bool':
			self.displayvalue = BooleanVar()
		elif self.typedesc == 'int':
			self.displayvalue = IntVar()
		else:
			self.displayvalue = StringVar()
		self.display = []
		self.description = description


	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=0):
		if self.latched:
			return False
		self.value = value
		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
		if self.value:
			return True
		else:
			return False

	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):
		try:
			i = int(self.value)
		except:
			i = 0
		return i
	def toggle(self):
		if self.latched == False:
			self.value = not self.value
	def clicked(self):
		if self.typedesc == 'bool':
			self.toggle_and_display()
	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.clicked,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)

			dv = None
			if self.typedesc == 'bool':
				dv = bool(self.value)
			elif self.typedesc == 'int':
				dv = int(self.value)
			else:
				dv = str(self.value)
			self.displayvalue.set(dv)

	
	def setdesc(self):
		result = tkSimpleDialog.askstring(self.name,self.name,initialvalue=self.description)
		if result is not None:
			self.description = result
		self.display_update()
	


class value_binary(IO):
	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 = BooleanVar()
		self.display = []
		self.description = description
		self.typedesc = 'bool'



def bound(mn,var,mx):
	return max(mn,min(mx,var))

class value_word(IO): #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 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 status(self):
		return self.value

	def bits(self):
		return self.values


	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 = block_functions.DEFAULT,parent=None,output=False,rung=None):
		self.function = function()
		self.name = self.function.name
		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 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 removeblock(self,child):
		if child in self.arguments:
			self.arguments.remove(child)

	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 testing(self):
		print self.arguments
		print self.status()
		print self.function.on
	def updatemenu(self):
		self.popup.delete(5)
		self.popup.delete(4)
		self.popup.delete(3)
		self.popup.delete(2)
		self.popup.delete(1)
		self.popup.delete(1)
		self.popup.delete(1)
		
		#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))
		for fnct in functionlist:
			addmenu.add_command(label=fnct.__name__,command=self.addsubblock(fnct))
		self.popup.add_cascade(label="add argument",menu=addmenu)
		self.popup.add_command(label="add output")
		self.popup.add_command(label="test",command=self.testing)
		self.popup.add_separator()
		self.popup.add_command(label="remove block",command=self.delblock)
		self.popup.add_separator()

		#self.updatemenu()


		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,)
	
		#addmenu = Menu(self.popup,tearoff=0)
		#for item in itemlist:
			#addmenu.add_command(label=item.name,command=self.additem(item))
#
		#self.popup.delete(1)
		#self.popup.add_cascade(0,label="add argument",menu=addmenu)


	def addsubblock(self,fnct):
		return lambda:self.arguments.append(block(fnct,parent=self.frm,rung=self))
	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):
		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)
		if all(conds) or not self.inputs:
			if self.frminput:
				self.frminput.configure(bg=TRUECOLOR)
			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 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)


	a = value_word(0)

	out1 = block()
	out1.function = func_add
	out1.arguments.append(a)
	out1.arguments.append(1)

	out1.run()

	r = rung()
	r.inputs += [b1]
	r.outputs += [out1]

	r.run()

	I1[0].set(False)
	r.run()




