import os, sys
newPath = os.path.abspath('../..')
if newPath not in sys.path:
	sys.path.insert(1, newPath)

from MiscUtils.NamedValueAccess import \
	NamedValueAccessError, valueForKey, valueForName, NamedValueAccess
from MiscUtils import AbstractError, NoDefault, unittest
from UserDict import UserDict
import types


class tRoot:
	pass

class t1(tRoot):
	def foo(self):
		return 1

class t2(tRoot):
	def _foo(self):
		return 1

class t3(tRoot):
	def foo(self):
		return 1
	def _foo(self):
		return 0

class t4(tRoot):
	def foo(self):
		return 1
	def __init__(self):
		self._foo = 0

class t5(tRoot):
	def __init__(self):
		self.foo = 0
	def _foo(self):
		return 1

class t6(tRoot):
	def __init__(self):
		self.foo = 1
		self._foo = 0

class t7(tRoot):
	def valueForKey(self, key, default):
		if key=='foo':
			return 1
		elif key=='nextObject':
			return getattr(self, 'nextObject')
		else:
			if default is NoDefault:
				raise NamedValueAccessError, key
			else:
				return default

class t8(tRoot):
	def valueForUnknownKey(self, key, default):
		if key=='foo':
			return 1
		else:
			if default is NoDefault:
				raise NamedValueAccessError, key
			else:
				return default

# Make a list of all the 't' classes which are used in testing
tClasses = []
for name in dir():
	if len(name)==2 and name[0]=='t':
		tClasses.append(globals()[name])

class NamedValueAccessTest(unittest.TestCase):
	"""
	This is the abstract root ancestor for all test case classes in this file.
	"""
	pass


class LookupTest(NamedValueAccessTest):
	"""
	This is an abstract super class for the test cases that cover the
	functions. Subclasses must implement self.lookup() and can make use
	of self.classes and self.objs.
	"""

	def setUp(self):
		self.setUpClasses()
		self.setUpObjects()

	def setUpClasses(self):
		self.classes = tClasses

	def setUpObjects(self):
		self.objs = map(lambda clazz: clazz(), self.classes)

	def lookup(self, obj, key, default=NoDefault):
		raise AbstractError, self.__class__

	def checkBasicAccess(self):
		"""
		Invoke the look up function with key 'foo', expecting 1 in return.
		Invoke the look up with 'bar', expected an exception.
		Invoke the look up with 'bar' and default 2, expecting 2.
		"""
		func = self.lookup
		for obj in self.objs:

			value = func(obj, 'foo')
			assert value==1, 'value = %r, obj = %r' % (value, obj)

			self.assertRaises(NamedValueAccessError, func, obj, 'bar')

			value = func(obj, 'bar', 2)
			assert value==2, 'value = %r, obj = %r' % (value, obj)

	def checkBasicAccessRepeated(self):
		"""
		Just repeat checkBasicAccess multiple times to check stability.
		"""
		for count in xrange(50):
			# Yes, it's safe to invoke this other particular test
			# multiple times without the usual setUp()/tearDown()
			# cycle
			self.checkBasicAccess()


class ValueForKeyTest(LookupTest):

	def lookup(self, obj, key, default=NoDefault):
		return valueForKey(obj, key, default)


class ValueForNameTest(LookupTest):

	def lookup(self, obj, key, default=NoDefault):
		return valueForName(obj, key, default)

	def checkNamedValueAccess(self):
		objs = self.objs

		# link the objects
		for i in range(len(objs)-1):
			objs[i].nextObject = objs[i+1]

		# test the links
		for i in range(len(objs)):
			name = 'nextObject.' * i + 'foo'
			assert self.lookup(objs[0], name)==1

	def checkDicts(self):
		# Basic dicts
		dict = { 'origin': {'x':1, 'y':2},  'size': {'width':3, 'height':4} }
		obj = self.objs[0]
		obj.rect = dict
		self._checkDicts(dict, obj)

		# User dicts
		dict = UserDict(dict)
		obj.rect = dict
		self._checkDicts(dict, obj)

	def _checkDicts(self, dict, obj):
		""" Used exclusively by checkDicts(). """
		assert self.lookup(dict, 'origin.x')==1
		assert self.lookup(obj, 'rect.origin.x')

		self.assertRaises(NamedValueAccessError, self.lookup, dict, 'bar')
		self.assertRaises(NamedValueAccessError, self.lookup, obj,  'bar')

		assert self.lookup(dict, 'bar', 2)==2
		assert self.lookup(obj, 'rect.bar', 2)==2


