# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
# APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
# HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
# OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
# IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
# ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
# WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
# THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
# GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
# USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
# DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
# PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
# EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGES.

# Copyright (c) 2018 Dr. Oliver Barth <info@drb-electronic.de>
# Manages python task execution.

import sys
import Tkinter as tk
import tkFileDialog
import ttk
import time
import StringIO
from threading import Thread
from drblayout import *
from stdoutRedirector import *
from stderrRedirector import *
from syntaxhighlighter import *
from bdbExt import *
from createToolTip import *
from drbTable import *

#
class TaskEditor(object):
	#
	def __init__(self):
		self.syntaxHighlighter = SyntaxHighlighter()
		self.run = True
		self.actLine = 0
		self.prevLine = 0
		self.debugger = BdbExt()

	#
	def layout(self, frmTask, initTask, tfMessages, taskSyncVars, hwmng):
		self.sync = taskSyncVars
		self.dio = hwmng.getDIO()
		self.anio = hwmng.getADC()
		self.ioexp = hwmng.getIOEXP0()
		self.rtclock = hwmng.getRTC()
		self.rs232 = hwmng.getRS232()
		self.can = hwmng.getCAN()
		self.kin = hwmng.getKin()
		
		self.tfMessages = tfMessages
		self.filename = initTask
		
		self.frmTask = frmTask
		self.frmTask.columnconfigure(0, weight=0)
		self.frmTask.columnconfigure(1, weight=1)
		self.frmTask.columnconfigure(2, weight=0)
		self.frmTask.columnconfigure(3, weight=0)
		self.frmTask.rowconfigure(0, weight=0)
		self.frmTask.rowconfigure(1, weight=1)
		self.frmTask.rowconfigure(2, weight=1)
		self.frmTask.rowconfigure(3, weight=1)
		self.frmTask.rowconfigure(4, weight=1)
		self.frmTask.rowconfigure(5, weight=1)
		self.frmTask.rowconfigure(6, weight=1)
		
		self.labTask = ttk.Label(self.frmTask, text=self.filename, padding=5)
		self.labTask.grid(column=0, row=0, columnspan=4, sticky="ew", padx=0, pady=0)
		
		self.scrbar1 = tk.Scrollbar(self.frmTask, command=self.scrollbar_yview)
		self.scrbar1.grid(column=2, row=1, rowspan=6, sticky='ns')
		
		self.tfTask = tk.Text(self.frmTask, height=40, tabs=('1c'))
		self.tfTask.grid(column=1, row=1, rowspan=6, sticky="nsew")
		self.tfTask.config(undo=True, yscrollcommand=self.tfTaskyview)
		self.tfTask.bind('<Button-3>', self.tfTaskMouseButton3)
		
		frmButtons = ttk.Frame(self.frmTask)
		frmButtons.grid(column=4, row=1, sticky="nsew", padx=DRBLAYOUT_CompPadding, pady=DRBLAYOUT_CompPadding)
		
		# load, save, save as
		self.openimg = tk.PhotoImage(file="./images/buttonOpen.gif")
		self.saveimg = tk.PhotoImage(file="./images/buttonSave.gif")
		self.saveasimg = tk.PhotoImage(file="./images/buttonSaveAs.gif")
		butLoadTask = ttk.Button(frmButtons, width=10, command=self.load,image=self.openimg)
		butLoadTask.grid(column=0, row=0, sticky="ew", padx=5, pady=3)
		CreateToolTip(butLoadTask, 'load')
		butSaveTask = ttk.Button(frmButtons, width=10, command=self.save,image=self.saveimg)
		butSaveTask.grid(column=0, row=1, sticky="ew", padx=5, pady=3)
		CreateToolTip(butSaveTask, 'save')
		butSaveAsTask = ttk.Button(frmButtons, width=10, command=self.saveas, image=self.saveasimg)
		butSaveAsTask.grid(column=0, row=2, sticky="ew", padx=5, pady=3)
		CreateToolTip(butSaveAsTask, 'save as')		
		
		# start, stop
		self.startimg = tk.PhotoImage(file="./images/buttonStart.gif")
		self.stopimg = tk.PhotoImage(file="./images/buttonStop.gif")
		butExecTask = ttk.Button(frmButtons, width=10, command=self.execute, image=self.startimg)
		butExecTask.grid(column=0, row=3, sticky="ew", padx=5, pady=[10, 3])
		CreateToolTip(butExecTask, 'run')
		butStopTask = ttk.Button(frmButtons, width=10, command=self.stop, image=self.stopimg)
		butStopTask.grid(column=0, row=4, sticky="ew", padx=5, pady=3)
		CreateToolTip(butStopTask, 'stop')
		
		# cut, copy, paste
		self.cutimg = tk.PhotoImage(file="./images/buttonCut.gif")
		self.copyimg = tk.PhotoImage(file="./images/buttonCopy.gif")
		self.pasteimg = tk.PhotoImage(file="./images/buttonPaste.gif")
		butCutTask = ttk.Button(frmButtons, width=10, command=lambda: self.tfTask.event_generate('<<Cut>>'), image=self.cutimg)
		butCutTask.grid(column=0, row=5, sticky="ew", padx=5, pady=[10, 3])
		CreateToolTip(butCutTask, 'cut')
		butCopyTask = ttk.Button(frmButtons, width=10, command=lambda: self.tfTask.event_generate('<<Copy>>'), image=self.copyimg)
		butCopyTask.grid(column=0, row=6, sticky="ew", padx=5, pady=3)
		CreateToolTip(butCopyTask, 'copy')
		butPasteTask = ttk.Button(frmButtons, width=10, command=lambda: self.tfTask.event_generate('<<Paste>>'), image=self.pasteimg)
		butPasteTask.grid(column=0, row=7, sticky="ew", padx=5, pady=3)
		CreateToolTip(butPasteTask, 'paste')
		
		# undo, redo
		self.undoimg = tk.PhotoImage(file="./images/buttonUndo.gif")
		self.redoimg = tk.PhotoImage(file="./images/buttonRedo.gif")
		butUndoTask = ttk.Button(frmButtons, width=10, command=self.tfTask.edit_undo, image=self.undoimg)
		butUndoTask.grid(column=0, row=8, sticky="ew", padx=5, pady=[10, 3])
		CreateToolTip(butUndoTask, 'undo')
		butRedoTask = ttk.Button(frmButtons, width=10, command=self.tfTask.edit_redo, image=self.redoimg)
		butRedoTask.grid(column=0, row=9, sticky="ew", padx=5, pady=3)
		CreateToolTip(butRedoTask, 'redo')
		
		#debug
		butDebugStart = ttk.Button(frmButtons, text='debug', width=10, command=self.debugStart)
		butDebugStart.grid(column=0, row=10, sticky="ew", padx=5, pady=[10, 3])

		self.tfLineNo = tk.Text(self.frmTask, width=5)
		self.tfLineNo.grid(column=0, row=1, rowspan=6, sticky="ns")
		self.tfLineNo.config(bg='grey')
		self.tfLineNo.insert("end", "1")

		# tags
		self.tfTask.tag_configure("tagActLine", background=DRBLAYOUT_actLine)
		self.tfTask.tag_configure("tagPythonKW", foreground=DRBLAYOUT_pythonKW)
		self.tfTask.tag_configure("tagAppKW", foreground=DRBLAYOUT_appKW)
		self.tfTask.tag_configure('tagBreakpoint', background=DRBLAYOUT_breakpoint)
		self.tfTask.tag_configure('tagTab1', background=DRBLAYOUT_tab1)
		self.tfTask.tag_configure('tagTab2', background=DRBLAYOUT_tab2)
		self.tfTask.tag_configure('tagComment', foreground=DRBLAYOUT_comment)
		self.tfTask.bind("<KeyRelease>", self.keyUp)
		
		# if autorun.py exists load and execute
		if self.filename != '':
			f = open(self.filename, 'r')
			txt = f.read()
			self.tfTask.insert("1.0", txt)
			f.close()
			self.syntaxHighlightAll()
			
	#
	def getMessageArea(self):
		return self.tfMessages

	#
	def load(self):
		txt = ""
		fileTask = tkFileDialog.askopenfile(initialdir="./tasks", defaultextension=".py")
		if(fileTask == None):
			return
		self.filename = fileTask.name
		self.labTask.config(text=self.filename)
		txt = fileTask.read()
		self.tfTask.delete("1.0", tk.END)
		self.tfTask.insert("1.0", txt)
		fileTask.close()
		self.syntaxHighlightAll();
		self.updateLineNo()

	#
	def save(self):
		f = open(self.filename, 'w')
		txt = self.tfTask.get("1.0", tk.END)
		f.write(txt)
		f.close()
		
	#
	def saveas(self):
		fileTask = tkFileDialog.asksaveasfile(mode='w', defaultextension=".py")
		if(fileTask == None):
			return
		txt = self.tfTask.get("1.0", tk.END)
		fileTask.write(txt)
		self.filename = fileTask.name
		self.labTask.config(text=self.filename)
		fileTask.close()
	
	#
	def execute(self):
		self.run = True
		thrd = Thread(target=self.runTask)
		thrd.start()
		
	#
	def runTask(self):
		self.clear()
		prog = self.tfTask.get("1.0", tk.END)
		code = compile(prog, self.filename, 'exec')
		#exec code
		self.debugger.run(code, globals(), locals())
		print "task finished"
		
	#
	def stop(self):
		self.clearTag()
		self.run = False
		self.debugger.dbg_cont()
		self.debugger.set_quit()
	
	# syntax highlights the row of the current cursor position
	def syntaxHighlightRow(self):
		self.syntaxHighlighter.highlightRow(self.tfTask)

	# syntax highlights the whole text
	def syntaxHighlightAll(self):
		self.syntaxHighlighter.highlightAll(self.tfTask)

	#
	def updateLineNo(self, start):
		start = start + 1
		end = start + 40
		for i in range(start, end):
			if i == end - 1:
				self.tfLineNo.insert("end", str(i))
			else:
				self.tfLineNo.insert("end", str(i) + "\n")

	# must be called cyclically
	def updateActLine(self):
		self.actLine = self.debugger.getLineno()
		if((self.actLine != self.prevLine) is True):
			self.tfTask.tag_remove("tagActLine", "1.0", "end")
			self.tfTask.tag_add( "tagActLine", "%d.0" % (self.actLine), "%d.end" % (self.actLine) )
			self.prevLine = self.actLine

	#
	def clearTag(self):
		self.tfTask.tag_remove("tagActLine", "1.0", "end")

	#
	def clear(self):
		self.tfMessages.delete("1.0", "end")

	#
	def write(self, msg):
		self.tfMessages.insert(tk.END, msg)
		
	# if return or control key (cut, insert) hit syntax highlight all text
	# else only highlight the current row
	def keyUp(self, event):
		if (event.keysym == "Return") or (event.keysym == "Control_L") or (event.keysym == "Control_R"):
			self.syntaxHighlightAll()
		else:
			self.syntaxHighlightRow()
		self.updateLineNo()
		
	# add a method name at the current cursor position selected from a combobox
	def tfTaskMouseButton3(self, event):
		self.dialog = tk.Toplevel(self.frmTask)
		self.dialog.transient(self.frmTask)
		self.dialog.title('add method')
		self.comboMethods = ttk.Combobox(self.dialog, values=self.syntaxHighlighter.getAppKeywords(), width=60)
		self.comboMethods.grid(row=0, column=0, sticky="ns", padx=DRBLAYOUT_CompPadding, pady=DRBLAYOUT_CompPadding)
		self.comboMethods.current(0)
		buttonOK = tk.Button(self.dialog, text='OK', command=self.insertMethod)
		buttonOK.grid(row=1, column=0)
		buttonCancel = tk.Button(self.dialog, text='Cancel', command=self.cancelMethod)
		buttonOK.grid(row=1, column=0)
		buttonCancel.grid(row=1, column=1)
		self.dialog.focus_set()
		#self.dialog.grab_set()
		self.dialog.wait_window()
		return
	
	#
	def insertMethod(self):
		sel = self.comboMethods.get()
		self.tfTask.insert("insert", sel)
		self.dialog.destroy()
		
	#
	def cancelMethod(self):
		self.dialog.destroy()
	
	# updates the line numbers in the line number text field
	def updateLineNo(self):
		idx = self.tfTask.index("end - 1c")
		row = int(idx.split('.')[0])
		idxcmp = self.tfLineNo.index("end - 1c")
		rowcmp = int(idxcmp.split('.')[0])
		# add line numbers
		if row > rowcmp:
			while row > rowcmp:
				rowcmp = rowcmp + 1
				self.tfLineNo.insert("end", '\n' + str(rowcmp))
		# delete line numbers
		elif row < rowcmp:
			while row < rowcmp:
				self.tfLineNo.delete("%d.0" % rowcmp, "end")
				rowcmp = rowcmp - 1

	#
	def tfTaskyview(self, *args):
		if self.tfTask.yview() != self.tfLineNo.yview():
			self.tfLineNo.yview_moveto(args[0])
		self.scrbar1.set(*args)

	#
	def scrollbar_yview(self, *args):
		self.tfTask.yview(*args)
		self.tfLineNo.yview(*args)

	#
	def debugStart(self):
		self.top = tk.Toplevel()
		self.top.title("debug");
		self.debugTableFrame = ttk.Frame(self.top)
		self.debugTableFrame.grid(column=0, row=0, columnspan=5, rowspan=6, sticky="nsew", padx=3, pady=3)
		colWidth = [20, 80]
		colHeader = [ 'variable', 'value' ]
		self.debugTable = DrbTable(self.debugTableFrame, "param", 1, 2, colWidth, colHeader)
		for i in range (0, 10):
			self.debugTable.addRow(['', ''])
		butDebugBreak = ttk.Button(self.top, text='debug break', width=10, command=self.debugBreak)
		butDebugBreak.grid(column=6, row=0, sticky="ew", padx=5, pady=[10, 3])
		butDebugStep = ttk.Button(self.top, text='debug step', width=10, command=self.debugStep)
		butDebugStep.grid(column=6, row=1, sticky="ew", padx=5, pady=3)
		butDebugCont = ttk.Button(self.top, text='debug cont', width=10, command=self.debugCont)
		butDebugCont.grid(column=6, row=2, sticky="ew", padx=5, pady=3)
		butDebugSetBreakpoint = ttk.Button(self.top, text='set brkpnt', width=10, command=self.debugSetBreakpoint)
		butDebugSetBreakpoint.grid(column=6, row=3, sticky="ew", padx=5, pady=[10, 3])
		butDebugClearBreakpoint = ttk.Button(self.top, text='clr brkpnt', width=10, command=self.debugClearBreakpoint)
		butDebugClearBreakpoint.grid(column=6, row=4, sticky="ew", padx=5, pady=3)
		butDebugPrint = ttk.Button(self.top, text='debug print', width=10, command=self.debugPrint)
		butDebugPrint.grid(column=6, row=5, sticky="ew", padx=5, pady=[10, 3])

	#
	def debugBreak(self):
		self.debugger.dbg_break()

	#
	def debugStep(self):
		self.debugger.dbg_step()

	#
	def debugCont(self):
		self.debugger.dbg_cont()

	#
	def debugPrint(self):
		idx = self.debugTable.getSelRowIdx()
		row = self.debugTable.getSelRow()
		val = self.debugger.dbg_print(row[0])
		self.debugTable.setCell(idx, 1, val)

	#
	def debugSetBreakpoint(self):
		line = int(self.tfTask.index("insert").split('.')[0])
		self.tfTask.tag_add("tagBreakpoint", "%d.0" % line, "%d.end - 1c" % line)
		self.debugger.set_break(self.filename, line)
		pass

	#
	def debugClearBreakpoint(self):
		line = int(self.tfTask.index("insert").split('.')[0])
		self.tfTask.tag_remove( "tagBreakpoint", "%d.0" % line, "%d.end - 1c" % line )
		self.debugger.clear_break(self.filename, line)

