
if __name__ == "__main__" : raise Exception ()


import inspect
import sys

import mosaic.tools.enforcements as enforcements
import mosaic.tools.transcript as transcript


tracer = object ()
enforcer = object ()


class _DebuggableMeta (type) :
	
	def __init__ (_class, _name, _bases, _namespace) :
		
		type.__init__ (_class, _name, _bases, _namespace)
		
		debuggable (_class, None, None)
		
		return


def _replace_te_class (_class, _class_parent_tracer, _class_parent_enforcer) :
	
	# ---- coercing target
	if not inspect.isclass (_class) :
		raise Exception ('wtf! (target `%s` is not a class)' % (_class,))
	
	# ---- replacing class tracer and enforcer members
	_class_tracer, _class_enforcer = _replace_te_members (_class, _class_parent_tracer, _class_parent_enforcer)
	
	# ---- determining members tracer and enforcer parents
	if _class_tracer is not None :
		_member_parent_tracer = _class_tracer
	else :
		_member_parent_tracer = _class_parent_tracer
	if _class_enforcer is not None :
		_member_parent_enforcer = _class_enforcer
	else :
		_member_parent_enforcer = _class_parent_enforcer
	
	# ---- replacing members tracer and enforcer members or arguments
	for _member_identifier, _member_value in inspect.getmembers (_class) :
		if inspect.ismethod (_member_value) or inspect.isfunction (_member_value) :
			_replace_te_members (_member_value, _member_parent_tracer, _member_parent_enforcer)
			_replace_te_arguments (_member_value, _member_parent_tracer, _member_parent_enforcer)
	
	return


def _replace_te_members (_object, _parent_tracer, _parent_enforcer) :
	
	# ---- determining members
	_tracer_identifier = None
	_enforcer_identifier = None
	for _member_identifier, _member_value in inspect.getmembers (_object) :
		if _member_value is tracer :
			if not inspect.ismodule (_object) or _member_identifier != '_tracer' :
				if _tracer_identifier is not None :
					raise Exception ('wtf! (duplicate `tracer` members found as `%s` and `%s`)' % (_tracer_identifier, _member_identifier))
				_tracer_identifier = _member_identifier
		if _member_value is enforcer :
			if not inspect.ismodule (_object) or _member_identifier != '_enforcer' :
				if _enforcer_identifier is not None :
					raise Exception ('wtf! (duplicate `enforcer` members found as `%s` and `%s`)' % (_enforcer_identifier, _member_identifier))
				_enforcer_identifier = _member_identifier
	
	# ---- deleting members
	if _tracer_identifier is not None :
		delattr (_object, _tracer_identifier)
	if _enforcer_identifier is not None :
		delattr (_object, _enforcer_identifier)
	
	# ---- creating objects
	if _tracer_identifier is not None :
		_tracer = transcript.tracer_tf (_object, parent = _parent_tracer)
	if _enforcer_identifier is not None :
		_enforcer = enforcements.enforcer_ef (_object, parent = _parent_enforcer)
	
	# ---- re-creating members
	if _tracer_identifier is not None :
		setattr (_object, _tracer_identifier, _tracer)
	else :
		_tracer = None
	if _enforcer_identifier is not None :
		setattr (_object, _enforcer_identifier, _enforcer)
	else :
		_enforcer = None
	
	return (_tracer, _enforcer)


def _replace_te_arguments (_function, _parent_tracer, _parent_enforcer) :
	
	# ---- coercing target
	if inspect.isfunction (_function) :
		_routine = _function
	elif inspect.ismethod (_function) :
		_routine = _function.im_func
	else :
		raise Exception ('wtf! (target `%s` is not a function or a method)' % (_function,))
	
	# ---- determining arguments
	_tracer_index = None
	_enforcer_index = None
	_argspec = inspect.getargspec (_routine)
	if _argspec.defaults is not None :
		_default_offset = len (_argspec.args) - len (_argspec.defaults)
		for _default_index in xrange (len (_argspec.defaults)) :
			_argument_index = _default_offset + _default_index
			if _argspec.defaults[_default_index] is tracer :
				if _tracer_index is not None :
					raise Exception ('wtf! (duplicate `tracer` members found as `%s` and `%s`)' % (_argspec.args[_tracer_index], _argspec.args[_argument_index]))
				if hasattr (_routine, _argspec.args[_argument_index]) :
					raise Exception ('wtf! (`tracer` attribute already exists)')
				_tracer_index = _argument_index
			if _argspec.defaults[_default_index] is enforcer :
				if _enforcer_index is not None :
					raise Exception ('wtf! (duplicate `enforcer` members found as `%s` and `%s`)' % (_argspec.args[_enforcer_index], _argspec.args[_argument_index]))
				if hasattr (_routine, _argspec.args[_argument_index]) :
					raise Exception ('wtf! (`enforcer` attribute already exists)')
				_enforcer_index = _argument_index
	
	# ---- creating objects
	if _tracer_index is not None :
		_tracer = transcript.tracer_tf (_function, parent = _parent_tracer)
	if _enforcer_index is not None :
		_enforcer = enforcements.enforcer_ef (_function, parent = _parent_enforcer)
	
	# ---- re-creating defaults
	if _tracer_index is not None or _enforcer_index is not None :
		_defaults = list (_argspec.defaults)
		if _tracer_index is not None :
			_defaults[_tracer_index - _default_offset] = _tracer
		if _enforcer_index is not None :
			_defaults[_enforcer_index - _default_offset] = _enforcer
		_routine.func_defaults = tuple (_defaults)
	
	# ---- setting attributes
	if _tracer_index is not None :
		setattr (_routine, _argspec.args[_tracer_index], _tracer)
	else :
		_tracer = None
	if _enforcer_index is not None :
		setattr (_routine, _argspec.args[_enforcer_index], _enforcer)
	else :
		_enforcer = None
	
	return (_tracer, _enforcer)


def debuggable (_target, parent_tracer = None, parent_enforcer = None) :
	
	_parent_tracer = parent_tracer
	_parent_enforcer = parent_enforcer
	
	if isinstance (_target, basestring) :
		_target = sys.modules[_target]
	
	if inspect.isfunction (_target) or inspect.ismethod (_target) :
		_replace_te_members (_target, _parent_tracer, _parent_enforcer)
		_replace_te_arguments (_target, _parent_tracer, _parent_enforcer)
	elif inspect.isclass (_target) :
		_replace_te_class (_target, _parent_tracer, _parent_enforcer)
	elif inspect.ismodule (_target) :
		_replace_te_members (_target, _parent_tracer, _parent_enforcer)
	else :
		raise Exception ('wtf! (unexpected target type)')
	
	return _target


class Debuggable :
	
	__metaclass__ = _DebuggableMeta
	__slots__ = ()
