#._cv_part guppy.gsl.Tester

class Doc2Tester:
    def __init__(self, mod, doc, node):
	self.mod = mod
	self.doc = doc
	self.node = node
	self.exdefs = []
	self.set_out([])
	self.test_names = {}
	self.condition_exprs = {}
	self.condition_methods = {}
	self.document_metas = []
	self.document_lang = None
	self.document_title = None
	self.example_exprs = {}
	self.ex_id = 1

    def _visit_children(self, node):
	E = self.mod.ReportedError
	for ch in node.children:
	    try:
		ch.accept(self)
	    except E:
		pass

    def ap(self, *args):
	self.extend(args)

    def error(self, msg, *args, **kwds):
	msg = 'Doc2Tester: ' + msg
	self.doc.env.error(msg, *args, **kwds)

    def get_condition_method(self, cond):
	idn = cond.get_id_name()
	name = self.condition_methods.get(idn)
	if name is None:
	    name = 'cond_%d'%len(self.condition_methods)
	    self.condition_methods[idn] = name
	    ap = self.ap
	    myself = '_self'
	    arg_names = cond.get_arg_names()
	    while myself in arg_names:
		myself = '_' + myself
	    argstring = ', '.join([myself]+arg_names)
	    ap('def %s(%s):'%(name, argstring), '<NL>', '<INDENT>')
	    ap('# Condition: %s'% idn, '<NL>')
	    pcs = cond.find_aspects('python_code')
	    if not pcs:
		self.error('No python code specified for testing condition: %r.'%idn,
			   cond.src.node, exception=None)
	    for pc in pcs:
		for ic in pc.find_aspects('in_context'):
		    ctx = ic.src.node.arg.strip()
		    for line in ctx.split('\n'):
			ap(line, '<NL>')
		if pc is pcs[-1]:
		    ap('return (%s)'%pc.src.node.arg.strip(), '<NL>')
		else:
		    ap('if not (%s):'%pc.src.node.arg.strip(), '<NL>','<INDENT>')
		    ap('return False','<NL>','<DEDENT>')
	    ap('<DEDENT>')
	return name


    def gen_comment(self, s):
	for line in s.split('\n'):
	    self.ap('# '+line, '<NL>')

    def get_children(self, node):
	if not node.arg.strip():
	    return node.children
	return (self.doc.node_of_taci('name', node.arg.strip()),)+tuple(node.children)

    def get_condition_expr(self, po, args, attr, src, tgt):
	cond = po.get_definition()
	if cond is None:
	    return None

	name = self.get_condition_method(cond)

	callargs = []
	self_name = attr.get_self_name()
	for an in po.arg_names:
	    v = None
	    if an == self_name:
		v = src
	    elif an == '<returned value>':
		v = tgt
	    else:
		v = args.get_arg_value(an)
		if v is None:
		    self.error('Invalid condition argument: %r.'%an,
			       po.src.node,
			       exception=None)
		    continue
		v = args.mapname(v)
	    callargs.append(v)


	return '%sself.%s(%s)'%(
	    ('','not ')[po.is_not],
	    name,
	    ', '.join(callargs))

    def get_example_expr(self, eg):
	ex = self.example_exprs.get(eg)
	if ex is not None:
	    return ex
	ex = eg.get_ex_text()
	ctx = eg.get_ctx_text()
	if ctx:
	    ex_name = 'get_ex_%d'%self.ex_id
	    self.ex_id += 1
	    self.ap('def %s(self):'%ex_name, '<NL>', '<INDENT>')
	    for line in ctx.split('\n'):
		self.ap(line, '<NL>')
	    self.ap('return %s'%ex, '<NL>', '<DEDENT>')
	    ex = 'self.%s()'%ex_name
	
	self.example_exprs[eg] = ex
	return ex	

    def get_test_name(self, kind):
	tn = kind.srclastname
	if tn in self.test_names:
	    i = self.test_names[tn]
	    self.test_names[tn] += 1
	    tn = '%s_%d'%(tn, i)
	else:
	    self.test_names[tn] = 1
	return tn
	
    def get_tester(self):
	ap = self.ap
	ap('# Tests generated by: %s'%__name__, '<NL>')
	ap('# Main source file: %s'%self.doc.env.get_package().get_filename(), '<NL>')
	
	ap('# Date: %s'%self.mod.time.asctime(self.mod.time.localtime()), '<NL>')
	ap('class Tester:','<NL>','<INDENT>')
	ap('tests = {}','<NL>')
	self._visit_children(self.node)
	lines = []
	indent = 0
	line = []
	for tok in self.out:
	    if tok == '<INDENT>':
		indent += 4
	    elif tok == '<DEDENT>':
		indent -= 4
	    elif tok == '<NL>':
		lines.append(''.join(line))
		line = []
	    else:
		if not line:
		    line.append(indent * ' ')
		line.append(tok)
	return '\n'.join(lines)+'\n'
			      

    def set_out(self, out):
	self.out = out
	self.extend = out.extend

    def visit_arglist(self, node):
	self.ap('(')
	comma = 0
	for ch in self.get_children(node):
	    if comma:
		self.ap(', ')
	    else:
		comma = 1
	    ch.accept(self)
	self.ap(')')

    def visit_assign(self, node):
	children = self.get_children(node)
	while children:
	    children[0].accept(self)
	    self.ap(' = ')
	    children[1].accept(self)
	    self.ap('<NL>')
	    children = children[2:]

    def visit_block(self, node):
	self.ap('<INDENT>')
	self._visit_children(node)
	self.ap('<DEDENT>')


    def visit_call(self, node):
	children = self.get_children(node)
	children[0].accept(self)
	self.ap('(')
	comma = 0
	for ch in children[1:]:
	    if comma:
		self.ap(', ')
	    else:
		comma = 1
	    ch.accept(self)
	self.ap(')')

    def visit_comment(self, node):
	pass

    def visit_def(self, node):
	self.ap('def ')
	children = self.get_children(node)
	children[0].accept(self)
	children[1].accept(self)
	self.ap(':','<NL>')
	for ch in children[2:]:
	    ch.accept(self)

    def visit_default(self, node):
	self.error('I don\'t know what to generate for the tag %r.'%node.tag, node)
	

    def visit_document(self, node):
	self._visit_children(node)

    def visit_document_lang(self, node):
	if self.document_lang is not None:
	    self.error('Duplicate document lang directive.', node)
	self.document_lang = node

    def visit_document_title(self, node):
	if self.document_title is not None:
	    self.error('Duplicate document title directive.', node)
	self.document_title = node

    def visit_exdefs(self, node):
	self.exdefs.append(node)

    def visit_fcall(self, node):
	self.visit_call(node)

    def visit_index(self, node):
	children = self.get_children(node)
	children[0].accept(self)
	self.ap('[')
	comma = 0
	for ch in children[1:]:
	    if comma:
		self.ap(', ')
	    else:
		comma = 1
	    ch.accept(self)
	self.ap(']')
	

    def visit_meta(self, node):
	self.document_metas.append(node)

    def visit_name(self, node):
	self.ap(node.arg.strip())

    def visit_pcall(self, node):
	self.visit_call(node)
	self.ap('<NL>')

    def visit_string(self, node):
	self.ap('%r'%node.arg.strip())

    def visit_subject(self, node):
	self.ap('SUBJECT')

    def visit_test_of(self, node):
	TestOf(self, node)


    def visit_test_program_segment(self, node):
	self.gen_comment('Test for: %s'%node.arg.strip())
	self._visit_children(node)

    def visit_to_document_only(self, node):
	pass

    def visit_to_tester_only(self, node):
	self._visit_children(node)

