import sys, traceback, string

from win32com.axscript import axscript
from win32com.axdebug import codecontainer, axdebug, gateways, documents, contexts, adb, expressions
from win32com.axdebug.util import trace, _wrap, _wrap_remove

import pythoncom
import win32api, winerror
import os

currentDebugger = None

class ModuleTreeNode:
	"""Helper class for building a module tree
	"""
	def __init__(self, module):
		modName = module.__name__
		self.moduleName = modName
		self.module = module
		self.realNode = None
		self.cont = codecontainer.SourceModuleContainer(module)
	def __repr__(self):
		return "<ModuleTreeNode wrapping %s>" % (self.module)
	def Attach(self, parentRealNode):
		self.realNode.Attach(parentRealNode)
		
	def Close(self):
		self.module = None
		self.cont = None
		self.realNode = None

def BuildModule(module, built_nodes, rootNode, create_node_fn, create_node_args ):
	if module:
		keep = module.__name__
		keep = keep and (built_nodes.get(module) is None)
		if keep and hasattr(module, '__file__'):
			keep = string.lower(os.path.splitext(module.__file__)[1]) not in [".pyd", ".dll"]
#		keep = keep and module.__name__=='__main__'
	if module and keep:
#		print "keeping", module.__name__
		node = ModuleTreeNode(module)
		built_nodes[module] = node
		realNode = apply(create_node_fn, (node,)+create_node_args)
		node.realNode = realNode
		
		# Split into parent nodes.
		parts = string.split(module.__name__, '.')
		if parts[-1][:8]=='__init__': parts = parts[:-1]
		parent = string.join(parts[:-1], '.')
		parentNode = rootNode
		if parent:
			parentModule = sys.modules[parent]
			BuildModule(parentModule, built_nodes, rootNode, create_node_fn, create_node_args)
			parentNode = built_nodes[parentModule].realNode
		node.Attach(parentNode)

def RefreshAllModules(builtItems, rootNode, create_node, create_node_args):
	for module in sys.modules.values():
		BuildModule(module, builtItems, rootNode, create_node, create_node_args)
		
# realNode = pdm.CreateDebugDocumentHelper(None) # DebugDocumentHelper node?
# app.CreateApplicationNode() # doc provider node.

class CodeContainerProvider(documents.CodeContainerProvider):
	def __init__(self, axdebugger):
		self.axdebugger = axdebugger
		documents.CodeContainerProvider.__init__(self)
		self.currentNumModules = len(sys.modules)
		self.nodes = {}
		self.axdebugger.RefreshAllModules(self.nodes, self)
		
	def FromFileName(self, fname):
### It appears we cant add modules during a debug session!
#		if self.currentNumModules != len(sys.modules):
#			self.axdebugger.RefreshAllModules(self.nodes, self)
#			self.currentNumModules = len(sys.modules)
#		for key in self.ccsAndNodes.keys():
#			print "File:", key
		return documents.CodeContainerProvider.FromFileName(self, fname)

	def Close(self):
		documents.CodeContainerProvider.Close(self)
		self.axdebugger = None
		print "Closing %d nodes" % (len(self.nodes))
		for node in self.nodes.values():
			node.Close()
		self.nodes = {}

class OriginalInterfaceMaker:
	def MakeInterfaces(self, pdm):
		app = self.pdm.CreateApplication()
		self.cookie = pdm.AddApplication(app)
		root = app.GetRootNode()
		return app, root

	def CloseInterfaces(self, pdm):
		pdm.RemoveApplication(self.cookie)

class SimpleHostStyleInterfaceMaker:
	def MakeInterfaces(self, pdm):
		app = pdm.GetDefaultApplication()
		root = app.GetRootNode()
		return app, root
	
	def CloseInterfaces(self, pdm):
		pass
	

class AXDebugger:
	def __init__(self, interfaceMaker = None, processName = None):
		if processName is None: processName = "Python Process"
		if interfaceMaker is None: interfaceMaker = SimpleHostStyleInterfaceMaker()

		self.pydebugger = adb.Debugger()
		
		self.pdm=pythoncom.CoCreateInstance(axdebug.CLSID_ProcessDebugManager,None,pythoncom.CLSCTX_ALL, axdebug.IID_IProcessDebugManager)

		self.app, self.root = interfaceMaker.MakeInterfaces(self.pdm)
		self.app.SetName(processName)
		self.interfaceMaker = interfaceMaker

		expressionProvider = _wrap(expressions.ProvideExpressionContexts(), axdebug.IID_IProvideExpressionContexts)
		self.expressionCookie = self.app.AddGlobalExpressionContextProvider(expressionProvider)
		
		contProvider = CodeContainerProvider(self)
		self.pydebugger.AttachApp(self.app, contProvider)

	def Break(self):
		# Get the frame we start debugging from - this is the frame 1 level up
		try:
			1 + ''
		except:
			frame = sys.exc_info()[2].tb_frame.f_back

		# Get/create the debugger, and tell it to break.
		self.app.StartDebugSession()
#		self.app.CauseBreak()

		self.pydebugger.SetupAXDebugging(None, frame)
		self.pydebugger.set_trace()

	def Close(self):
		self.pydebugger.ResetAXDebugging()
		self.interfaceMaker.CloseInterfaces(self.pdm)
		self.pydebugger.CloseApp()
		self.app.RemoveGlobalExpressionContextProvider(self.expressionCookie)
		self.expressionCookie = None

		self.pdm = None
		self.app = None
		self.pydebugger = None
		self.root = None

	def RefreshAllModules(self, nodes, containerProvider):
		RefreshAllModules(nodes, self.root, self.CreateApplicationNode, (containerProvider,))

	def CreateApplicationNode(self, node, containerProvider):
		realNode = self.app.CreateApplicationNode()
		
		document = documents.DebugDocumentText(node.cont)
		document = _wrap(document, axdebug.IID_IDebugDocument)

		node.cont.debugDocument = document

		provider = documents.DebugDocumentProvider(document)
		provider = _wrap(provider, axdebug.IID_IDebugDocumentProvider)
		realNode.SetDocumentProvider(provider)
		
		containerProvider.AddCodeContainer(node.cont, realNode)
		return realNode

def _GetCurrentDebugger():
	global currentDebugger
	if currentDebugger is None:
		currentDebugger = AXDebugger()
	return currentDebugger
	
def Break():
	_GetCurrentDebugger().Break()
	
brk = Break
set_trace = Break
		
def dosomethingelse():
	a=2
	b = "Hi there"
	
def dosomething():
	a=1
	b=2
	dosomethingelse()

def test():
	Break()
	raw_input("Waiting...")
	dosomething()
	print "Done"

if __name__=='__main__':
	print "About to test the debugging interfaces!"
	test()
	print " %d/%d com objects still alive" % (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
