# -*-coding:Utf-8 -*

import sixpad as sp
import os
import re
import ctypes
import ctypes.wintypes
import subprocess

pythonFunPattern = r'^( |\t)*(?P<type>((def)|(class))) +(?P<name>\w+)'
pythonFunRegex = re.compile(pythonFunPattern, re.MULTILINE)
matlabFunPattern = r'^\s*(?P<type>(function)|(classdef))\s+(\w+\s*=\s*)?(?P<name>\w+)'
matlabFunRegex = re.compile(matlabFunPattern, re.IGNORECASE | re.MULTILINE)

#Fichier de sortie temporaire
sFicOutput = os.path.join(sp.appdir, 'output.tmp')
try:
	os.remove(sFicOutput)
except OSError:
	pass

def comment(bComment):
	"""Commente (bComment=True) ou décommente (bComment = False) le texte sélectionné.
	"""
	c = getCommentChar()
	if not c:
		sp.say(u'Type de fichier inconnu.')
		return
	saved = sp.window.curPage.rangesInLines
	
	try:
		sp.window.curPage.rangesInLines = True
		deb = sp.window.curPage.selectionStart
		deb = sp.window.curPage.lineOfOffset(deb)
		fin = sp.window.curPage.selectionEnd
		fin = sp.window.curPage.lineOfOffset(fin)
		sp.say(str(deb))
		sp.say(str(fin))
		for n in range(deb, fin+1):
			if bComment:
				sp.window.curPage[n] = c + sp.window.curPage[n]
			else:
				if sp.window.curPage[n].startswith(c):
					sp.window.curPage[n] = sp.window.curPage[n][1:]
	except:
		raise
	finally:
		sp.window.curPage.rangesInLines = saved
	
def comment_old():
	commentAction(True)
	raise

def uncomment():
	commentAction(False)
	raise

def getCommentChar():
	fileName = sp.window.curPage.file
	dummy, ext = os.path.splitext(fileName)
	if ext.lower() == '.m':
		c = '%'
	elif ext.lower() == '.py':
		c = '#'
	else: #Unknown file type
		c = ''
	return c

def goToNextFunction():
	curPage = sp.window.curPage
	lst = listFunctionsAndClasses()
	lst = [(l,n) for l,n in lst if l > curPage.curLine]
	if not lst:
		sp.window.messageBeep(0)
		return
	targetLine, name = lst[0]
	curPage.position = curPage.lineSafeStartOffset(targetLine)
	sp.say(name)


def goToPreviousFunction():
	curPage = sp.window.curPage
	lst = listFunctionsAndClasses()
	lst = [(l,n) for l,n in lst if l < curPage.curLine]
	if not lst:
		sp.window.messageBeep(0)
		return
	targetLine, name = lst[-1]
	curPage.position = curPage.lineSafeStartOffset(targetLine)
	sp.say(name)


def runFile():
	with open(sFicOutput, 'w') as fic:
		pass
	filename = sp.window.curPage.file
	if filename == '':
		#BUGSIXPAD: save() ne sauvegarde pas fichier
		sp.window.messageBeep(0)
		return
	sp.window.curPage.save()
	cmdLine = ' '.join(['python', '"' + filename + '"'])
	si = subprocess.STARTUPINFO()
	si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
	proc = subprocess.Popen(cmdLine , stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=si)
	sOutput, err = proc.communicate()
	sOutput = sOutput.decode()
	#proc = os.popen(cmdLine)
	#sOutput = proc.read()
	with open(sFicOutput, 'a') as fic:
		fic.write(sOutput)
	if sOutput == '':
		sOutput = u'Opération effectuée.'
	sp.say(sOutput)
	#compileTask = subprocess.Popen(['python', sp.window.curPage.file, '>', sOutput], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	#output, err = compileTask.communicate()
	#rc = compileTask.returncode	
	return
	pageOutput = sp.window.open(sFicOutput)
	#BUGFIX: BUGSIXPAD: open ne renvoit pas la page ouverte
	pageOutput = [p for p in sp.window.pages if p.file == sFicOutput]
	pageOutput = pageOutput[0]
	#Fin BUGFIX
	pageOutput.focus()

