#!/usr/bin/env python

from vserver import *
from ccserver import *
from server import *
from queue import *
from optparse import OptionParser
from network import Network
import re
import sys

class MultiHomedReliableServer:

    def __init__( self, vs_type ):
        self.vservers = []
        self.resetFields()
        self.type = vs_type
        self.offset = 0 ## to tell us which vserver to begin sending data

    def resetFields( self ):
        """Reset data members to their default values.
        
        Call this on initialization, and between client connections.
        """
        self.fd = 0
        self.seqno = 0
        self.done = False
        self.sent_fin = False
        for vs in self.vservers:
            vs.resetFields()
        self.offset = 0

    def addVServer( self, vserver ):
        self.vservers.append( vserver )

    def sendData( self, host, port, msg ):
        for i in range(len(self.vservers)):
            vs = self.vservers[(i+self.offset) % len(self.vservers)]
            vs.done = self.done
            if( not self.sent_fin ):

                if( self.type != "fixed" ):
                    # outstanding variable is changed by sendData in
                    # ReliableServer, so taking the shared value
                    old_outstanding = vs.data.outstanding
                    vs.outstanding = vs.data.outstanding  

                if( self.type == "fixed" ):
                    ReliableServer.sendData( vs, host, port, msg )
                else:
                    AIMDReliableServer.sendData( vs, host, port, msg )

                if( self.type != "fixed" ):
                    vs.data.outstanding = vs.outstanding
                if( self.type == "shared" and
                    vs.outstanding != old_outstanding ):
                    # we have to sync everyone's outstanding
                    for v in self.vservers:
                        v.outstanding = vs.outstanding

                self.done = vs.done
        self.offset = (self.offset+1) % len(self.vservers)

    def canSendFin( self, host, port ):
        # we can send the FIN under the following conditions
        # 1) all data has been sent (i.e., we are done)
        # 2) no message on any connections are outstanding
        # 3) we haven't already sent one
        if( not self.done or self.sent_fin ):
            return False
        for vs in self.vservers:
            if( vs.outstanding != 0 ):
                return False
        self.sent_fin = True
        return True

#####################################################################
##  Parse options / start network
#####################################################################

def makeMHServerParser():
    """Parse server options
    """

    host = gethostname()
    port = 6829
    window = 4
    chunk_size = 1000
    loss = 0
    debug = False
    networks = []
    vs_type = "fixed"

    parser = OptionParser()
    parser.add_option("-w", "--window", dest="window", type="int",
                      default=window, action="store",
                      help="""fixed window size (in packets) -- relevant
                      only for the fixed vserver type.""")
    parser.add_option("-n", "--network", dest="networks", type="string",
                      default=networks, action="append",
                      help="""A 4-tuple of comma-separated ints, specifying
                      the characteristics of an underlying network.
                      Can be specified multiple times to create multiple
                      networks.  Argument looks like this:
                      <loss>,<rate>,<qsize>,<delay>""")
    parser.add_option("-v", "--vserver-type", dest="vs_type", type="string",
                      default=vs_type, action="store",
                      help="""type of vservers to use.  Possible values:
                      fixed, aimd, shared.  Default: fixed""")
    parser.add_option("-p", "--port", dest="port", type="int", default=port,
                      action="store", help="port to listen on")
    parser.add_option("-s", "--size", dest="size", type="int",
                      default=chunk_size, action="store",
                      help="data chunk size")
    parser.add_option("-d", "--debug", dest="debug",
                      default=debug, action="store_true",
                      help="print network debug info")
    parser.add_option("-b", "--bind", dest="host", type="string",
                      default=host, action="store",
                      help="host name to bind to")
    parser.add_option("-f", "--fast-rxmit", dest="fast",
                      action="store_true", default=False,
                      help="turn on fast retransmissions")
    return parser

shared_data = None

def makeVServer( type, mhrs, network, window, chunk_size, fast ):
    """Create a virtual server of the appropriate type.
    """

    global shared_data

    if( type == "fixed" ):
        s = ReliableServer( network, window, chunk_size )
    elif( type == "aimd" ):
        data = AIMDData()
        s = AIMDReliableServer( data, network, window, chunk_size, fast )
        #s.openCwndLog() ####opening cwnd dump log file
    elif( type == "shared" ):
        if( shared_data == None ):
            shared_data = AIMDData()
        s = AIMDReliableServer( shared_data, network, window, chunk_size,
                                fast )
        #s.openCwndLog() ####opening cwnd dump log file
    else:
        print "Unexpected vserver type:",type,"-- exiting."
        sys.exit(-1)

    virtualizeServer( s, mhrs )

    return s

def main(argv=None):
    """Run a multi-homed server from the command line.

    Parse options, and enter the L{Network <network.Network>} event loop.
    """
    
    if argv is None:
        argv = sys.argv

    # Get command line options
    parser = makeMHServerParser()
    (options, args) = parser.parse_args()

    window = options.window
    port = options.port
    chunk_size = options.size
    host = options.host
    debug = options.debug
    networks = options.networks
    vs_type = options.vs_type
    fast = options.fast

    server = MultiHomedReliableServer(vs_type)

    # create all networks
    first_net_done = False
    for n in networks:

        curr_port = -1
        if( not first_net_done ):
            curr_port = port
            first_net_done = True

        [loss, rate, qsize, delay] = re.split( ",", n )

        queue = Queue( int(rate), int(qsize) )
        network = Network( gethostname(), float(loss), curr_port, debug,
                           queue, float(delay) )
        vserver = makeVServer( vs_type, server, network, window, chunk_size,
                               fast )
        server.addVServer( vserver )

    if( not first_net_done ):
        network = Network( gethostname(), 0, port, debug )
        vserver = makeVServer( vs_type, server, network, window, chunk_size,
                               fast )
        server.addVServer( vserver )

    try:
        Network.loop()
    except KeyboardInterrupt:
        if( vs_type != "fixed" ):
            for vs in server.vservers:
                vs.closeCwndLog()  # closing cwnd dump log
        print "Exiting on command.  Goodbye."

if __name__ == "__main__":
    sys.exit(main())
