from migen.util.misc import xdir
from migen.fhdl.std import *
from migen.flow.actor import *
from migen.flow.actor import _Endpoint
from migen.flow.transactions import *
from migen.actorlib.sim import TokenExchanger
from migen.bus import wishbone, memory
from migen.bus.transactions import *

class UnifiedIOObject(Module):
	def do_finalize(self):
		if self.get_dataflow():
			self.busy = Signal()
		self.specials += set(v for k, v in xdir(self, True) if isinstance(v, Memory))

	def get_dataflow(self):
		return dict((k, v) for k, v in xdir(self, True) if isinstance(v, _Endpoint))

	def get_buses(self):
		return dict((k, v) for k, v in xdir(self, True) if isinstance(v, (wishbone.Interface, Memory)))

(_WAIT_COMPLETE, _WAIT_POLL) = range(2)

class UnifiedIOSimulation(UnifiedIOObject):
	def __init__(self, generator):
		self.generator = generator

	def do_finalize(self):
		UnifiedIOObject.do_finalize(self)
		callers = []
		self.busname_to_caller_id = {}
		if self.get_dataflow():
			callers.append(TokenExchanger(self.dispatch_g(0), self))
		for k, v in self.get_buses().items():
			caller_id = len(callers)
			self.busname_to_caller_id[k] = caller_id
			g = self.dispatch_g(caller_id)
			if isinstance(v, wishbone.Interface):
				caller = wishbone.Initiator(g, v)
			elif isinstance(v, Memory):
				caller = memory.Initiator(g, v)
			callers.append(caller)
		self.submodules += callers

		self.dispatch_state = _WAIT_COMPLETE
		self.dispatch_caller = 0
		self.pending_transaction = None

	def identify_transaction(self, t):
		if isinstance(t, Token):
			return 0
		elif isinstance(t, TRead) or isinstance(t, TWrite):
			if t.busname is None:
				if len(self.busname_to_caller_id) != 1:
					raise TypeError
				else:
					return list(self.busname_to_caller_id.values())[0]
			else:
				return self.busname_to_caller_id[t.busname]
		else:
			raise TypeError

	def dispatch_g(self, caller_id):
		while True:
			if self.dispatch_state == _WAIT_COMPLETE and self.dispatch_caller == caller_id:
				transaction = next(self.generator)
				tr_cid = self.identify_transaction(transaction)
				self.dispatch_caller = tr_cid
				if tr_cid == caller_id:
					yield transaction
				else:
					self.pending_transaction = transaction
					self.dispatch_state = _WAIT_POLL
					yield None
			elif self.dispatch_state == _WAIT_POLL and self.dispatch_caller == caller_id:
				self.dispatch_state = _WAIT_COMPLETE
				yield self.pending_transaction
			else:
				yield None