'''
Created on Nov 3, 2011
Modified Jan 1, 2012
@version: 0.2
@author: Dilum Bandara
@email: dilumb@engr.colostate.edu
@copyright: Copyright(c) 2011, Dilum Bandara
@license: GPL
'''

import math
import sys
import Config as Config
import PeerManager as PM
from CCNInterest import CCNInterest
from CCNData import CCNData

class Application(object):
    '''
    Application class
    '''

    def __init__(self, NID, loc, appType):
        '''
        Constructor
        @param loc: Location 
        @param type: Application type, e.g., reflectivity, dual Doppler 
        '''
        self.NID = NID
        self.loc = loc
        self.type = appType
        self._penAppInter = {} #Pending interests from proxy
        self._penSubInter = {} #pending subscriptions
        self._penDataInter = {} # Pending data
        self.numSubInterMsgsGen = 0 #Subscription interest messages
        self.numSubInterMsgsSend = 0
        self.numDataInterMsgsGen = 0 #Data interest messages
        self.numDataInterMsgsSend = 0
        self.numInterMsgsRec = 0
        self.numTotalSubInterMsgsSend = 0.0
        self.numTotalDataInterMsgsSend = 0.0
        self.numTotalInterMsgsRec = 0.0 #In bits
        self.numDataMsgsSend = 0
        self.numDataMsgsRec = 0
        self.numTotalDataMsgsSend = 0.0
        self.numTotalDataMsgsRec = 0.0


    def recMsg(self, msg):
        '''
        Receive message from CCN
        @param msg: Message
        '''
        if msg.msgType == 'CCNInt':
            self.numInterMsgsRec += 1
            self.numTotalInterMsgsRec += msg.size 
            self._handleAppInt(msg)
        elif msg.msgType == 'CCNData':
            self.numDataMsgsRec += 1
            self.numTotalDataMsgsRec += msg.size         
            if msg.name in self._penSubInter: #reply to Subscription interests
                toRemove = []
                for i in range(len(self._penSubInter[msg.name])):
                    originName = self._penSubInter[msg.name][i].keys()[0]
                    validTime = self._penSubInter[msg.name][i][originName][3]
                    if msg.genTime <  validTime: continue 
                    if self._handleSubData(msg, i, originName, validTime) == True: 
                        toRemove.append(self._penSubInter[msg.name][i])
                for j in toRemove:
                    del self._penSubInter[msg.name][self._penSubInter[msg.name].index(j)]
                if len(self._penSubInter[msg.name]) == 0: 
                    del self._penSubInter[msg.name]
            elif msg.name in self._penDataInter: #reply to data interests
                toRemove = []
                for i in range(len(self._penDataInter[msg.name])):
                    originName = self._penDataInter[msg.name][i].keys()[0]
                    validTime = self._penDataInter[msg.name][i][originName][4]
                    if msg.genTime <  validTime: continue
                    self._handleAppData(msg, i, originName, validTime) 
                    toRemove.append(self._penDataInter[msg.name][i])
                for j in toRemove:
                    del self._penDataInter[msg.name][self._penDataInter[msg.name].index(j)]
                if len(self._penDataInter[msg.name]) == 0: 
                    del self._penDataInter[msg.name]
            else:
                print 'Application::recMsg No msg in pending interests', msg.name
        else: print 'Application::recMsg Unknown msg type', msg.msgType
  

    def _handleAppInt(self, msg):
        '''
        Handle interest message from proxy
        @param msg: Interest message
        '''
        if msg.app == 'REFL': numRadars = 1
        elif msg.app == 'DDOP': numRadars = 2
        elif msg.app == 'NBRR': numRadars = 3
        elif msg.app == 'NCAS': numRadars = 3
        elif msg.app == 'QPE': numRadars = 3
        else: 
            print 'Application::_handleAppInt Unknown application type', msg.app
            sys.exit()

        name = '/' + str(msg.minLoc.x) + '/' + str(msg.minLoc.y) + '/' \
                + str(msg.maxLoc.x) + '/' + str(msg.maxLoc.y) + '/RADAR/SUBS/' +\
                msg.app + '/'
        realName = name + str(numRadars) + '/' + str(msg.time) + '/'        
        numTilesX = int(math.ceil((msg.maxLoc.x - \
                                   msg.minLoc.x)/Config.TILE_X_RADAR))
        numTilesY = int(math.ceil((msg.maxLoc.y - \
                                   msg.minLoc.y)/Config.TILE_Y_RADAR))
        bitMap = [0] * (numTilesX * numTilesY)
        #Track application interest
        #Data format {validTime: [dataSize, oldestDataTime, originalHops,  
        # newHops, realName, dataInterests, all complete]}
        if msg.name in self._penAppInter:
            self._penAppInter[msg.realName][msg.time] = [0, 999999.9, \
                                        msg.hops, -1, msg.name, [], False]
        else:
            self._penAppInter[msg.realName] = {msg.time:[0, 999999.9, \
                                        msg.hops, -1, msg.name, [], False]}
        #Track subscription interest
        if name in self._penSubInter: #msg.name helps to identify which request
            self._penSubInter[name].append({msg.realName: [msg.app, numRadars,\
                                                bitMap, msg.time, msg.minLoc]})
        else: self._penSubInter[name] = [{msg.realName: [msg.app, numRadars, \
                                                bitMap, msg.time, msg.minLoc]}]
        #Send message
        newMsg = CCNInterest(name, realName, msg.minLoc, msg.maxLoc, 'RADAR', \
                             msg.time, 'SUBS', numRadars)
        result = PM.OVERLAY[self.NID].fwdInter(newMsg)
        self.numSubInterMsgsGen += 1
        if result != None: #In local cache
            if self._handleSubData(result, self._penSubInter[name].index(\
                {msg.realName: [msg.app, numRadars, bitMap, msg.time, \
                                msg.minLoc]}), msg.realName, msg.time) == True:
                del self._penSubInter[name][self._penSubInter[name].index(\
                                    {msg.realName: [msg.app, numRadars, bitMap,\
                                                     msg.time, msg.minLoc]})]
                if len(self._penSubInter[name]) == 0: del self._penSubInter[name]
        else: #Messages was send through network
            self.numSubInterMsgsSend += 1
            self.numTotalSubInterMsgsSend += newMsg.size


    def _handleSubData(self, msg, idx, originName, validTime):
        '''
        Handle subscription response
        @param msg: Message
        @param idx: Index in pending list  
        @param originName: Name from interest send by proxy
        @param validTime: Content should be fresh than this time 
        @return: Indicate whether all required data for subscription is found
        '''
        if originName not in self._penAppInter: return
        dataType  = self._penSubInter[msg.name][idx][originName][0]
        numRadars = self._penSubInter[msg.name][idx][originName][1]
 
        requests = []
        allComplete = True
        #radar should generate this data & it's not already received
        if type(msg.data[0]).__name__ == 'list' :#Data from local or remote cache
            for i in range(len(msg.data)):
                tilesToAsk = [0] * len(msg.data[i][3])
                sendNewInter = False #Should a new interest message be send
                allComplete = True
                for j in range(len(msg.data[i][3])):
                    if (msg.data[i][3][j] == 1) and (numRadars >
                        self._penSubInter[msg.name][idx][originName][2][j]):
                        tilesToAsk[j] = 1
                        self._penSubInter[msg.name][idx][originName][2][j] += 1
                        sendNewInter = True
                    if numRadars > \
                            self._penSubInter[msg.name][idx][originName][2][j]:
                        allComplete = False
                if sendNewInter == True : requests.append([msg.data[i][0], \
                                                msg.data[i][1], tilesToAsk])
                if allComplete == True: break #Found all
        else: #Data from source
            tilesToAsk = [0] * len(msg.data)
            sendNewInter = False #Should a new interest message be send
            allComplete = True
            for j in range(len(msg.data)):
                if (msg.data[j] == 1) and (numRadars > \
                        self._penSubInter[msg.name][idx][originName][2][j]):
                    tilesToAsk[j] = 1
                    self._penSubInter[msg.name][idx][originName][2][j] += 1
                    sendNewInter = True
                if numRadars > self._penSubInter[msg.name][idx][originName][2][j]:
                    allComplete = False            
            if sendNewInter == True : requests.append([msg.source, \
                                                       msg.sourceLoc, tilesToAsk])     
        if len(requests) == 0: return allComplete #Data not useful
        
        #Prepare data request(s)
        messages = []
        for i in range(len(requests)):
            strTilesToAsk = ''
            for j in range(len(requests[i][2])): 
                strTilesToAsk += str(requests[i][2][j])
            nodeLoc = '/'+ str(requests[i][1].x) + '/' + str(requests[i][1].y)
            nodeLoc += nodeLoc
            #lower left corner of area of interest
            llAOI = str(self._penSubInter[msg.name][idx][originName][4].x) +'/'+\
                    str(self._penSubInter[msg.name][idx][originName][4].y) +'/'
            validTimeS = str(validTime)        
            if dataType == 'REFL':
                name =  nodeLoc + '/RADAR/REFL/' + llAOI
                realName = name + validTimeS +'/' + strTilesToAsk + '/'               
                #Track Data interest
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                    [dataType, numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                    [dataType, numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                if allComplete == True: 
                    self._penAppInter[originName][validTime][6] = True                
                #Create list of messages to send
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, dataType, -1, \
                            requests[i][2])
                messages.append([newMsg, requests[i], dataType])
            elif dataType == 'DDOP' :
                baseName = nodeLoc + '/RADAR/'
                name = baseName + 'REFL/' + llAOI
                realName = baseName +'REFL/'+ validTimeS +'/'+ strTilesToAsk +'/'
                #Track Data interest
                if name in self._penDataInter: #msg.name helps to identify which request
                    self._penDataInter[name].append({originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                if allComplete == True: 
                    self._penAppInter[originName][validTime][6] = True                
                #Create list of messages to send
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'REFL', -1, \
                            requests[i][2])                
                messages.append([newMsg, requests[i], 'REFL'])                
                #Also ask for radial velocity (DDOP)
                name = baseName + 'DDOP/' + llAOI
                realName = baseName +'DDOP/'+ validTimeS +'/'+ strTilesToAsk +'/'
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                        ['DDOP', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['DDOP', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)                
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'DDOP', -1, \
                            requests[i][2])
                messages.append([newMsg, requests[i], 'DDOP'])
            elif  dataType == 'NBRR' or dataType == 'NCAS':
                name =  nodeLoc + '/RADAR/REFL/' + llAOI                    
                realName = name + validTimeS +'/'+ strTilesToAsk +'/'
                #Track Data interest
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                if allComplete == True: 
                    self._penAppInter[originName][validTime][6] = True                
                #Create list of messages to send
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'REFL', -1, \
                            requests[i][2])
                messages.append([newMsg, requests[i], 'REFL'])
            elif dataType == 'QPE' :
                baseName = nodeLoc + '/RADAR/'
                name = baseName + 'REFL/' + llAOI
                realName = baseName +'REFL/'+ validTimeS +'/'+ strTilesToAsk+'/'
                #Track Data interest
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['REFL', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                if allComplete == True: 
                    self._penAppInter[originName][validTime][6] = True                
                #Create list of messages to send
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'REFL', -1, \
                            requests[i][2])                
                messages.append([newMsg, requests[i], 'REFL'])
                
                #Also ask for correlation coefficient (RHV)
                name = baseName + 'RHV/' + llAOI
                realName = baseName +'RHV/'+ validTimeS +'/'+ strTilesToAsk+'/'
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                        ['RHV', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['RHV', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'RHV', -1, \
                            requests[i][2])
                messages.append([newMsg, requests[i], 'RHV'])
                        
                #QPE also ask for differential phase (FDP)
                name = baseName + 'FDP/' + llAOI
                realName = baseName +'FDP/'+ validTimeS +'/'+ strTilesToAsk +'/'
                if name in self._penDataInter: #msg.name identify which request
                    self._penDataInter[name].append({originName: \
                        ['FDP', numRadars, requests[i][2], msg.name, validTime]})
                else: self._penDataInter[name] = [{originName: \
                        ['FDP', numRadars, requests[i][2], msg.name, validTime]}]
                self._penAppInter[originName][validTime][5].append(name)
                                    
                newMsg = CCNInterest(name, realName, requests[i][1], \
                            requests[i][1], 'RADAR', validTime, 'FDP', -1, \
                            requests[i][2])
                messages.append([newMsg, requests[i], 'FDP'])
        #Send data
        for k in range(len(messages)):
            result = PM.OVERLAY[self.NID].fwdInter(messages[k][0],\
                                                    messages[k][1][0])
            self.numDataInterMsgsGen += 1
            if result != None: #In local cache
                self._handleAppData(result, self._penDataInter[result.name].index(\
                    {originName: [messages[k][2], numRadars, messages[k][1][2], \
                                  msg.name, validTime]}), originName, validTime)
                del self._penDataInter[result.name]\
                        [self._penDataInter[result.name].index({originName: \
                         [messages[k][2], numRadars, messages[k][1][2], \
                          msg.name, validTime]})]
                if len(self._penDataInter[result.name]) == 0: 
                        del self._penDataInter[result.name]
            else: #Send through networks
                self.numDataInterMsgsSend += 1
                self.numTotalDataInterMsgsSend += messages[k][0].size
        return allComplete


    def _handleAppData(self, msg, idx, originName, validTime):
        '''
        Handle data send by radar
        @param msg: Message
        @param idx: Index
        @param originName: Name from interest send by proxy
        @param validTime: Content should be fresh than this time 
        '''
        if originName not in self._penAppInter: return
        if msg.name not in self._penAppInter[originName][validTime][5]: return        
        self._penAppInter[originName][validTime][0] += msg.dataSize
        if self._penAppInter[originName][validTime][1] > msg.genTime:
            self._penAppInter[originName][validTime][1] = msg.genTime
        if self._penAppInter[originName][validTime][3] < msg.hops:
            self._penAppInter[originName][validTime][3] = msg.hops
        #radar should generate this data & it's not already received
        del self._penAppInter[originName][validTime][5]\
                [self._penAppInter[originName][validTime][5].index(msg.name)]
        if (len(self._penAppInter[originName][validTime][5]) > 0) or \
                        (self._penAppInter[originName][validTime][6] == False): 
            return #Data not complete
        dataSize = 0
        #REFL - 1 radar & 1 data type
        #DDOP - 2 radars & 2 data types - 2 data types needed for speed & direction
        #NBRR - 3 radars & 1 data type
        #NCAS - 3 radars & 1 data type
        #QPE - 3 radars & 3 data types
        if 'REFL' in originName: 
            dataSize = self._penAppInter[originName][validTime][0]
        elif 'DDOP' in originName: 
            dataSize = self._penAppInter[originName][validTime][0]/2
        elif 'NBRR' in originName: 
            dataSize = self._penAppInter[originName][validTime][0]/3
        elif 'NCAS' in originName: 
            dataSize = self._penAppInter[originName][validTime][0]/3
        elif 'QPE' in originName: 
            dataSize = self._penAppInter[originName][validTime][0]/9
        #We have all required data
        newMsg = CCNData(self._penAppInter[originName][validTime][4], originName\
                , dataSize, [], self._penAppInter[originName][validTime][1], \
                self._penAppInter[originName][validTime][2] + \
                self._penAppInter[originName][validTime][3], self.NID, self.loc)
        PM.OVERLAY[self.NID].fwdData(newMsg)
        self.numDataMsgsSend += 1
        self.numTotalDataMsgsSend += newMsg.size
        del self._penAppInter[originName][validTime]
        if len(self._penAppInter[originName]) == 0:
            del self._penAppInter[originName]
        
        
    def getStat(self):
        '''
        Get statistics
        @return: results
        '''
        return [len(self._penAppInter), len(self._penSubInter), \
                len(self._penDataInter), self.numSubInterMsgsGen, \
                self.numSubInterMsgsSend, self.numInterMsgsRec, \
                self.numDataInterMsgsGen, self.numDataInterMsgsSend,\
                self.numDataMsgsSend, self.numDataMsgsRec,\
                self.numTotalSubInterMsgsSend, self.numTotalDataInterMsgsSend,\
                self.numTotalInterMsgsRec, self.numTotalDataMsgsSend, \
                self.numTotalDataMsgsRec]
