"""Virtual ports

Virtual ports (not to be confused with Virtual Ports) provide an
interface for sending Python objects (restricted to certain types) to
"virtual port numbers" on other nodes.  Nodes are usually networked
computers, represented by an IP and a port; however, it can also be
run in simulation mode where all of the nodes are on a local computer
and the network isn't actually used.  Virtual port numbers are just
like regular port numbers, except that everything is sent over a
single actual port, and the virtual port space is completely open for
application use.  Essentially, this is a very stripped-down RPC
mechanism.
"""

MIN_PORT = 1
MAX_PORT = sys.maxint

class Node(object):
    def __init__(self):
        raise NotImplementedError, \
              "Node's cannot be directly instantiated"

    def registerPort(self, port, cb):
        """Registers a callback for incoming calls on port.

        When a call comes in to this node on the given port, cb will
        be called with a Request object as its first argument, and the
        arguments of the call as its remaining arguments.

        A callback can be unregistered by passing None as the value of
        cb for the appropriate port.

        If this callback has already been registered on this port,
        nothing occurs.  If a different callback has already been
        registered on this port or this port is out of range,
        ValueError is raised.  If this object represents a remote
        node, then ports cannot be registered on it, so ValueError is
        raised.
        """
        
        raise NotImplementedError

    def sendCall(self, port, *args):
        """Sends a call to this node and returns the request ID.

        Sends args as a call on the given port to this node.  The
        corresponding callback function on the remote node will be
        invoked as a result of this.

        This returns a request ID that can be used for tracking
        replies.  This request ID is unique for every call sent to a
        particular node.

        If the call to the remote node fails due to a network error,
        the network error callback will be called.  While this uses
        UDP transport, and thus individual calls cannot produce a
        timeout at the network level, acknowledgements are tracked,
        and any timeouts resulting from this will produce a timeout
        error that appears to be coming from the underlying network.

        If an exception occurs on the remote end while executing a
        call, the local remote exception callback will be called.
        This includes if no callback is registered on the given port
        on the remote node.
        """
        
        raise NotImplementedError
    
    def registerNetErrorCB(self, cb):
        """Registers a callback function for network errors.

        The callback function should expect an instance of some
        subclass of socket.error.

        Pass None to unregister the network error callback.
        """

        raise NotImplementedError

    def registerRemoteExceptionCB(self, cb):
        """Registers a callback function for remote exception.

        The callback function should expect a Request object
        containing the source node and request ID of the call that
        generated the exception and two strings.  The first string
        will be the name of the exception class, and the second will
        be the repr of the exception value.  This is not really meant
        to be used as part of normal RPC behavior, but more as a way
        to detect possibly rogue nodes.  The proper way to deal with
        exception handling is to wrap RPC callbacks in try blocks and
        generate an appropriate response given any exception.

        Pass None to unregister the remote exception callback.
        """

        raise NotImplementedError

class Request(object):
    def __init__(self, src, dst, reqid, port, args):
        self.src, self.dst, self.reqid = src, dst, reqid
        self.port, self.args = port, args

class _LocalRealNode(Node):
    def __init__(self, addr):
        raise NotImplementedError

class _RemoteRealNode(Node):
    def __init__(self, addr):
        raise NotImplementedError

