###########################################################################

class CircuitCombinatoire(object):
	'''
	Representation generique des circuits combinatoire
	'''
	def __init__(self, entrees, sorties):
		'''
		Initialise un circuit combinatoire a partir d'une liste d'entrees et d'une liste de sorties
		'''
		if self.__class__ is EntreeBinaire:
			raise TypeError('EntreeBinaire est une classe abstraite')
		try:
			# les entrees et les sorties doivent etre iterables
			iter(entrees)
			iter(sorties)
		except TypeError:
			raise
		self._entrees = tuple(entrees)
		self._sorties = tuple(sorties)

	def sortie(self, i):
		'''
		Retourne la i-eme sortie du circuit combinatoire
		'''
		return self._sorties[i]

###########################################################################

class EntreeBinaire(object):
	'''
	Classe de base pour les entrees binaires.
	'''
	def __init__(self, valeur):
		'''
		Initialise cette entree binaire a partir d'une valeur donnee binaire
		'''
		self._valeur = valeur

	def evaluer(self):
		'''
		Retourne la valeur binaire de cette entree
		'''
		return self._valeur

class Faux(EntreeBinaire):
	'''
	Entree constante Faux
	'''
	def __init__(self):
		super(Faux, self).__init__(False)

class Vrai(EntreeBinaire):
	'''
	Entree constante Vrai
	'''
	def __init__(self):
		super(Vrai, self).__init__(True)

###########################################################################

class PorteLogique(object):
	'''
	Classe de base (vide) pour les portes logiques
	'''
	pass

class Non(PorteLogique):
	'''
	Porte logique (unaire) Non
	'''
	def __init__(self, entree):
		'''
		Initialisation de la porte logique unaire
		'''
		self._entree = entree

	def evaluer(self):
		'''
		Evaluation: negation de l'entree binaire
		'''
		return not self._entree.evaluer()

class PorteLogiqueBinaire(PorteLogique):
	'''
	Classe de base (abstraite) pour les portes logiques binaires
	'''
	def __init__(self, entree_1, entree_2):
		'''
		Initialisation de la porte logique binaire
		'''
		if self.__class__ is PorteLogiqueBinaire:
			raise TypeError('PorteLogiqueBinaire est une classe abstraite')
		self._entree_1 = entree_1
		self._entree_2 = entree_2

class Ou(PorteLogiqueBinaire):
	'''
	Porte logique (binaire) Ou
	'''
	def evaluer(self):
		'''
		Evaluation de la porte logique Ou
		'''
		return self._entree_1.evaluer() or self._entree_2.evaluer()

class Et(PorteLogiqueBinaire):
	'''
	Porte logique (binaire) Et
	'''
	def evaluer(self):
		'''
		Evaluation de la porte logique Et
		'''
		return self._entree_1.evaluer() and self._entree_2.evaluer()

class Diff(PorteLogiqueBinaire):
	'''
	Porte logique (binaire) Diff
	'''
	def evaluer(self):
		'''
		Evaluation de la porte logique Diff: retourne vrai ssi e1 et e2 sont differents
		'''
		return self._entree_1.evaluer() != self._entree_2.evaluer()

class NonEt(PorteLogiqueBinaire):
	'''
	Porte logique (binaire) NonEt

	Une implementation alternative serait:
	def NonEt(e1, e2): return Non(Et(e1, e2))
	'''
	def evaluer(self):
		'''
		Evaluation de la porte logique Diff: retourne vrai ssi e1 et e2 sont differents
		'''
		return not(self._entree_1.evaluer() and self._entree_2.evaluer())

class PorteLogiqueTernaire(PorteLogique):
	'''
	Classe de base (abstraite) pour les portes logiques binaires
	'''
	def __init__(self, entree_1, entree_2, entree_3):
		'''
		Initialisation de la porte logique ternaire
		'''
		if self.__class__ is PorteLogiqueTernaire:
			raise TypeError('PorteLogiqueTernaire est une classe abstraite')
		self._entree_1 = entree_1
		self._entree_2 = entree_2
		self._entree_3 = entree_3

