"""
<Start Date>
  January 13th, 2009

<Description>
  This class is the public interface for tcp.
  Implements the Berkley Sockets interface.

<Usage>
  Public Object
    Connection() - get new socket

  Public Methods:
    connection.bind(localip, localport) - sets localip, localport
    connection.listen() - listen for peers
    connection.accept() - accept a request from a peer
    connection.connect(remoteip, remoteport) - request a connection
    connection.send(data) - send data to current peer
    connection.recv(maxLen) - recv data from current peer
    connection.disconnect() - end connection with current peer

  Private Methods:
    connection.callback(ip, port, mess, ch) - single inlet for receiving
    connection.register_recvmess() - single point for setting up listener
    connection.register_sendmess(destip, destport, message,
				 srcip = None, srcport = None) - single outlet for sending

  Private Objects:
    TcpStateMachine - handles event calls, sending, recving, handshake.
    TcpClient - handles packets with ACK bit set
    TcpServer - handles packets with SYN, FIN, PSH bits set
"""

include client.repy
include constants.repy
include controller.repy
include errors.repy
include packet.repy
include server.repy
include state_machine.repy
include utilities.repy

##############################################################
# Public Interface
##############################################################

class Connection:
  """
  <Purpose>
    Wraps up a Tcp socket.

  <Example Use>
    # Get an object
    connection = Connection()
    # bind to an address
    connection.bind(getmyip(), 12345)
    # open a connection
    connection.connect('123.4.5.6', 12345)
    # write information to the connected party
    connection.send("hello")
    # receive information from the connected party
    connection.recv(4096)
    # and disconnect without checking if they have said anything
    connection.disconnect()
  """   

  ##############################################################
  # Constructors
  ##############################################################
  def __init__(self):
    """
    <Purpose>
      Initializes an object to communicate from.

    <Arguments>
      None

    <Exceptions>
      None

    <Side Effects>
      Sets defaults for:
        * maxdgramsize - the maximum size of a packet
        * retries - times to retry sending
        * timeout - seconds to wait before retrying

      Gets a lock through Repy's getlock().
      Initializes a statemachine.

    <Returns>
      A new Connection object.
    """

    # Let's assign some constants
    self.DEFAULT_MAXLEN = 4096 # 4 kilo bytes ??


    # Configuration  
    self.maxdgramsize = DEFAULT_MAXDGRAMSIZE
    self.retries = DEFAULT_RETRIES
    self.timeout = DEFAULT_TIMEOUT
    self.way_to_close = FOUR_WAY

    # undefined
    self.localip = None
    self.localport = None
    self.remoteip = None
    self.remoteport = None

    self.conn = None

    # temporary for listen - accept transition and spawn
    self.tcup = None

    # Let's put all the protocol details in a state machine
    self.state_machine = TcpStateMachine()

    # also let's logically separate tasks
    self.client = None
    self.server = None

    # need lock for race conditions
    self.lock = getlock() # general

  ###########################
  # Bind
  #############################
 
  def bind(self, localip, localport):
    """
    <Purpose>
      Attaches the socket to an address.

    <Arguments>
      localip:
             The ip to communicate from.  Probably is either '127.0.0.1' or
             the result from getmyip().

      localport:
             The port to communicate from.  The restrictions file must allow
             this port as a messport.

    <Exceptions>
      raise AlreadyConnectedError("cannot rebind an active connection")

    <Side Effects>
      None

    <Returns>
      Nothing
    """
    self.lock.acquire()
    if self.conn:
      self.lock.release()
      raise AlreadyConnectedError("cannot rebind an active connection")
    
    # I am:
    self.localip = localip
    self.localport = localport
    self.lock.release()

  def listen(self):
    """
    <Purpose>
      Allows the local connection to receive messages.

    <Arguments>
      None

    <Exceptions>
      raise NotBoundError("need to bind before listening")
      raise AlreadyConnectedError("cannot listen with an active connection")

    <Side Effects>
      Sets up a UDP callback.
      Initializes server.

    <Returns>
      None
    """

    self.lock.acquire()
    if not self.localip or not self.localport:
      self.lock.release()
      raise NotBoundError("need to bind before listening")

    if self.conn:
      self.lock.release()
      raise AlreadyConnectedError("cannot listen with an active connection")

    tcup = Connection()
    tcup.bind(self.localip, self.localport)
    tcup.conn = tcup.register_recvmess()
    tcup.state_machine.listen() # do any state accounting
    tcup.server = TcpServer(tcup.register_sendmess)
    
    self.tcup = tcup
    self.lock.release()

  def accept(self):
    """
    <Purpose>
      Blocks until a peer requests a conn or it times out.
      Responds to request with SYN ACK and returns established.
    
    <Arguments>
      None      
    
    <Exceptions>
      raise NotBoundError("need to bind and listen before accepting")
      raise NotConnectedError("cannot accept without a connection")
      raise AlreadyConnectedError("cannot accept when already connected")
      TimeoutError if the request host does not acknowledge.    

    <Side Effects>
      Initializes client.
     
    <Returns>
      A tuple (tcup, addr) of a spawned TCUP and the remote addr (ip, port)
    """
    self.lock.acquire()
    if not self.localip or not self.localport:
      self.lock.release()
      raise NotBoundError("need to bind and listen before accepting")

    if self.tcup == None:
      self.lock.release()
      raise NotConnectedError("cannot accept without a connection")

    if self.remoteip and self.remoteport:
      self.lock.release()
      raise AlreadyConnectedError("cannot accept when already connected")

    tcup = self.tcup
    self.lock.release()

    # block until syn request or timeout
    request_addr = tcup.server.listen(self.timeout * self.retries)

    # fill in what we have
    self.lock.acquire()

    # guarenteed to never happen here (timeout is in server)
    if not request_addr:
      self.lock.release()
      raise TimeoutError("timed out listening")

    # change states
    tcup.state_machine.accept()

    # change to estab
    tcup.remoteip, tcup.remoteport = request_addr

    # setup out client
    tcup.client = TcpClient(tcup.register_sendmess)
    tcup.client.set_recv_window(tcup.server.get_recv_window())
    ack_num = tcup.server.get_ack_num()
    self.lock.release()

    # actually complete it, blocks for final ACK
    tcup.client.accept(ack_num, tcup.remoteip, tcup.remoteport, \
				tcup.localip, tcup.localport, \
					tcup.retries, tcup.timeout)

    # spawn
    self.lock.acquire()
    self.tcup = None
    self.lock.release()

    return (tcup, (tcup.remoteip, tcup.remoteport))

  def connect(self, remoteip, remoteport):
    """
    <Purpose>
      Requests a TCP connection with the remote party.  
      The remote party should have called listen().  Blocks
      until the remote party responds by calling accept().
      Then returns and waits for calls to send() and recv().

    <Arguments>
      remoteip:
             The ip to communicate to.

      remoteport:
             The port to communicate to.

    <Exceptions>
      raise NotBoundError("need to bind before connecting")
      raise AlreadyConnectedError("cannot connect on an active connection")
      TimeoutError if the remote host does not respond.

    <Side Effects>
      Sets up a UDP callback.
      Initializes client and server.

    <Returns>
      Nothing
    """
    self.lock.acquire()
    if not self.localip or not self.localport:
      self.lock.release()
      raise NotBoundError("need to bind before connecting")

    if self.conn:
      self.lock.release()
      raise AlreadyConnectedError("cannot connect on an active connection")

    # Peer is:
    self.remoteip = remoteip
    self.remoteport = remoteport

    # listen for responses
    self.conn = self.register_recvmess()
    self.client = TcpClient(self.register_sendmess)
    self.server = TcpServer(self.register_sendmess)

    self.state_machine.connect() # do any state accounting
    self.lock.release()

    self.client.connect(remoteip, remoteport, \
		self.localip, self.localport, \
                self.retries, self.timeout) # block until syn or synack recv

  def send(self, data):
    """
    <Purpose>
      Send a message over our TCP connection to the remote party.

    <Arguments>
      data:
             Whatever message you want to send to the remote party.

    <Exceptions>
      raise NotBoundError("cannot send data without binding")
      raise NotConnectedError("cannot send data without a receiver")
      raise NotEstablishedError("cannot send data without accepting")
      TimeoutError if the remote party does not acknowlege the message.

    <Side Effects>
      None

    <Returns>
      The bytes sent over the network.  The bytes include lost or
      dropped packets.
    """

    self.lock.acquire()
    if not self.localip or not self.localport:
      self.lock.release()
      raise NotBoundError("cannot send data without binding")

    if self.conn == None:
      self.lock.release()
      raise NotConnectedError("cannot send data without a receiver")

    if not self.remoteip or not self.remoteport:
      self.lock.release()
      raise NotEstablishedError("cannot send data without accepting")
   

    self.lock.release()
    # send them
    bytes = self.client.send(data, self.remoteport, self.localport, self.retries, self.timeout)
    return bytes

  def recv(self, maxLen):
    """
    <Purpose>
      Receive a message over our TCP connection from the remote party.

    <Arguments>
      maxLen:
             The maximum bytes you want to receive.

    <Exceptions>
      raise NotBoundError("cannot recv data without binding, listening, and accepting")
      raise NotConnectedError("cannot recv data without listening and accepting")
      raise NotEstablishedError("cannot recv data without accepting")

    <Side Effects>
      Deletes the returned data from the connection buffer.
      Add to the client's advertise window the new server's free buffer space.

    <Returns>
      The maxLen bytes of buffered messages.
    """
    self.lock.acquire()

    if not self.localip or not self.localport:
      self.lock.release()
      raise NotBoundError("cannot recv data without binding, listening, and accepting")

    if self.conn == None:
      self.lock.release()
      raise NotConnectedError("cannot recv data without listening and accepting")

    if not self.remoteip or not self.remoteport:
      self.lock.release()
      raise NotEstablishedError("cannot recv data without accepting")
  
    message = self.server.recv(maxLen)
    
    # If empty, wait a little
