#!python
"""
<Program Name>
  baseaffix.r2py

<Author>
  Danny Y. Huang, yh1@cs.williams.edu
  Monzur Muhammad, monzum@cs.washington.edu

<Date Started>
  March 7th, 2011.
  Originally written by Danny on June 2010.

<Purpose>
  This is the core of a affix. The very base interface
  that every affix has to extend and implement specified
  functions. Note that this file itself is not a affix
  in itself but rather the skeleton of a affix.
"""

# Import all the exceptions.
dy_import_module_symbols("affix_exceptions.r2py")

# Import all the wrapper classes.
dy_import_module_symbols("affix_wrapper_lib.r2py")



class BaseAffix:


  def __init__(self, next_affix, optional_args=None):
    """
    <Purpose>
      Initialize this particular affix.

    <Arguments>
      next_affix - This is the affix component that resides beneath
          the current affix in the stack. This AFFIX component may
          be used appropriately or removed if the current AFFIX 
          chooses to remove it.

      optional_args - Any optional argument that this particular 
          affix takes.

    <Side Effects>
      None

    <Exceptions>
      None

    <Return>
      None
    """

    # Global variables that are used to keep the state of this affix.
    self.affix_context = {'optional_args' : None,
                         'next_affix' : None
                        }


    # Ensure that the reference to the affixstack that we get
    # is of the type BaseAffix.
    try:
      if next_affix:
        assert(isinstance(next_affix, BaseAffix))
    except AssertionError:
      err = "Bad arg provided. next_affix must be of type BaseAffix."
      err += " Type provided: '%s'" % str(type(next_affix))
      raise AffixArgumentError(err)

    self.affix_context['optional_args'] = optional_args
    
    # I am going to do a copy just to make sure we don't have any 
    # reference to it.
    self.affix_context['next_affix'] = next_affix



  
  # ===========================================================================
  # Public methods that the child classes must override.
  # ===========================================================================


  def copy(self):
    """
    Returns a copy of the current instance. Be sure to set internal states to be
    copied in the new object. An example would be:

    class MyAffix(BaseAffix):
      def copy(self):
        if self.affix_context['next_affix']:
          next_affix = self.affix_context['next_affix'].copy()
        else:
          next_affix = None
		
        mycopy = MyAffix(next_affix, self.affix_context['optional_args']
        mycopy._internalstate = self._internalstate
        return mycopy

    """
    raise AffixError("The copy() method is not implemented.")



  def get_advertisement_string(self):
    """
    Returns the string used for advertisement of this affix. For example:

    class MyAffix(BaseAffix):
      def get_advertisement_string(self):
        return '(MyAffix,' + str(self._some_shared_state) + ')' + self.peek().get_advertisement_string()

    If the subclass affix is not meant to be public, then simply return an empty
    string.

    """
    raise AffixError("The get_advertisement_string() method is not implemented.")  




  # =========================================================================
  # These are public methods that are used to view or modify the AFFIX stack.
  # =========================================================================
 
  def peek(self):
    """
    <Purpose>
      Peek into the affix stack and return the next layer
      of affix. If the affix stack is empty, then we will 
      return the RepyNetworkApi object, which is used to
      call the original repy network calls.

    <Arguments>
      None

    <Side Effects>
      None

    <Exceptions>
      None.

    <Return>
      Next layer of affix or an object that encompasses the original
      repy network api.
    """
    
    next_layer = self.affix_context['next_affix']
    
    if not next_layer:
      # This is the case when the affix stack is empty.
      # So we return a wrapper object which will allow
      # us to use the original repy network api calls.
      next_layer = RepyNetworkApiWrapper()

    return next_layer




  def pop(self):
    """
    <Purpose>
      Remove and return the nex affix layer from the AFFIX stack.
      If stack is empty, it raises an error

    <Arguments>
      None

    <Side Effects>
      Stack gets modified.

    <Exception>
      AffixStackError - raised if the affix stack is empty.

    <Return>
      The next AFFIX object in the stack.
    """

    # We make a reference to the next AFFIX that lies in the stack
    # and point our next_affix pointer to the next_affix of the 
    # next_affix. Example: 
    # Originally : A->B->C->Null
    # Finally: A->C->Null,  B->Null
    next_affix = self.peek()

    # If we have reached the bottom of the stack and the 
    # next layer is the RepyNetworkApiWrapper, then we
    # return as we don't need to pop anything.
    if isinstance(next_affix, RepyNetworkApiWrapper):
      raise AffixStackError("The stack is empty. We cannot pop() further Affixes.")

    next_next_affix = next_affix.peek()
    self.affix_context['next_affix'] = next_next_affix

    # We point the next AFFIX for the return object to null so
    # the popped AFFIX object no longer has any reference to any
    # of the AFFIXs in the stack.
    next_affix.affix_context['next_affix'] = None

    return next_affix
    



  def push(self, new_affix_object):
    """
    <Purpose>
      Add the new_affix_object underneath the current AFFIX object.

    <Arguments>
      new_affix_object - the affix that needs to be added to the stack.

    <Side Effects>
      The stack of the current top affix gets modified

    <Exceptions>
      AffixStackError - raised if unable to add affix_object to stack.
    
    <Return>
      None
    """
    
    # We point the new_affix_object to our next_affix and then point
    # ourself to the new_affix_object.
    next_affix_object = self.peek()

    if isinstance(next_affix_object, RepyNetworkApiWrapper):
      next_affix_object = None

    new_affix_object.affix_context['next_affix'] = next_affix_object
    self.affix_context['next_affix'] = new_affix_object




  # ============================================================================
  # Public methods that directly interface with the application. The methods may
  # be overwritten by the affix objects that inherit the base affix. If the methods
  # are not overwritten, then these methods will be used in the affix that inherits
  # them.
  # ===========================================================================

  def sendmessage(self, destip, destport, message, localip, localport):
    return self.peek().sendmessage(destip, destport, message, localip, localport)



  def listenformessage(self, localip, localport):
    next_layer_socket = self.peek().listenformessage(localip, localport)
    return AffixUDPServerSocket(next_layer_socket, self)



  def openconnection(self, destip, destport, localip, localport, timeout):
    # We make a copy of ourselves before we do anything as we may have multiple
    # openconnection calls that return multiple real sockets.
    #this_affix_copy = self.copy()
    next_sockobj = self.peek().openconnection(destip, destport, localip, localport, timeout)
    
    return AffixSocket(next_sockobj, self)



  def listenforconnection(self, localip, localport):
    # TODO: Not sure if we have to make a copy of self before retrieving
    # the next layer.
    next_layer_socket = self.peek().listenforconnection(localip, localport)
    return AffixTCPServerSocket(next_layer_socket, self)



  def socket_close(self, socket):
    return self.peek().socket_close(socket)



  def socket_send(self, socket, msg):
    return self.peek().socket_send(socket, msg)



  def socket_recv(self, socket, bytes):
    return self.peek().socket_recv(socket, bytes)



  def tcpserversocket_getconnection(self, tcpserversocket):
    # We make a copy of ourselves before doing a getconnection
    # as this function may be invoked multiple times and each
    # individual socket may each modify the affix stack 
    # below it differently. 
    this_affix_copy = self.copy()
    (remote_ip, remote_port, repy_socket) = this_affix_copy.peek().tcpserversocket_getconnection(tcpserversocket)

    return (remote_ip, remote_port, AffixSocket(repy_socket, self))



  def tcpserversocket_close(self, tcpserversocket):
    return self.peek().tcpserversocket_close(tcpserversocket)



  def udpserversocket_getmessage(self, udpserversocket):
    return self.copy().peek().udpserversocket_getmessage(udpserversocket)



  def udpserversocket_close(self, udpserversocket):
    return self.peek().udpserversocket_close(udpserversocket)


  def __str__(self):
    return '(%s)' % repr(self).replace(' instance at', '') + str(self.peek())
