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

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
		'''
		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].evaluer()

###########################################################################
    
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
		'''
		if self.__class__ is EntreeBinaire:
			raise TypeError('EntreeBinaire est une classe abstraite')
		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
	'''
	def __init__(entrees):
		if self.__class__ is PorteLogique:
			raise TypeError('PorteLogique est une classe abstraite')
		try:
			iter(entrees)
		except TypeError:
			raise
		self._entrees = tuple(entrees)

	def arite(self):
		'''
		Retourne l'arite de la porte logique
		'''
		raise TypeError('Non implemente')
		
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 implémentation 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
			
###########################################################################

if __name__ == '__main__':

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

	print	
	print 'test #2'
	entree = Faux()
	sortie = Non(entree)
	c = CircuitCombinatoire((entree,), (sortie,))
	print 'sortie:', c.sortie(0)
	assert(c.sortie(0) == 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)) for i in range(3)])
	assert(c.sortie(0) == True and c.sortie(1) == True and c.sortie(2) == 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)) for i in range(3)])
	assert(c.sortie(0) == True and c.sortie(1) == False and c.sortie(2) == 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)) for i in range(3)])
	assert(c.sortie(0) == True and c.sortie(1) == False and c.sortie(2) == 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)) for i in range(3)])
	assert(c.sortie(0) == False and c.sortie(1) == False and c.sortie(2) == 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)) for i in range(2)])
	assert(c.sortie(0) == True and c.sortie(1) == True)


