# intpyapp.py  - Interactive Python application class
#
import win32con
import win32api
import win32ui
import __main__
import sys
import os
from . import app
import traceback
from pywin.mfc import afxres, dialog
import commctrl
from . import dbgcommands

lastLocateFileName = ".py" # used in the "File/Locate" dialog...

# todo - _SetupSharedMenu should be moved to a framework class.
def _SetupSharedMenu_(self):
	sharedMenu = self.GetSharedMenu()
	from pywin.framework import toolmenu
	toolmenu.SetToolsMenu(sharedMenu)
	from pywin.framework import help
	help.SetHelpMenuOtherHelp(sharedMenu)
from pywin.mfc import docview
docview.DocTemplate._SetupSharedMenu_=_SetupSharedMenu_

class MainFrame(app.MainFrame):
	def OnCreate(self, createStruct):
		self.closing = 0
		if app.MainFrame.OnCreate(self, createStruct)==-1:
			return -1
		style = win32con.WS_CHILD | afxres.CBRS_SIZE_DYNAMIC | afxres.CBRS_TOP | afxres.CBRS_TOOLTIPS | afxres.CBRS_FLYBY

		self.EnableDocking(afxres.CBRS_ALIGN_ANY)

		tb = win32ui.CreateToolBar (self, style | win32con.WS_VISIBLE)
		tb.ModifyStyle(0, commctrl.TBSTYLE_FLAT)
		tb.LoadToolBar(win32ui.IDR_MAINFRAME)
		tb.EnableDocking(afxres.CBRS_ALIGN_ANY)
		tb.SetWindowText("Standard")
		self.DockControlBar(tb)
		# Any other packages which use toolbars
		from pywin.debugger.debugger import PrepareControlBars
		PrepareControlBars(self)
		# Note "interact" also uses dockable windows, but they already happen

		# And a "Tools" menu on the main frame.
		menu = self.GetMenu()
		from . import toolmenu
		toolmenu.SetToolsMenu(menu, 2)
		# And fix the "Help" menu on the main frame
		from pywin.framework import help
		help.SetHelpMenuOtherHelp(menu)

	def OnClose(self):
		try:
			import pywin.debugger
			if pywin.debugger.currentDebugger is not None and pywin.debugger.currentDebugger.pumping:
				try:
					pywin.debugger.currentDebugger.close(1)
				except:
					traceback.print_exc()
				return
		except win32ui.error:
			pass
		self.closing = 1
		self.SaveBarState("ToolbarDefault")
		self.SetActiveView(None) # Otherwise MFC's OnClose may _not_ prompt for save.

		from pywin.framework import help
		help.FinalizeHelp()

		self.DestroyControlBar(afxres.AFX_IDW_TOOLBAR)
		self.DestroyControlBar(win32ui.ID_VIEW_TOOLBAR_DBG)
		
		return self._obj_.OnClose()

	def DestroyControlBar(self, id):
		try:
			bar = self.GetControlBar(id)
		except win32ui.error:
			return
		bar.DestroyWindow()

	def OnCommand(self, wparam, lparam):
		# By default, the current MDI child frame will process WM_COMMAND
		# messages before any docked control bars - even if the control bar
		# has focus.  This is a problem for the interactive window when docked.
		# Therefore, we detect the situation of a view having the main frame
		# as its parent, and assume it must be a docked view (which it will in an MDI app)
		try:
			v = self.GetActiveView() # Raise an exception if none - good - then we want default handling
			# Main frame _does_ have a current view (ie, a docking view) - see if it wants it.
			if v.OnCommand(wparam, lparam):
				return 1
		except (win32ui.error, AttributeError):
			pass
		return self._obj_.OnCommand(wparam, lparam)

class InteractivePythonApp(app.CApp):
	# This works if necessary - just we dont need to override the Run method.