class DiffKind:
    def __init__(self, a, b):
	self.a = a
	self.b = b
	self.d_tag = 'diffkind'

    def get_atom_kinds(self):
	return [self]

    def get_id_name(self):
	return self.a.get_id_name() + '+' + self.b.get_id_name() + "'"

class EitherTest:
    def __init__(self, ):
	pass

class TestOf(Doc2Tester):
    def __init__(self, parent, node):

	self.__dict__.update(parent.__dict__)

	self.parent = parent
	self.node = node
	kind, args = node.arg, node.children

	mod = self.mod = parent.mod
	self.Kan = mod.KanExtension
	self.Cat = mod.Cat

	
	self.args = args

	self.kind = kind

	self.coverage = None

	for node in args:
	    t = node.tag
	    if t == 'coverage':
		if self.coverage is not None:
		    self.error('Duplicate coverage specifications', node, exception=None)
		else:
		    try:
			coverage = int(node.arg.strip())
		    except:
			coverage = node.arg.strip()
		    self.coverage = coverage
	    else:
		self.error('Invalid element in %r.'%self.node.tag, node, exception=None)

	if self.coverage is None:
	    self.coverage = 1 

	self.make_cat()

    def getattr_code(self, obj, attr):
	if self.mod.is_identifier(attr):
	    return '%s.%s'%(obj, attr)
	else:
	    return 'getattr(%s, %r)'%(obj, attr)

    def warn(self, message, node=None):
	self.error(message, node, exception=None, harmless=1)

    def make_cat(self):
	objects = []
	arrows = {}
	relations = []
	fa = {}
	fo = {}
	arg_counter = 1
	ac = [1]


	eithertests = []
	kinds = []
	kindofname = {}
	ex_setup = []
	ex_map = {}
	pc_checks = {}

	def add_kind(kind):
	    name = kind.get_id_name()
	    if name not in objects:
		objects.append(name)
		kinds.append(kind)
		kindofname[name] = kind
	    return name
		
	def get_example_expr(a):
	    if a in ex_map:
		return ex_map[a]
	    x = self.parent.get_example_expr(a)
            if 0:
                name = 'ex_%d'%len(ex_map)
                ex_setup.append('%s = %s'%(name, x))
                x = name
            usage = a.get_use_text(x)
	    ex_map[a] = usage
	    return usage

	def gen_atom_beam(asp):
	    asptgt = asp.tgt
	    tag = asptgt.d_tag
	    if tag == 'attribute':
		attr = asptgt
		otherkinds = []
		atkak = attr.get_atom_beams()
		for ab in atkak:
		    a = ab.tgt
		    if a.d_tag == 'mapping':
			op = asp + ab
			ret_kind = a.get_return_test_kind()
			tgt_name = add_kind(ret_kind)
			examples = a.get_args_examples(get_example_expr, kind)
			for args in examples:
			    arrow_name = 'CALLATTR%d'%ac[0]
			    ac[0] += 1
			    arrows[arrow_name] = (src_name, tgt_name)
			    fa[arrow_name] = ('callattr', op, args)
		    else:
			otherkinds.append(a)

		if otherkinds or not atkak:
		    if len(otherkinds) != len(atkak):
			# make new atk somehow
			oth = [(x.get_id_name(), x) for x in otherkinds]
			oth.sort()
			otherkinds = [y for (x, y) in oth]
			atk = self.mod.Main.Kind()
			atk.d_tag = 'kind'
			atk.aspects = otherkinds
			atk.tgtfullname = '(%s)'%('&'.join([x.tgtfullname for x in otherkinds]))

		    else:
			atk = attr.get_test_kind()
		    arrow_name = '%s:%d'%(attr.get_id_name(), ac[0])
		    ac[0] += 1
		    tgt_name = add_kind(atk)

		    assert arrow_name not in arrows
		    arrows[arrow_name] = (src_name, tgt_name)
		    fa[arrow_name] = ('getattr', attr.get_attr_name())

	    elif tag in ('operator','function_operator', 'inplace_operator',
			 'reverse_operator', 'mapping', 'setitem', 'delitem', 'getitem'):
		ret_kind = asp.get_return_test_kind()
		tgt_name = add_kind(ret_kind)

		examples = asp.get_args_examples(get_example_expr)
		for args in examples:
		    arrow_name = 'OPERATOR%d'%ac[0]
		    ac[0] += 1
		    arrows[arrow_name] = (src_name, tgt_name)
		    fa[arrow_name] = (tag, asp, args)
	    elif tag == 'either':
		asp_name = add_kind(asptgt)
		if asptgt is not kind:
		    arrow_name = '(%s:%s:%d)'%(src_name, asp_name, ac[0])
		    ac[0] += 1
		    arrows[arrow_name] = (src_name, asp_name)
		    fa[arrow_name] = ('subkind', asp_name)

		for i, ak in enumerate(asptgt.get_alt_kinds()):
		    tgt_name = add_kind(ak)
		    arrow_name = "(%s'%s)"%(asp_name, tgt_name)
		    arrows[arrow_name] = (asp_name, tgt_name)
		    fa[arrow_name] = ('superkind', i)
	    else:
		assert 0

	add_kind(self.kind)

	while kinds:
	    kind = kinds.pop()
	    src_name = kind.get_id_name()
	    for asp in kind.get_atom_beams():
		try:
		    gen_atom_beam(asp)
		except self.mod.ReportedError:
		    pass

		    

	cat = self.Cat.oarcat(objects, arrows, relations)
	tester = self.Kan.category_tester(self.Cat.Functor(fo, fa, cat), coverage=self.coverage)
	def get_arrow_name(a):
	    return ','.join(a)

	tester.get_arrow_name = get_arrow_name

	object_tester = tester.get_object_tester(self.kind.get_id_name())
	if 0:
	    print 'kind: ', self.kind.get_id_name()
	    print 'objects', objects
	    print 'arrows', arrows

	# print 'test code:'

	icode = object_tester.get_intermediate_test_code()
	# print 'icode', icode

	e_names = {}
	e_name_no = [0]

	def e_name_of(a):
	    if not a:
		return 'arg'
	    #return 'e[%r]'%a
	    if a not in e_names:
		e_names[a] = 't%d'%e_name_no[0]
		e_name_no[0] += 1
	    return e_names[a]
	    
	def call_fo(a, b):
	    return 'fo[%r](%s)'%(a, e_name_of(b))


	def assign_fa(append, tgt, func, src):
	    tag = func[0]
	    if tag == 'getattr':
		name = func[1]
		append('%s = %s'%(tgt, self.getattr_code(src, name)))
	    elif tag in ('callattr', 'operator', 'inplace_operator', 'function_operator',
			 'reverse_operator', 'mapping', 'setitem', 'delitem', 'getitem'):
		op = func[1]
		opname = op.get_op_name()
		args = func[2]


		sus = args.get_setups_for_preconditions()
		if sus is not None:
		    for su in sus:
			append('%s.%s(%s)'%(src, su.get_name(), su.get_args()))
		else:
		    self.error('Could not find postcondition to satisfy precondition for %r.'%(
			op.get_op_id_name()),
			       exception=None)

		eqs = []

		for eq in op.find_equations():
		    pres = eq.find_aspects('precondition')
		    posts = eq.find_aspects('postcondition')
		    # xxx late checking but will do for now I may relax it anyway
		    if len(pres) != 1:
			if pres:
			    node = pres[0].src.node
			else:
			    node = eq.src.node
			self.error('Exactly one precondition currently allowed in equation.',
				   node,
				   exception=None)
		    if len(posts) < 1:
			self.error('At least one postcondition currently required in equation.',
				   eq.src.node,
				   exception=None)
		    if pres:
			pre = pres[0]
			ce = self.parent.get_condition_expr(pre, args, op, src, None)
			if ce is None:
			    # This 'cant happen' anymore, checked here 'just in case'...
			    raise SyntaxError, 'Undefined condition: %r'%pre
			prename = 'pre_%d'%len(eqs)
			eqs.append((prename, posts))
			append('%s = %s'%(prename, ce))
		    
		    

		if tag == 'callattr':
		    append('%s = %s(%s)'%(tgt, self.getattr_code(src, opname), args))
		elif tag == 'operator':
		    append('%s = %s %s %s'%(tgt, src, opname, args))
		elif tag == 'inplace_operator':
		    append('%s = %s'%(tgt, src))
		    append('%s %s %s'%(tgt, opname, args))
		elif tag == 'reverse_operator':
		    append('%s = %s %s %s'%(tgt, args, opname, src))
		elif tag == 'function_operator':
		    argstr = str(args)
		    if argstr:
			argstr = ', ' + argstr
		    append('%s = %s(%s%s)'%(tgt, opname, src, argstr))
		elif tag == 'mapping':
		    append('%s = %s(%s)'%(tgt, src, args))
		elif tag == 'getitem':
		    # Number of args >=1, has been checked.
		    append('%s = %s[%s]'%(tgt, src, args))
		elif tag == 'delitem':
		    # Number of args >=1, has been checked.
		    append('del %s[%s]'%(src, args))
		elif tag == 'setitem':
		    # Number of args >=2, has been checked.
		    append('%s[%s] = %s'%(src, ', '.join(args.negs[:-1]), args.negs[-1]))
		else:
		    assert 0

		posts = args.get_postconditions()
		for po in posts:
		    ce = self.parent.get_condition_expr(po, args, op, src, tgt)
		    if ce is None:
			continue

		    append('assert %s, %r'%(
			ce,
			'Failed postcondition: %r'%po.cond_expr,
			))


		for (prename, posts) in eqs:
		    for post in posts:
			ce = self.parent.get_condition_expr(post, args, op, src, tgt)
			if ce is None:
			    # This 'cant happen' anymore, checked here 'just in case'...
			    raise SyntaxError, 'Undefined condition: %r'%post
			message = 'Failed postcondition equality: %r'%post
			append('assert %s == %s, %r'%(
			    prename, ce, message))

	    else:
		assert 0
	    

	pylines = []


	class ArrowTree:
	    def __init__(self, pre):
		self.pre = pre
		self.children = []

	    def __str__(self):
		if self.children:
		    chdrn = ', chdrn = %s'%self.children
		else:
		    chdrn = ''

		return 'AT(pre = %s%s)'%(self.pre, chdrn)

	    __repr__ = __str__

	ats = {}

	def at_of_pre(pre):
	    at = ats.get(pre)
	    if at is None:
		at = ArrowTree(pre)
		ats[pre] = at
		if pre:
		    at_of_pre(pre[:-1]).children.append(at)
	    return at

	def trav(t):
	    subs = []
	    src = t.pre
	    src_name = e_name_of(src)
	    for ch in t.children:
		try:
		    ar = ch.pre[-1]
		    func = fa[ar]
		    tgt = ch.pre
		    tag = func[0]
		    if tag == 'subkind':
			e_names[ch.pre] = src_name
			trav(ch)
		    elif tag == 'superkind':
			subs.append((func, ch))
		    else:
			assign_fa(pylines.append, e_name_of(tgt), func, src_name)
			trav(ch)
		except self.mod.ReportedError:
		    pass
	    if subs:
		subs.sort()
		indents = 0
		for func, ch in subs:
		    e_names[ch.pre] = src_name
		    pos = len(pylines)
		    pylines.append('try:')
		    pylines.append('<INDENT>')
		    trav(ch)
		    if pos+2 == len(pylines):
			pylines.pop()
			pylines.pop()
		    else:
			indents += 1
			pylines.append('<DEDENT>')
			pylines.append('except:')
			pylines.append('<INDENT>')

		if indents:
		    pylines.append('raise')
		pylines.extend(['<DEDENT>']*indents)
		

	alla = object_tester.get_all_arrows()
	init = at_of_pre(())
	for a in alla:
	    for i in range(1, len(a)+1):
		at_of_pre(a[:i])

	#print init
	#set_trace()
	#print ats

	trav(init)

	if not pylines:
	    self.warn('No tests generated for %r.'%self.kind.tgtfullname)
	    pylines=['pass']




	ap = self.parent.ap
	name = self.parent.get_test_name(self.kind)
	test_name = 'test_%s'%name
	ap('def %s(self, arg):'%test_name, '<NL>', '<INDENT>')

	for line in ex_setup + pylines:
	    ap(line)
	    if line not in ('<INDENT>', '<DEDENT>'):
		ap('<NL>')
	ap('<DEDENT>')
	ap('tests[%r] = %s'%(self.kind.tgtfullname, test_name), '<NL>')
	

