#!/usr/bin/python2
import socket, string, struct, os, mmap, random, threading, time, sys
import ConfigParser
import blockwriter_mmap,blockwriter_lseek,blockwriter_stream

# utility function
def strlist(list):
    s = string.join(map (lambda (k,v):str(k),list[:10]))
    if len(list)>10:
        s += " (%d more)"%(len(list)-10)
    return s

# request announce
#if len(sys.argv)>1:
#    netsocket.sendto("FFFFFFFF",(sys.argv[1],2133))




class receiver(threading.Thread):
    def __init__(self,blockwriter,netsocket):
        threading.Thread.__init__(self)
        self.stamp = time.time()
        self.lock = threading.Lock()
        self.blockwriter = blockwriter
        self.lastpacketreceived = -1 # index of last received packet
        self.missing = {} # key=packetnumber, value=time of request
        self.lastpackettoreceive = self.blockwriter.blockcount-1
        self.netsocket = netsocket
        self.unicasttimeout = 2
        self.endofstreamdelay = 10
        self.blocksize = blockwriter.blocksize
        self.wholeblocks = blockwriter.wholeblocks
    def request(self,blocknumber):
        self.serveraddr = "193.55.63.165"
        self.serverport = 2133
        self.missing[blocknumber]=time.time()
        self.netsocket.sendto("%08X"%blocknumber,
                              (self.serveraddr,self.serverport))
    def finished(self):
        return \
               (not self.missing) and \
               (self.lastpacketreceived==self.lastpackettoreceive)
    def run(self):
        while not self.finished():
            (data,(addr,port))=self.netsocket.recvfrom\
                                (self.blockwriter.blocksize+20)
            self.addr = addr
            self.port = port

            # random packet loss !
            if 0==random.choice(range(0,10)): continue
    
            (blocknumber,data) = string.split (data, ":", 1)
            if blocknumber=="FFFFFFFF": continue
            self.stamp = time.time()
            blocknumber = string.atoi("0x"+blocknumber,16)
            if blocknumber<=self.lastpacketreceived:
                if blocknumber not in self.missing.keys():
                    print "I don't need block %d"%blocknumber
                    continue
                del self.missing[blocknumber]
            for b in range(self.lastpacketreceived+1,blocknumber):
                self.missing[b]=time.time()
            if blocknumber>self.lastpacketreceived:
                self.lastpacketreceived=blocknumber
            begin = blocknumber*self.blocksize
            if blocknumber < self.wholeblocks: end=begin+self.blocksize
            else: end=begin+self.blockwriter.lastblock
            if end-begin != len(data):
                print "ERROR: wrong packet size, expected=%d, got=%d"%\
                      (end-begin, len(data))
            self.blockwriter.putblock(blocknumber,data)
        print "Receiver thread exitting"

    def checkqueue(self):
        timedoutunicasts = filter (lambda (k,v),
                                   limit=time.time()-self.unicasttimeout:
                                   v<limit, self.missing.items())
        if len(timedoutunicasts)>0:
            print "%d unicast requests have timed out, requesting %s"%\
                  (len(timedoutunicasts), strlist(timedoutunicasts))
            for (b,t) in timedoutunicasts:
                self.request (b)
        if self.stamp+self.endofstreamdelay<time.time():
            print "no packet received since a long time, requesting %s"%\
                  strlist(missing.items())
            for b in self.missing.keys():
                self.request (b)
            if lastpacketreceived!=lastpackettoreceive:
                print "we lack the end of the stream, requesting %d packets"%\
                      lastpackettoreceive-lastpacketreceived
                for b in range(lastpacketreceived+1,lastpackettoreceive+1):
                    self.request (b)
    
class updater(threading.Thread):
    def __init__(self,receiver,syncinterval,checkqueueinterval,sleepinterval):
        threading.Thread.__init__(self)
        self.receiver = receiver
        self.syncinterval = syncinterval
        self.checkqueueinterval = checkqueueinterval
        self.sleepinterval = sleepinterval
    def run(self):
        self.nextsync = time.time()
        self.nextcheckqueue = time.time()
        while not self.receiver.finished():
            now = time.time ()
            if self.nextsync < now:
                os.system("sync")
                self.nextsync += self.syncinterval
            if self.nextcheckqueue < now:
                self.receiver.checkqueue()
                self.nextcheckqueue += self.checkqueueinterval
            time.sleep(self.sleepinterval)
        print "Updater thread exitting"

class wrapper:
    def __init__(self):
        mcastaddr  = "224.2.13.3"
        mcastport  = 2133
        netsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        netsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        netsocket.bind(('', mcastport))
        bytes = map(int, string.split(mcastaddr, "."))
        grpaddr = 0
        for byte in bytes: grpaddr = (grpaddr << 8) | byte
        ifaddr = socket.INADDR_ANY
        mreq = struct.pack('ll', socket.htonl(grpaddr), socket.htonl(ifaddr))
        netsocket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

        netsocket.sendto("FFFFFFFF",("193.55.63.165",2133))

        # first state : waiting for "announce" packet
        while 1:
            (data,(addr,port)) = netsocket.recvfrom(1600)
            data = string.split(data,":")
            if data[0]!="FFFFFFFF":
                continue
            params = {}
            for kv in data[1:]:
                (k,v) = string.split(kv,"=")
                params[k]=v
            break

        # second state : send registration to the server
        netsocket.sendto("FFFFFFFF",(addr,port))

        # receive data
        filename = params['filename']
        blocksize = int(params['blocksize'])
        lastblock = int(params['lastblock'])
        wholeblocks = int(params['wholeblocks'])
        print "receiving: ",filename
        blockwriter = blockwriter_stream.blockwriter_stream("RECV-%s"%filename,
                                                            blocksize,
                                                            wholeblocks,
                                                            lastblock)
        r = receiver (blockwriter,netsocket)
        r.start()
        u = updater (r,1,1,1)
        u.start()

        while r.missing or \
              r.lastpackettoreceive!=r.lastpacketreceived:
            time.sleep(1)
            print "%d blocks left, %d missing : %s"%\
                  (r.lastpackettoreceive-r.lastpacketreceived,
                   len(r.missing),strlist(r.missing.items()))


w=wrapper()
