#!python
"""
<Purpose>
  The Legacy Affix is used for allowing a legacy client to connect
  to a affix enabled server. The Legacy Affix opens up two tcpserversocket
  when listenforconnection is called in order for both affix enabled client
  and legacy clients to connect to the server.
"""

_DEBUG_MODE = False

dy_import_module_symbols("baseaffix.r2py")
dy_import_module_symbols("affix_stack.r2py")


class LegacyAffixTCPServerSocket():
  """
  This is a wrapper TCPServerSocket for the legacy affix.
  """

  def __init__(self, tcpserversocket_dict, affix_object):
    self.tcpserversocket_dict = tcpserversocket_dict
    self.affix_object = affix_object

  def getconnection(self):
    """
    <Purpose>
      We call getconnection first on the affix_tcpsocket, and if
      that fails we call getconnection for the repy_tcpsocket.
    
    <Side Effect>
      The affix tcpserversocket always gets called first, so clients
      that are affix enabled will get serviced first.
      MMM: Should we alternate between the two?
    """

    # If we were able to create the affix_tcpsocket, then we attempt to call
    # getconnection() on the affix tcp server socket first. If we were unable 
    # to create it or get a SocketWouldBlockError, we default to the basic
    # repy getconnection() call. The reason for this is to ensure that even
    # if the affixstack breaks down, we are able to revert to the default repy
    # connection.
    if self.tcpserversocket_dict['affix_tcpsocket']:
      try:
        rip, rport, sockobj = self.tcpserversocket_dict['affix_tcpsocket'].getconnection()
        return (rip, rport, AffixSocket(sockobj, self.affix_object)) 
      except SocketWouldBlockError:
        pass

    return self.tcpserversocket_dict['repy_tcpsocket'].getconnection()


  def close(self):
    self.tcpserversocket_dict['affix_tcpsocket'].close()
    return self.tcpserversocket_dict['repy_tcpsocket'].close()





class LegacyAffix(BaseAffix):
  """
  A affix that does nothing (no-operation). For debug purpose only.

  """

  def __init__(self, next_affix=None, optional_args=None):
    """
    <Purpose>
      Initialize the LegacyAffix with the appropriate affix stack 
      underneath it.

    <Arguments>
      affix_stack - the affix stack that will be initially beneath
          us. If optional_args is provided then it will be used
          to create the affix stacks that will be used, but this
          will be the default affix stack.

      optional_args - If any optional args is provided, then it will
          be used to create affix stacks.
          
    <Side Effects>
      Two tcpserversocket or two udpserversockets are created for 
      each call.
      
    <Exceptions>
      AffixArgumentError will be raised if the arguments provided
      is of not the right form or are incorrect.

      Assertion error if optional args is not type of list.

    <Return>
      None
    """

    if optional_args:
      assert(isinstance(optional_args, list)), "Bad optional args. Optional arg must be a list."
      assert(len(optional_args) == 4), "Bad optional args. Must provide tcp/udp port numbers and AFFIX stack for the legacy affix to listen on."

      try:
        self.server_name = optional_args[0]
        self.tcp_listenport = int(optional_args[1])
        self.udp_listenport = int(optional_args[2])
        self.affix_string = optional_args[3]
      except ValueError:
        raise AffixInternalError("TCP or UDP listenport provided is not of type int.")

                                        
    BaseAffix.__init__(self, next_affix, optional_args)




  # ====================== TCP Connection ===========================
  def listenforconnection(self, localip, localport):
    """
    <Purpose>
      The purpose of this call is to open up two different tcp
      server socket. One of the server socket will be affix enabled
      and will call listenforconnection for the next affix on the stack
      while the other server socket will be a pure repy server socket.

    <Exception>
      The Repy API exceptions will be raised in case of failure. List
      of exceptions can be found here: https://seattle.cs.washington.edu/wiki/FutureRepyAPI
     
    <Return>
      The call will return a LegacyAffixTCPServerSocket object.
    """

    # We build a new stack for the branch that accepts AFFIX connections.
    self.affix_stack_branch = AffixStack(self.affix_string, self.server_name)

    tcpserversocket_dict = {}

    tcpserversocket_dict['affix_tcpsocket'] = self.affix_stack_branch.peek().listenforconnection(localip, self.tcp_listenport)

    tcpserversocket_dict['repy_tcpsocket'] = listenforconnection(gethostbyname(localip), localport)

    if _DEBUG_MODE:
      log("Opened up two listening sockets with Legacy Affix on '%s:%d'\n" % (localip, localport))

    return LegacyAffixTCPServerSocket(tcpserversocket_dict, self)




  def openconnection(self, destip, destport, localip, localport, timeout):
    """
    <Purpose>
      The purpose of this openconnection call is to first try and connect
      to the affix stack of the server. If that fails, it will default to
      the repy openforconnection call.

    <Exception>
      The default Repy exceptions are raised. The list of exceptioncs can
      be found here: https://seattle.cs.washington.edu/wiki/FutureRepyAPI
    """

    # First try to open a connection using our affix stack. If we get a ConnectionRefusedError
    # or a DuplicateTupleError, then we default to the repy openconnection.
    # We have already created a new stack for the Legacy AFFIX branch and if
    # we did a proper lookup in the root CoordinationAffix, the new stack
    # 'should' only consist of a new CoordinationAffix. Thus we pass in
    # the server name and the tcp port that the Legacy AFFIX branch on the
    # server side is listening on.

    # We build a new stack for the branch before doing openconnection.
    self.affix_stack_branch = AffixStack(self.affix_string, self.server_name)
    try:
      if _DEBUG_MODE:
        log("LegacyAffix is opening connection to '%s:%d'\n" % (self.server_name, self.tcp_listenport))

      sockobj = self.affix_stack_branch.peek().openconnection(self.server_name, self.tcp_listenport, localip, localport, timeout)
      return AffixSocket(sockobj, self)
    except (DuplicateTupleError, ConnectionRefusedError):
      pass
    
    # If we failed to use the Legacy AFFIX branch, we try a normal openconnection
    # and try to connect to the legacy branch that is not using AFFIX.
    return openconnection(gethostbyname(destip), destport, gethostbyname(localip), localport, timeout)
      



  def copy(self):
    if self.affix_context['next_affix']:
      next_affix_copy = self.affix_context['next_affix'].copy()
    else:
      next_affix_copy = None


    return LegacyAffix(next_affix_copy, self.affix_context['optional_args'])
    


  
  def get_advertisement_string(self):
    return '(LegacyAffix,%s,%d,%d,(CoordinationAffix))' % (self.server_name, self.tcp_listenport, self.udp_listenport)
