"""Virtual ports simulator

"""

import vports

_sim_local_node = None
_sim_queue = []

_QUEUE_SIM_RPC = 0
_QUEUE_SIM_EXC = 1

class _SimulatedNode(Node):
    __slots__ = ["__ports", "__recb", "__id"]

    def __init__(self, id):
        self.__ports = {}
        self.__recb = None
        self.__id = id

    def __checkPerm(self,
                    err = "Can't do that to a remote simulated node"):
        if _sim_local_node not in (None, self):
            raise ValueError, err

    def registerPort(self, port, cb):
        # Make sure this is acting as the local node
        self.__checkPerm("Attempt to register port on a remote"
                         " simulated node")

        # Register this callback in the ports dictionary
        if port in self.__ports:
            if self.__ports[port] != cb:
                raise ValueError, \
                      "A callback is already registered on port %d" \
                      % port
        else:
            self.__ports[port] = cb

    def sendCall(self, port, *args):
        if _sim_local_node == None:
            raise RuntimeError, \
                  "Attempt to send a call when there is no" \
                  " currently simulated node"

        # XXX Copy args (except Nodes) to simulate serialization

        # Build request object
        reqid = _sim_local_node.__nextreqid
        _sim_local_node.__nextreqid += 1
        req = vports.Request(_sim_local_node, self, reqid, port, args)

        # Enqueue this call for the next scheduler run
        _sim_queue.append((_QUEUE_SIM_RPC, (req,)))

        return reqid

    def _dispatchCall(self, req):
        cb = self.__ports.get(req.port, None)
        if cb is None:
            _sim_queue.append((_QUEUE_SIM_EXC, 

    def registerNetErrorCB(self, cb):
        # That's nice.  Currently network errors are not simulated.
        self.__checkPerm("Attempt to register net error callback on a"
                         " remote simulated node")
        pass

    def registerRemoteExceptionCB(self, cb):
        # Keep the callback around
        self.__checkPerm("Attempt to register remote exception"
                         " callback on a remote simulated node")
        self.__recb = cb

    def __repr__(self):
        return "<simulated node %d>" % self.__id

__simulated_node_count = 0

def newNode():
    node = _SimulatedNode(__simulated_node_count)
    __simulated_node_count += 1
    return node

def simulate():
    while _sim_queue:
        t, d = _sim_queue.pop()
        if t == _QUEUE_SIM_RPC:
            req, = d
            _sim_local_node = req.dst
            req.dst._dispatchCall(req)