class MixInTest(NamedValueAccessTest):
	"""
	This test case is really just a utility to mix-in the
	NamedValueAccess so that the test classes (t1, t2, ...) inherit it.
	Run this test suite after the basic tests, but before the
	NamedValueAccess mix-in tests.
	"""

	def setUp(self):
		if NamedValueAccess not in tRoot.__bases__:
			tRoot.__bases__ = tRoot.__bases__ + (NamedValueAccess,)

	def checkNothing(self):
		pass


class MixInKeyTest(ValueForKeyTest):

	def lookup(self, obj, key, default=NoDefault):
		if type(obj) is not types.InstanceType:
			# just so non-obj cases pass
			return valueForKey(obj, key, default)
		else:
			# What we're really testing, the mix-in method:
			return obj.valueForKey(key, default)


class MixInNameTest(ValueForNameTest):

	def lookup(self, obj, key, default=NoDefault):
		if type(obj) is not types.InstanceType:
			# just so non-obj cases pass
			return valueForName(obj, key, default)
		else:
			# What we're really testing, the mix-in method:
			return obj.valueForName(key, default)


class MixInExtrasTest(MixInTest, NamedValueAccessTest):

	def checkValuesForNames(self):
		# def valuesForNames(self, keys, default=None, defaults=None, forgive=0)

		# set up structure: rect(attrs(origin(x, y), size(width, height)))
		rect = t1()
		origin = t1();  origin.x = 5;  origin.y = 6
		size = t1();  size.width = 43;  size.height = 87;
		attrs = {'origin': origin, 'size': size}
		rect.attrs = attrs

		# test integrity of structure and validity of valueForName()
		assert rect.valueForName('attrs') is attrs
		assert rect.valueForName('attrs.origin') is origin
		assert rect.valueForName('attrs.size') is size

		# test valuesForNames()
		notFound = 'notFound'
		assert rect.valuesForNames(['attrs', 'attrs.origin', 'attrs.size']) \
			== [attrs, origin, size]
		assert rect.valuesForNames(['attrs.dontFind', 'attrs', 'attrs.origin.dontFind'], default=notFound) \
			== [notFound, attrs, notFound]
		assert rect.valuesForNames(['attrs.dontFind', 'attrs', 'attrs.origin.dontFind'], defaults=[1, 2, 3]) \
			== [1, attrs, 3]
		assert rect.valuesForNames(['attrs.dontFind', 'attrs', 'attrs.origin.dontFind'], forgive=1) \
			== [attrs]


class WrapperTest(NamedValueAccessTest):
	"""
	This is a utility class that modifies LookupTest. After running this,
	run	all the LookupTests (e.g., all subclasses) as usual in order to
	test wrappers.
	"""

	def setUp(self):
		def setUpObjects(self):
			wrapper = NamedValueAccessWrapper
			self.objs = map(lambda wrapper=wrapper: clazz(), self.classes)
		LookupTest.setUpObjects = setUpObjects


def makeTestSuite():
	testClasses = [
		ValueForKeyTest,
		ValueForNameTest,
		MixInTest, # utility
			MixInKeyTest,
			MixInNameTest,
			MixInExtrasTest,
		WrapperTest, # utility
			MixInKeyTest,
			MixInNameTest,
			MixInExtrasTest,
	]
	make = unittest.makeSuite
	suites = [make(clazz, 'check') for clazz in testClasses]
	return unittest.TestSuite(suites)


def testUse():
	runner = unittest.TextTestRunner(stream=sys.stdout)
	unittest.main(defaultTest='makeTestSuite', testRunner=runner)


def usage():
	sys.stdout = sys.stderr
	print 'usage:'
	print '  TestNamedValueAccess.py use'
	print '  TestNamedValueAccess.py leaks <iterations>'
	print
	sys.exit(1)


if __name__=='__main__':
	if len(sys.argv)<2:
		testUse()
	elif sys.argv[1] in ['-h', '--help', 'help']:
		usage()
	elif sys.argv[1]=='use':
		testUse()
	elif sys.argv[1]=='leaks':
		testLeaks()
	else:
		usage()