def displayOutput():
	sOutput = os.path.join(sp.appdir, 'output.tmp')
	sp.window.open(sOutput)


def getFileType():
	curPage = sp.window.curPage
	dummy, ext = os.path.splitext(curPage.file)
	ext = ext.lower()
	if ext == '.m':	
		return 'matlab'
	elif ext == '.py':
		return 'python'
	else:
		return None


def listFunctionsAndClasses():
	curPage = sp.window.curPage
	lst = []
	fType = getFileType()
	if fType  == 'matlab':
		regex = matlabFunRegex
	elif fType == 'python':
		regex = pythonFunRegex
	else:
		sp.window.messageBeep(0)
		return []
	for match in regex.finditer(curPage.text):
		text = match.group('type') + ' ' + match.group('name')
		position = match.start()
		line = curPage.lineOfOffset(position)
		lst.append((line, text))
	return lst

def chooseFunctionOrClass():
	lst = listFunctionsAndClasses()
	lstNames = [n for l,n in lst]
	ret = sp.window.choice('Choisir une fonction ou classe', 'Liste des fonctions et classes', lstNames)
	if ret == -1:
		return
	targetLine, dummy = lst[ret]
	curPage = sp.window.curPage
	curPage.position = curPage.lineSafeStartOffset(targetLine)
	sp.say(curPage.curLineText)

FindWindow = ctypes.windll.user32.FindWindowW
FindWindow.restype = ctypes.wintypes.HWND
FindWindow.argtypes = [
	ctypes.wintypes.LPCWSTR, #lpClassName
	ctypes.wintypes.LPCWSTR, #lpWindowName
]

FindWindowEx = ctypes.windll.user32.FindWindowExW
FindWindowEx.restype = ctypes.wintypes.HWND
FindWindowEx.argtypes = [
	ctypes.wintypes.HWND,
	ctypes.wintypes.HWND,
	ctypes.wintypes.LPCWSTR, #lpClassName
	ctypes.wintypes.LPCWSTR, #lpWindowName
]

SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
SetForegroundWindow.restype = ctypes.wintypes.HWND
SetForegroundWindow.argtypes = [
	ctypes.wintypes.HWND
]

def findMatlabWindow():
	hMatlab = FindWindow(None, u"MATLAB Command Window")
	if not hMatlab:
		return hMatlab
	hMatlabChild = FindWindowEx(hMatlab, None, u'AfxFrameOrView110', None)
	if not hMatlabChild:
		sp.say(u'Child non trouvé')
		return hMatlabChild
	hMatlabEdit = FindWindowEx(hMatlabChild, None, u'Edit', None)
	if not hMatlabEdit:
		sp.say('no edit')
	return hMatlabEdit 



hPythonEdit = 0

def findPythonWindow():
	global hPythonEdit
	if not hPythonEdit:
		hPythonEdit = int(sp.window.prompt(u'Entrer le handle de la fenêtre Python', u'Définition de la fenêtre Python'))
	if not hPythonEdit:
		sp.say('no edit')
		return
	return hPythonEdit 


#===============

WM_CHAR = 0x0102
sendMessage = ctypes.windll.user32.SendMessageA

def sendCommand(hwnd, cmd):
	for c in cmd:
		sendMessage(hwnd, WM_CHAR, ord(c), 0)
	sendMessage(hwnd, WM_CHAR, 13, 0)


def setBreakpoint():
	nom_fic = sp.window.curPage.file
	ligne = sp.window.curPage.curLine
	if nom_fic[-2:].lower() == '.m':
		hWindow = findMatlabWindow()
		cmd = "dbstop('in', '" + nom_fic + "', 'at', '" + str(ligne)+ "')"
	elif nom_fic[-3:].lower() == '.py':
		hWindow = findPythonWindow()
		cmd = 'b ' + nom_fic + ':' + str(ligne+1)
	else:
		sp.say(u'Type de fichier inconnu.')
	if not hWindow:
		sp.say(u'Fenêtre console non trouvée')
		return
	sp.say(u'Set breakpoint.')
	SetForegroundWindow(hWindow)
	sendCommand(hWindow, cmd)