#	def Run(self):
#		return self._obj_.Run()

	def HookCommands(self):
		app.CApp.HookCommands(self)
		dbgcommands.DebuggerCommandHandler().HookCommands()
		self.HookCommand(self.OnViewBrowse,win32ui.ID_VIEW_BROWSE)
		self.HookCommand(self.OnFileImport,win32ui.ID_FILE_IMPORT)
		self.HookCommand(self.OnFileCheck,win32ui.ID_FILE_CHECK)
		self.HookCommandUpdate(self.OnUpdateFileCheck, win32ui.ID_FILE_CHECK)
		self.HookCommand(self.OnFileRun,win32ui.ID_FILE_RUN)
		self.HookCommand(self.OnFileLocate,win32ui.ID_FILE_LOCATE)
		self.HookCommand(self.OnInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE)
		self.HookCommandUpdate(self.OnUpdateInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE)
		self.HookCommand(self.OnViewOptions, win32ui.ID_VIEW_OPTIONS)
		self.HookCommand(self.OnHelpIndex, afxres.ID_HELP_INDEX)
		self.HookCommand(self.OnFileSaveAll, win32ui.ID_FILE_SAVE_ALL)
		self.HookCommand(self.OnViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG)
		self.HookCommandUpdate(self.OnUpdateViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG)

	def CreateMainFrame(self):
		return MainFrame()

	def MakeExistingDDEConnection(self):
		# Use DDE to connect to an existing instance
		# Return None if no existing instance
		try:
			from . import intpydde
		except ImportError:
			# No dde support!
			return None
		conv = intpydde.CreateConversation(self.ddeServer)
		try:
			conv.ConnectTo("Pythonwin", "System")
			return conv
		except intpydde.error:
			return None

	def InitDDE(self):
		# Do all the magic DDE handling.  
		# Returns TRUE if we have pumped the arguments to our
		# remote DDE app, and we should terminate.
		try:
			from . import intpydde
		except ImportError:
			self.ddeServer = None
			intpydde = None
		if intpydde is not None:
			self.ddeServer = intpydde.DDEServer(self)
			self.ddeServer.Create("Pythonwin", intpydde.CBF_FAIL_SELFCONNECTIONS )
			try:
				# If there is an existing instance, pump the arguments to it.
				connection = self.MakeExistingDDEConnection()
				if connection is not None:
					connection.Exec("self.Activate()")
					if self.ProcessArgs(sys.argv, connection) is None:
						return 1
			except:
				# It is too early to 'print' an exception - we
				# don't have stdout setup yet!
				win32ui.DisplayTraceback(sys.exc_info(), " - error in DDE conversation with Pythonwin")
				return 1

	def InitInstance(self):
		# Allow "/nodde" and "/new" to optimize this!
		if ("/nodde" not in sys.argv and "/new" not in sys.argv
				and "-nodde" not in sys.argv and "-new" not in sys.argv):
			if self.InitDDE():
				return 1 # A remote DDE client is doing it for us!
		else:
			self.ddeServer = None

		win32ui.SetRegistryKey("Python %s" % (sys.winver,)) # MFC automatically puts the main frame caption on!
		app.CApp.InitInstance(self)

		# Create the taskbar icon
		win32ui.CreateDebuggerThread()

		# Allow Pythonwin to host OCX controls.
		win32ui.EnableControlContainer()

		# Display the interactive window if the user wants it.
		from . import interact
		interact.CreateInteractiveWindowUserPreference()

		# Load the modules we use internally.
		self.LoadSystemModules()

		# Load additional module the user may want.
		self.LoadUserModules()

		# Load the ToolBar state near the end of the init process, as
		# there may be Toolbar IDs created by the user or other modules.
		# By now all these modules should be loaded, so all the toolbar IDs loaded.
		try:
			self.frame.LoadBarState("ToolbarDefault")
		except win32ui.error:
			# MFC sucks.  It does essentially "GetDlgItem(x)->Something", so if the
			# toolbar with ID x does not exist, MFC crashes!  Pythonwin has a trap for this
			# but I need to investigate more how to prevent it (AFAIK, ensuring all the
			# toolbars are created by now _should_ stop it!)
			pass

		# Finally process the command line arguments.
		try:
			self.ProcessArgs(sys.argv)
		except:
			# too early for printing anything.
			win32ui.DisplayTraceback(sys.exc_info(), " - error processing command line args")

	def ExitInstance(self):
		win32ui.DestroyDebuggerThread()
		try:
			from . import interact
			interact.DestroyInteractiveWindow()
		except:
			pass
		if self.ddeServer is not None:
			self.ddeServer.Shutdown()
			self.ddeServer = None
		return app.CApp.ExitInstance(self)

	def Activate(self):
		# Bring to the foreground.  Mainly used when another app starts up, it asks
		# this one to activate itself, then it terminates.
		frame = win32ui.GetMainFrame()
		frame.SetForegroundWindow()
		if frame.GetWindowPlacement()[1]==win32con.SW_SHOWMINIMIZED:
			frame.ShowWindow(win32con.SW_RESTORE)

	def ProcessArgs(self, args, dde = None):
		# If we are going to talk to a remote app via DDE, then
		# activate it!
		if len(args)<1 or not args[0]: # argv[0]=='' when started without args, just like Python.exe!
			return

		i = 0
		while i < len(args):
			argType = args[i]
			i += 1
			if argType.startswith('-'):
				# Support dash options. Slash options are misinterpreted by python init
				# as path and not finding usually 'C:\\' ends up in sys.path[0]
				argType = '/' + argType[1:]
			if not argType.startswith('/'):
				argType = win32ui.GetProfileVal("Python","Default Arg Type","/edit").lower()
				i -= 1  #  arg is /edit's parameter
			par = i < len(args) and args[i] or 'MISSING'
			if argType in ['/nodde', '/new', '-nodde', '-new']:
				# Already handled
				pass
			elif argType.startswith('/goto:'):
				gotoline = int(argType[len('/goto:'):])
				if dde:
					dde.Exec("from pywin.framework import scriptutils\n"
							 "ed = scriptutils.GetActiveEditControl()\n"
							 "if ed: ed.SetSel(ed.LineIndex(%s - 1))" % gotoline)
				else:
					from . import scriptutils
					ed = scriptutils.GetActiveEditControl()
					if ed: ed.SetSel(ed.LineIndex(gotoline - 1))
			elif argType == "/edit":
				# Load up the default application.
				i += 1
				fname = win32api.GetFullPathName(par)
				if not os.path.isfile(fname):
					# if we don't catch this, OpenDocumentFile() (actually
					# PyCDocument.SetPathName() in
					# pywin.scintilla.document.CScintillaDocument.OnOpenDocument)
					# segfaults Pythonwin on recent PY3 builds (b228)
					win32ui.MessageBox(
							"No such file: %s\n\nCommand Line: %s" % (
									fname, win32api.GetCommandLine()),
							"Open file for edit", win32con.MB_ICONERROR)
					continue
				if dde:
					dde.Exec("win32ui.GetApp().OpenDocumentFile(%s)" % (repr(fname)))
				else:
					win32ui.GetApp().OpenDocumentFile(par)
			elif argType=="/rundlg":
				if dde:
					dde.Exec("from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 1)" % (par, ' '.join(args[i + 1:])))
				else:
					from . import scriptutils
					scriptutils.RunScript(par, ' '.join(args[i + 1:]))
				return
			elif argType=="/run":
				if dde:
					dde.Exec("from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 0)" % (par, ' '.join(args[i + 1:])))
				else:
					from . import scriptutils
					scriptutils.RunScript(par, ' '.join(args[i + 1:]), 0)
				return
			elif argType=="/app":
				raise RuntimeError("/app only supported for new instances of Pythonwin.exe")
			elif argType=='/dde': # Send arbitary command
				if dde is not None:
					dde.Exec(par)
				else:
					win32ui.MessageBox("The /dde command can only be used\r\nwhen Pythonwin is already running")
				i += 1
			else:
				raise ValueError("Command line argument not recognised: %s" % argType)


	def LoadSystemModules(self):
		self.DoLoadModules("pywin.framework.editor,pywin.framework.stdin")

	def LoadUserModules(self, moduleNames = None):
		# Load the users modules.
		if moduleNames is None:
			default = "pywin.framework.sgrepmdi,pywin.framework.mdi_pychecker"
			moduleNames=win32ui.GetProfileVal('Python','Startup Modules',default)
		self.DoLoadModules(moduleNames)

	def DoLoadModules(self, moduleNames): # ", sep string of module names.
		if not moduleNames: return
		modules = moduleNames.split(",")
		for module in modules:
			try:
				__import__(module)
			except: # Catch em all, else the app itself dies! 'ImportError:
				traceback.print_exc()
				msg = 'Startup import of user module "%s" failed' % module
				print(msg)
				win32ui.MessageBox(msg)

	#
	# DDE Callback
	#
	def OnDDECommand(self, command):
		try:
			exec(command + "\n")
		except:
			print("ERROR executing DDE command: ", command)
			traceback.print_exc()
			raise

	#
	# General handlers
	#
	def OnViewBrowse( self, id, code ):
		" Called when ViewBrowse message is received "
		from pywin.tools import browser
		obName = dialog.GetSimpleInput('Object', '__builtins__', 'Browse Python Object')
		if obName is None:
			return
		try:
			browser.Browse(eval(obName, __main__.__dict__, __main__.__dict__))
		except NameError:
			win32ui.MessageBox('This is no object with this name')
		except AttributeError:
			win32ui.MessageBox('The object has no attribute of that name')
		except:
			traceback.print_exc()
			win32ui.MessageBox('This object can not be browsed')

	def OnFileImport( self, id, code ):
		" Called when a FileImport message is received. Import the current or specified file"
		from . import scriptutils
		scriptutils.ImportFile()

	def OnFileCheck( self, id, code ):
		" Called when a FileCheck message is received. Check the current file."
		from . import scriptutils
		scriptutils.CheckFile()

	def OnUpdateFileCheck(self, cmdui):
		from . import scriptutils
		cmdui.Enable( scriptutils.GetActiveFileName(0) is not None )

	def OnFileRun( self, id, code ):
		" Called when a FileRun message is received. "
		from . import scriptutils
		showDlg = win32api.GetKeyState(win32con.VK_SHIFT) >= 0
		scriptutils.RunScript(None, None, showDlg)

	def OnFileLocate( self, id, code ):
		from . import scriptutils
		global lastLocateFileName # save the new version away for next time...

		name = dialog.GetSimpleInput('File name', lastLocateFileName, 'Locate Python File')
		if name is None: # Cancelled.
			return
		lastLocateFileName = name
		# if ".py" supplied, rip it off!
		# should also check for .pys and .pyw
		if lastLocateFileName[-3:].lower()=='.py':
			lastLocateFileName = lastLocateFileName[:-3]
		lastLocateFileName = lastLocateFileName.replace(".","\\")
		newName = scriptutils.LocatePythonFile(lastLocateFileName)
		if newName is None:
			win32ui.MessageBox("The file '%s' can not be located" % lastLocateFileName)
		else:
			win32ui.GetApp().OpenDocumentFile(newName)

	# Display all the "options" proprety pages we can find
	def OnViewOptions(self, id, code):
		win32ui.InitRichEdit()
		sheet = dialog.PropertySheet("Pythonwin Options")
		# Add property pages we know about that need manual work.
		from pywin.dialogs import ideoptions
		sheet.AddPage( ideoptions.OptionsPropPage() )

		from . import toolmenu
		sheet.AddPage( toolmenu.ToolMenuPropPage() )

		# Get other dynamic pages from templates.
		pages = []
		for template in self.GetDocTemplateList():
			try:
				# Dont actually call the function with the exception handler.
				getter = template.GetPythonPropertyPages
			except AttributeError:
				# Template does not provide property pages!
				continue
			pages = pages + getter()

		# Debugger template goes at the end
		try:
			from pywin.debugger import configui
		except ImportError:
			configui = None
		if configui is not None: pages.append(configui.DebuggerOptionsPropPage())
		# Now simply add the pages, and display the dialog.
		for page in pages:
			sheet.AddPage(page)

		if sheet.DoModal()==win32con.IDOK:
			win32ui.SetStatusText("Applying configuration changes...", 1)
			win32ui.DoWaitCursor(1)
			# Tell every Window in our app that win.ini has changed!
			win32ui.GetMainFrame().SendMessageToDescendants(win32con.WM_WININICHANGE, 0, 0)
			win32ui.DoWaitCursor(0)

	def OnInteractiveWindow(self, id, code):
		# toggle the existing state.
		from . import interact
		interact.ToggleInteractiveWindow()

	def OnUpdateInteractiveWindow(self, cmdui):
		try:
			interact=sys.modules['pywin.framework.interact']
			state = interact.IsInteractiveWindowVisible()
		except KeyError: # Interactive module hasnt ever been imported.
			state = 0
		cmdui.Enable()
		cmdui.SetCheck(state)

	def OnFileSaveAll(self, id, code):
		# Only attempt to save editor documents.
		from pywin.framework.editor import editorTemplate
		num = 0
		for doc in editorTemplate.GetDocumentList():
			if doc.IsModified() and doc.GetPathName():
				num = num = 1
				doc.OnSaveDocument(doc.GetPathName())
		win32ui.SetStatusText("%d documents saved" % num, 1)

	def OnViewToolbarDbg(self, id, code):
		if code==0:
			return not win32ui.GetMainFrame().OnBarCheck(id)

	def OnUpdateViewToolbarDbg(self, cmdui):
		win32ui.GetMainFrame().OnUpdateControlBarMenu(cmdui)
		cmdui.Enable(1)

	def OnHelpIndex( self, id, code ):
		from . import help
		help.SelectAndRunHelpFile()

# As per the comments in app.py, this use is depreciated.
# app.AppBuilder = InteractivePythonApp

# Now all we do is create the application
thisApp = InteractivePythonApp()