class _GLUECLAMP_:

    _imports_ = (
	'_parent:SpecNodes',
	'_parent.SpecNodes:node_of_taci',
	'_parent:Main',
	'_parent.Main:ReportedError',
	'_root:doctest',
	'_root.StringIO:StringIO',
	'_root.sys:stdout',
	'_root:sys',
	'_root.traceback:format_exception',
	'_root.guppy.etc:KanExtension',
	'_root.guppy.etc:Cat',
	'_root:re',
	'_root:time',
	)

    # Reserved words found in Grammar of Python 2.3.3
    # Is there a list somewhere in some std lib?
    reserved_words = (
	'and',
	'assert',
	'break',
	'class',
	'continue',
	'def',
	'del',
	'elif',
	'else',
	'except',
	'exec',
	'finally',
	'for',
	'from',
	'global',
	'if',
	'import',
	'in',
	'is',
	'lambda',
	'not',
	'or',
	'pass',
	'print',
	'raise',
	'return',
	'try',
	'while',
	'yield',
	)
	

    def _get_is_identifier(self):
	m = self.re.compile('[_a-zA-Z][_a-zA-Z0-9]*').match
	rwd = dict([(rw, 1) for rw in self.reserved_words])
	return lambda x:m(x) and x not in rwd


    def doc2text(self, doc, node):
	d2h = Doc2Tester(self, doc, node)
	return d2h.get_tester()

    def doc2filer(self, doc, node, name, dir, opts, IO):
	text = self.doc2text(doc, node)
	path = IO.path.join(dir, '%s.py'%name)
	node = self.node_of_taci('write_file', path, [self.node_of_taci('text', text)])
	return node