#    if not message:
#      self.lock.release()
#      sleep(self.timeout)
#      self.lock.acquire()
#      message = self.server.recv(maxLen)

    if self.client:  # could be half open?
      self.client.set_recv_window(self.server.get_recv_window())
    self.lock.release()

    return message

  def disconnect(self):
    """
    <Purpose>
      End our TCP connection with the remote party.

    <Arguments>
      None

    <Exceptions>
      raise NotConnectedError("cannot disconnect without connection")

    <Side Effects>
      Destroys the UDP callback.
      Destroys client and server.

    <Returns>
      Nothing
    """

    self.lock.acquire()
    if self.conn == None:
      self.lock.release()
      raise NotConnectedError("cannot disconnect without connection")

    # Need to decide whether to send FIN or just close.
    # Let's ask the state machine.
    if self.state_machine.close(): # need to send FIN?
      # copy them all for thread safety
      (remoteip, remoteport, localip, localport, retries, timeout) =\
         (self.remoteip, self.remoteport, self.localip, self.localport, \
	  self.retries, self.timeout)
      self.lock.release()

      # Start sending FIN, block until ACK'd
      self.client.close(remoteip, remoteport, localip, localport, retries, timeout)

      self.lock.acquire()
      self.client = None

      if self.server.is_closed(): # half open already
        self._destroy() # close it fully
      else:
        pass # we can only recv now

    else: # cut out
      self._destroy()

    self.lock.release()


  ##############################################################
  # Private Methods
  ##############################################################

  def callback(self, ip, port, mess, ch):
    """
    <Purpose>
      Provides a single way point between received messages.

      Constructs packet from mess and sends to state machine.
      The state machine tells the client and server what to do
      to handle the packet.      
    """
    self.lock.acquire()
    #print "recving: " + mess + " at: " + str(ip) + ":" + str(port) # DEBUG
    packet = unpack(mess)

    # How can this happen?
    # (1) the thread came in and hit this function's lock
    # (2) socket.disconnect()
    # (3) the thread continued on assuming a connection
    if not self.conn: 
      self.lock.release() # ignore it quietly
      return None 

    # ignore bad ips and ports
    if (self.remoteip and self.remoteport) and \
	(ip != self.remoteip or packet.srcport != self.remoteport):
      self.lock.release()
      return None

    # routed incorrectly
    if self.localport != packet.destport:
      self.lock.release()
      raise Exception("controller error?")    

    # Let the state machine tell us what to do.
    advice = self.state_machine.next(packet)
    # init
    if advice == OP_RCV_REMOTE_SYN: 
      self.server.process(ip, packet)

    # mock synack
    elif advice == OP_RCV_LOOPBACK_SYN: 
      packet.ack_num = mod_add(packet.seq_num, 1)
      packet.control_bits = "SYNACK"    
      # Is valid SYN sequence?
      if self.client.process(packet): # stop send syn
        self.server.process(ip, packet) # send back final ack
      else:
	self.state_machine.rollback() # error so rollback state_machin
   
    # real syanck
    elif advice == OP_RCV_SYNACK: 
      self.client.process(packet) # stop send syn
      # Is valid SYN sequence?
      if self.client.process(packet): # stop send syn
        self.server.process(ip, packet) # send back final ack
      else:
	self.state_machine.rollback() # error so rollback state_machin

    # payload and final ack
    elif advice == OP_RCV_PAYLOADACK:  
      self.server.process(ip, packet) # send ack?
      self.client.set_recv_window(self.server.get_recv_window())
      self.client.process(packet)      

    # ack estab, maybe final
    elif advice == OP_RCV_ACK:
      self.client.process(packet) # send payloads?

    # payload estab
    elif advice == OP_RCV_PAYLOAD:  
      self.server.process(ip, packet) # send ack?
      if self.client: # could be half-open
         self.client.set_recv_window(self.server.get_recv_window())

    # closing handshake
    elif advice == OP_RCV_FIN:
      if self.way_to_close == THREE_WAY:
        seq_num = self.client.get_seq_num()
        self.server.set_fin_num(seq_num) # send FINACK
     
      if not self.server.process(ip, packet):
	self.state_machine.rollback() # error so rollback state_machin
      elif not self.client: # half open connection
        self._destroy()
      else: # we can only send, probably call close next
        pass

    elif advice == OP_RCV_ACK_FIN:
      if not self.client.process(packet):
	self.state_machine.rollback() # not so fast, let's try again

    # three way close
    elif advice == OP_RCV_FINACK:

      packet.control_bits = "ACK"
      if not self.client.process(packet):
	self.state_machine.rollback() # not so fast, let's try again

      # then do FIN as well        
      packet.control_bits = "FIN"
      if not self.server.process(ip, packet):
	self.state_machine.rollback() # error so rollback state_machine

    # ignore
    else:
      pass 
    self.lock.release()

  def register_recvmess(self):
    """
    <Purpose>
      Provides a single point for setting up a listener.

    <Side Effects>
      Sets up UDP listener event.
     
    <Returns>
      A handle to the listener event.
    """
    if self.localip is None or self.localport is None:
      raise NotBoundError("Unable to register listener if not bound")
    #print "listening on: " + str(self.localip) + ":" + str(self.localport) # DEBUG
    return recvmess(self.localip, self.localport, self.callback)


  def register_sendmess(self, message):
    """
    <Purpose>
      Provides a single outlet for tracking sent messages.

    <Returns>
      The bytes sent.
    """
    if self.localip is None or self.localport is None:
      raise NotConnectedError("Must be bound before sending!")
    if self.remoteip is None or self.remoteport is None:
      raise NotConnectedError("must be connected to send messages!")

    #print "sending: " + message # DEBUG
    return sendmess(self.remoteip, self.remoteport, message, self.localip, self.localport)
    

  def _destroy(self):
    """
    <Purpose>
      Cleanup all the variables because we're done.
    """
    # timewait
    self.lock.release()
    if self.state_machine.time_waiting():
      self.state_machine.timeout_close()
    self.lock.acquire()

    stopcomm(self.conn)
    self.conn = None
    self.client = None
    self.server = None
    self.remoteip = None
    self.remoteport = None
    self.requestip = None
    self.requestport = None
