#!/usr/bin/env python2

import unittest
from pyvap import Store, UnsolvableError, UndeterminedError, Maybe
from pyvap.constraints import Subtract
import pyvap.types

class Common(unittest.TestCase):
	def setUp(self):
		self.store = Store()
		self.a = self.store.IntVar('a')
		self.b = self.store.IntVar('b')
		self.constraint = Subtract(self.a, self.b)
		self.out = self.constraint.result
		self.out.name = 'out'

class TestBasics(Common):
	def test_type(self):
		self.assertIs(type(self.out), pyvap.types.IntVar)

	def test_attributes(self):
		self.assertEqual(self.constraint.operands, (self.a, self.b))
		self.assertIs(self.constraint.operands[0], self.a)
		self.assertIs(self.constraint.operands[1], self.b)

	def test_limits(self):
		default = self.store.IntVar()
		self.assertEqual(self.a.mini,   default.mini)
		self.assertEqual(self.a.maxi,   default.maxi)
		self.assertEqual(self.b.mini,   default.mini)
		self.assertEqual(self.b.maxi,   default.maxi)
		self.assertEqual(self.out.mini, default.mini)
		self.assertEqual(self.out.maxi, default.maxi)

	def test_different_stores_raises(self):
		extra = Store().IntVar('extra')
		with self.assertRaisesRegexp(ValueError, 'Different stores'):
			Subtract(self.a, extra)
		with self.assertRaisesRegexp(ValueError, 'Different stores'):
			Subtract(extra, self.a)

	def test_operator(self):
		out = self.a - self.b
		self.a.value = 10
		self.b.value = 7
		self.assertEqual(out.value, 3)

	def test_exact(self):
		for aval in xrange(-5, 5):
			for bval in xrange(-5, 5):
				outval = aval - bval

				# first one way...
				self.store._push()
				self.a.value = aval
				self.b.value = bval
				self.assertEqual(self.out.value, outval)
				self.store._pop()

				# then another.
				self.store._push()
				self.b.value = bval
				self.a.value = aval
				self.assertEqual(self.out.value, outval)
				self.store._pop()

	def test_raises(self):
		# an UnsolvableError must ALWAYS be raised when all values are
		# determined without fulfilling the constraint.
		for aval in xrange(-2, 2):
			for bval in xrange(-2, 2):
				for oval in (x for x in xrange(-2, 2) if x != aval - bval):
					self.store._push()
					self.a.value = aval
					self.b.value = bval
					with self.assertRaises(UnsolvableError):
						self.out.value = oval
					self.store._pop()

					self.store._push()
					self.out.value = oval
					with self.assertRaises(UnsolvableError):
						self.b.value = bval
						self.a.value = aval
					self.store._pop()

					self.store._push()
					self.out.value = oval
					with self.assertRaises(UnsolvableError):
						self.a.value = aval
						self.b.value = bval
					self.store._pop()

class TestRanges(Common):

	def test_both_undetermined(self):
		self.a.shrink(2,5)
		self.b.shrink(8,10)
		self.assertEqual(self.out.mini, -8)
		self.assertEqual(self.out.maxi, -3)
		self.assertEqual(len(self.out), 6)

	def test_a_undetermined(self):
		self.a.shrink(10,20)
		self.b.value = 5
		self.assertEqual(self.out.mini,  5)
		self.assertEqual(self.out.maxi, 15)
		self.assertEqual(len(self.out), 11)

	def test_b_undetermined(self):
		self.a.value = 5
		self.b.shrink(10,20)
		self.assertEqual(self.out.mini, -15)
		self.assertEqual(self.out.maxi,  -5)
		self.assertEqual(len(self.out),  11)

	def test_negative(self):
		self.a.value = -50
		self.b.shrink(40,60)
		self.assertEqual(self.out.mini, -110)
		self.assertEqual(self.out.maxi,  -90)
		self.assertEqual(len(self.out),   21)

	def test_split_a(self):
		self.a.shrink(-10, 10)
		self.a.exclude(-5, 5)
		self.b.shrink(5,15)
		self.assertEqual(self.out.mini, -25)
		self.assertEqual(self.out.maxi,   5)
		self.assertEqual(len(self.out),  31)

	def test_split_out(self):
		self.a.shrink(0,5)
		self.b.shrink(-11, 15)
		self.b.exclude(-10, 14)
		self.assertEqual(self.out.mini, -15)
		self.assertEqual(self.out.maxi,  16)
		expected = [-15, -14, -13, -12, -11, -10, 10, 11, 12, 13, 14, 15, 16]
		for ex in expected:
			self.assertIn(ex, self.out)
		# TODO: it would be nice if this worked:
		#self.assertEqual(len(self.out), len(expected))

class TestReverseRanges(Common):

	def test_both_undetermined(self):
		self.a.shrink(10,20)
		self.out.shrink(11, 31)
		self.assertEqual(self.b.mini, -21)
		self.assertEqual(self.b.maxi,   9)
		self.assertEqual(len(self.b),  31)

	def test_out_undetermined(self):
		self.out.shrink(-10, -5)
		self.b.value = 11
		self.assertEqual(self.a.mini, 1)
		self.assertEqual(self.a.maxi, 6)
		self.assertEqual(len(self.a), 6)

	def test_split_out(self):
		self.b.shrink(0, 10)
		self.out.shrink(0, 10)
		self.out.exclude(3, 8)
		self.assertEqual(self.a.mini,   0)
		self.assertEqual(self.a.maxi,  20)
		self.assertEqual(len(self.a),  21)

	def test_split_b(self):
		self.a.shrink(5,10)
		self.out.shrink(0, 20)
		self.out.exclude(2, 19)
		self.assertEqual(self.b.mini, -15)
		self.assertEqual(self.b.maxi,  10)
		expected = [-15, -14, -13, -12, -11, -10, 4, 5, 6, 7, 8, 9, 10]
		for ex in expected:
			self.assertIn(ex, self.b)
		# TODO: it would be nice if this worked:
		#self.assertEqual(len(self.b), len(expected))

if __name__ == '__main__':
	unittest.main()