def getCurrentWord():
	curPage = sp.window.curPage
	curLineText = curPage.curLineText
	pattern = r'(?P<name>[A-Za-z_]+)'
	matchList = [(m.start(), m.group('name')) for m in re.finditer(pattern, curLineText)]
	wordList = [(w, p) for p, w in matchList if p <= curPage.curColumn]
	if not wordList:
		return None
	word, position = wordList[-1]
	if curPage.curColumn > position+len(word):
		return None
	return word

	

def goToDefinition():
	w = getCurrentWord()
	if not w:
		sp.window.messageBeep(0)
		return
	fType = getFileType()
	if fType == 'matlab':
		findDefinitionMatlab(w)
	elif fType == 'python':
		findDefinitionPython(w)
	else:
		sp.window.messageBeep(0)
		return
	sp.say(w)


def findDefinitionPython(text):
	raise NotImplementedError
	
def findDefinitionMatlab(text):
	pattern = r'^\s*(?P<type>(function)|(class)s+)(\w+\s*=\s*)?' + text
	pattern = r'function.*' + text + r'\b'
	file, lineno = findInFiles(pattern)
	if file is None:
		sp.window.messageBeep(0)
		return
	sp.window.open(file)
	sp.window.curPage.curLine = lineno

def findInFiles(pattern):
	rep, fic = os.path.split(sp.window.curPage.file)
	for fic in os.listdir(rep):
		path = os.path.join(rep, fic)
		lineno = findInFile(path, pattern)
		if lineno is not None:
			return path, lineno
	#Occurance non trouvée
	return None, None


def findInFile(path, pattern, fromline=0):
	with open(path, 'r') as fic:
		for n, line in enumerate(fic):
			if n<fromline:
				continue
			m = re.search(pattern, line)
			if m:
				return n
	#pattern non trouvé
	return None
	
def runAsPythonScript():
	codeName = sp.window.curPage.name
	codeFile = sp.window.curPage.file
	if sp.window.curPage.modified:
		codeFile = codeFile + ' [modified]'
	filename = codeName + ' - ' + codeFile
	global_vars = {}
	print('toto')
	codeStr = sp.window.curPage.text
	try:
		code = compile(codeStr, filename, 'exec')
		exec(code, global_vars)
	except SyntaxError:
		raise


menuPerso = sp.window.menus.add(label='&Perso',submenu=True, name = 'perso')
menuPerso.add(label='Commenter', action= lambda : comment(True), accelerator='Ctrl+Q', name='comment', submenu=False)
menuPerso.add(label='Décommenter', action = lambda : comment(False), accelerator='Ctrl+Shift+Q', name='uncomment', submenu=False)
menuPerso.add(label='Fonction &suivante', action=goToNextFunction, accelerator='alt+PageDown', name='goToNextFunction', submenu=False)
menuPerso.add(label=u'Fonction &précédante', action=goToPreviousFunction, accelerator='alt+PageUp', name='goToPreviousFunction', submenu=False)
menuPerso.add(label='List function or class', action=chooseFunctionOrClass, accelerator='Ctrl+L', name='listFunctionOrClass', submenu=False)
menuPerso.add(label='&Run', action=runFile, accelerator='Ctrl+F5', name='runFile', submenu=False)
menuPerso.add(label='Display &output', action=displayOutput, accelerator='Alt+Shift+F5', name='displayOutput', submenu=False)
menuPerso.add(label=u'Set &breakpoint', action=setBreakpoint, accelerator='F9', name='setBreakpoint', submenu=False)
menuPerso.add(label=u'Go to &definition', action=goToDefinition, accelerator='Ctrl+F2', name='goToDefinition', submenu=False)
menuPerso.add(label=u'Run as P&ython script', action=runAsPythonScript, accelerator='Ctrl+Shift+F5', name='runAsPythonScript', submenu=False)
