from migen.fhdl.structure import *
from migen.flow.actor import *
from migen.flow.transactions import *
from migen.sim.generic import PureSimulable

# Generators yield None or a tuple of Tokens.
# Tokens for Sink endpoints are pulled and the "value" field filled in.
# Tokens for Source endpoints are pushed according to their "value" field.
#
# NB: the possibility to push several tokens at once is important to interact
# with actors that only accept a group of tokens when all of them are available.
class TokenExchanger(PureSimulable):
	def __init__(self, generator, actor):
		self.generator = generator
		self.actor = actor
		self.active = set()
		self.busy = True
		self.done = False

	def _process_transactions(self, s):
		completed = set()
		for token in self.active:
			ep = self.actor.endpoints[token.endpoint]
			if isinstance(ep, Sink):
				if s.rd(ep.ack):
					if s.rd(ep.stb):
						token.value = s.multiread(ep.token)
						completed.add(token)
						s.wr(ep.ack, 0)
				else:
					s.wr(ep.ack, 1)
			elif isinstance(ep, Source):
				if s.rd(ep.stb):
					if s.rd(ep.ack):
						completed.add(token)
						s.wr(ep.stb, 0)
				else:
					s.wr(ep.stb, 1)
					s.multiwrite(ep.token, token.value)
			else:
				raise TypeError
		self.active -= completed
		if not self.active:
			self.busy = True
	
	def _next_transactions(self):
		try:
			transactions = next(self.generator)
		except StopIteration:
			self.done = True
			self.busy = False
			transactions = None
		if isinstance(transactions, Token):
			self.active = {transactions}
		elif isinstance(transactions, tuple) \
			or isinstance(transactions, list) \
			or isinstance(transactions, set):
			self.active = set(transactions)
		elif transactions is None:
			self.active = set()
		else:
			raise TypeError
		if self.active and all(transaction.idle_wait for transaction in self.active):
			self.busy = False
	
	def do_simulation(self, s):
		if not self.done:
			if not self.active:
				self._next_transactions()
			if self.active:
				self._process_transactions(s)

class SimActor(Actor):
	def __init__(self, generator, *endpoint_descriptions, **misc):
		Actor.__init__(self, *endpoint_descriptions, **misc)
		self.token_exchanger = TokenExchanger(generator, self)
	
	def update_busy(self, s):
		s.wr(self.busy, self.token_exchanger.busy)
	
	def get_fragment(self):
		return self.token_exchanger.get_fragment() + Fragment(sim=[self.update_busy])

class Dumper(SimActor):
	def __init__(self, layout, prefix=""):
		def dumper_gen():
			while True:
				t = Token("result")
				yield t
				if len(t.value) > 1:
					s = str(t.value)
				else:
					s = str(list(t.value.values())[0])
				print(prefix + s)
		SimActor.__init__(self, dumper_gen(),
			("result", Sink, layout))