class Mux(PorteLogique):
	'''
	Porte Mux (3 entrees)
	'''
	def evaluer(self):
		'''
		Evaluation de la porte logique: si e3 est vrai alors e2 sinon e1
		'''
		e1 = self._entree_1.evaluer()
		e2 = self._entree_2.evaluer()
		e3 = self._entree_3.evaluer()
		if e3:
			return e2
		else:
			return e1

###########################################################################

def DemiAdditionneurUnBit(e1, e2):
	addition = Diff(e1, e2)
	retenu = Et(e1, e2)
	return CircuitCombinatoire((e1, e2), (addition, retenu))

def AdditionneurCascadable(cin, e1, e2):
	demi1 = DemiAdditionneurUnBit(e1, e2)
	demi2 = DemiAdditionneurUnBit(cin, demi1.sortie(0))
	ou_retenu = Ou(demi1.sortie(1), demi2.sortie(1))
	return CircuitCombinatoire((cin, e1, e2), (demi2.sortie(0), ou_retenu))

###########################################################################

if __name__ == '__main__':

	print 'test #1'
	entree = Vrai()
	sortie = Non(entree)
	c = CircuitCombinatoire((entree,), (sortie,))
	print 'sortie:', c.sortie(0).evaluer()
	assert(c.sortie(0).evaluer() == False)

	print	
	print 'test #2'
	entree = Faux()
	sortie = Non(entree)
	c = CircuitCombinatoire((entree,), (sortie,))
	print 'sortie:', c.sortie(0).evaluer()
	assert(c.sortie(0).evaluer() == True)

	print
	print 'test #3'
	entrees = (Vrai(), Vrai())
	c = CircuitCombinatoire(entrees, 
				(Ou(entrees[0], entrees[1]), 
				  Et(entrees[0], entrees[1]), 
				  Diff(entrees[0], entrees[1])))
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(3)])
	assert(c.sortie(0).evaluer() == True and c.sortie(1).evaluer() == True and c.sortie(2).evaluer() == False)

	print
	print 'test #4'
	entrees = (Vrai(), Faux())
	c = CircuitCombinatoire(entrees, 
				(Ou(entrees[0], entrees[1]), 
				  Et(entrees[0], entrees[1]), 
				  Diff(entrees[0], entrees[1])))
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(3)])
	assert(c.sortie(0).evaluer() == True and c.sortie(1).evaluer() == False and c.sortie(2).evaluer() == True)

	print
	print 'test #5'
	entrees = (Faux(), Vrai())
	c = CircuitCombinatoire(entrees, 
				(Ou(entrees[0], entrees[1]), 
				  Et(entrees[0], entrees[1]), 
				  Diff(entrees[0], entrees[1])))
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(3)])
	assert(c.sortie(0).evaluer() == True and c.sortie(1).evaluer() == False and c.sortie(2).evaluer() == True)

	print
	print 'test #6'
	entrees = (Faux(), Faux())
	c = CircuitCombinatoire(entrees, 
				(Ou(entrees[0], entrees[1]), 
				  Et(entrees[0], entrees[1]), 
				  Diff(entrees[0], entrees[1])))
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(3)])
	assert(c.sortie(0).evaluer() == False and c.sortie(1).evaluer() == False and c.sortie(2).evaluer() == False)

	print
	print 'test #7'
	V, F = (Vrai(), Faux())
	c = CircuitCombinatoire((V, F), 
				(Ou(V, Non(F)), 
				  Et(Et(Ou(V, F), Non(Diff(V, Non(F)))), V)))
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	assert(c.sortie(0).evaluer() == True and c.sortie(1).evaluer() == True)

	print
	c = AdditionneurCascadable(V, V, V)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(V, V, F)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(V, F, V)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(F, V, V)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(V, F, F)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(F, V, F)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(F, F, V)
	print 'sortie:', ', '.join([str(c.sortie(i).evaluer()) for i in range(2)])
	c = AdditionneurCascadable(F, F, F)

		
