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

import Config as Config


class PIT(object):
    '''
    Pending Interest Table (PIT) class
    '''


    def __init__(self, maxSize):
        '''
        Constructor
        '''
        self._maxSize = maxSize
        self._PIT = {} #{name: [[list of faces interested in content], expireTime]}
        self.numPITTimeouts = 0 #Number of PIT entries timed out
        self.numPITIntChecks = 0 #Number of interest messages
        self.numPITIntHits = 0 #Number of interest message hits
        self.numPITDataChecks = 0
        self.numPITDataHits = 0 #Number of data messages
        self.lastTimeOutCheck = -1 #When was PIT timeout checked


    def put(self, name, source, time, validTime, numRadars = -1, bitMap = [], \
            direct = False):
        '''
        Add to PIT.
        @param msg: Content name without timeout
        @param source: Source node
        @param time: Current time
        @param validTime: Content should be fresh than this time
        @param numRadars: Number of radars to receive response from (optional)
        @param bitMap: Bit map use to track multiple replies 
        @param direct: Interest message send directly
        @return: 0 - no need to forward, 1 - need to forward, 2 - forward to app
        as this is a direct message. No pending messages, -1 PIT size exceed  
        '''
        self.numPITIntChecks += 1
        if self.lastTimeOutCheck + 5 < time: #Time to check PIT timeouts
            expiredNames = [] #remove any expired entries
            for i in self._PIT: #Check for timeouts in PIT            
                if self._PIT[i][1] < time: expiredNames.append(i)
            for j in expiredNames: #remove expired ones
                self.numPITTimeouts += 1
                del self._PIT[j]
            self.lastTimeOutCheck = time
                 
        if (name in self._PIT) and (time <= self._PIT[name][1]): #In PIT                
            newSource = True
            moreResent = False
            someResultsAlreadySend = False
            largestValidTime = -1.0
            for i in range(len(self._PIT[name][0])):
                if self._PIT[name][0][i][5] == True:
                    someResultsAlreadySend = True                
                #Is source already in PIT & need to be more resent? 
                if self._PIT[name][0][i][0] == source:
                    if self._PIT[name][0][i][3] >= validTime: 
                        if self._PIT[name][0][i][4] == True: newSource = False
                    else: moreResent = True
                    break
                if self._PIT[name][0][i][3] > largestValidTime:
                    largestValidTime = self._PIT[name][0][i][3]
            if (newSource == True) or (someResultsAlreadySend == True): #New source 
                self._PIT[name][0].append([source, numRadars, bitMap, \
                                               validTime, True, False])
            self._PIT[name][1] = time + Config.PIT_TIMEOUT #Update timer
            self.numPITIntHits += 1
            #last value indicate whether entry is marked to retain
            if (direct == True) and (self._PIT[name][2] == False):
                self._PIT[name][2] = True
                return 2
            if newSource == True: #New source
                if moreResent == True: return 1
                if largestValidTime < validTime: return 1
            if someResultsAlreadySend == True: return 1
            return 0            
        else: #Not in PIT or timeout. Will size exceed?
            if (name in self._PIT) and (time > self._PIT[name][1]): #expired
                del self._PIT[name]
                self.numPITTimeouts += 1
            if (self._maxSize > 0) and (len(self._PIT) + 1 > self._maxSize):\
                return -1
            self._PIT[name] = [[[source, numRadars, bitMap, validTime, True, \
                                False]], time + Config.PIT_TIMEOUT, direct]
            return 1


    def get(self, name, time, genTime, bitMap):
        '''
        Get sources from PIT. Remove PIT entry
        @param name: Content name without timeout
        @param time: Current time
        @param genTime: Time that content/data was generated 
        @param bitMap: Bit map use to track multiple replies
        @return: [list of nodes to forward data] 
        '''
        self.numPITDataChecks += 1
        if name not in self._PIT: return []
        if time > self._PIT[name][1]: #entry expired
            del self._PIT[name]
            self.numPITTimeouts += 1
            return []
        #Has entry in PIT & not expired
        self.numPITDataHits += 1
        nodes = []
        for i in range(len(self._PIT[name][0])):
            if self._PIT[name][0][i][4] == False: continue #Not active
            if self._PIT[name][0][i][3] > genTime: continue #old content 
            nodes.append(self._PIT[name][0][i][0])
            if self._PIT[name][0][i][1] == -1: #No of radars not given 
                self._PIT[name][0][i][4] = False  #Mark as no longer active
            else: #No of radars are important
                if type(bitMap[0]).__name__ == 'int': #Single bitmap entry
                    allComplete = True
                    for j in range(len(bitMap)): 
                        self._PIT[name][0][i][2][j] += bitMap[j]
                        if (allComplete == True) and (self._PIT[name][0][i][2][j]\
                                 < self._PIT[name][0][i][1]):
                            allComplete = False                      
                    if allComplete == True: self._PIT[name][0][i][4] = False
                else: #Multiple bitmap entries, if data is from cache
                    for k in range(len(bitMap)):
                        allComplete = True
                        for j in range(len(bitMap[k][3])): 
                            self._PIT[name][0][i][2][j] += bitMap[k][3][j]
                            if (allComplete == True) and \
                                (self._PIT[name][0][i][2][j] < \
                                 self._PIT[name][0][i][1]):
                                allComplete = False                      
                        if allComplete == True: 
                            self._PIT[name][0][i][4] = False
                            break
                    assert(bitMap != 0), 'PIT::get Wrong bitmap size ' + \
                                        str(len(bitMap))
                self._PIT[name][0][i][5] = True #BitMap partially full
        remAll = True
        for i in range(len(self._PIT[name][0])):
            if self._PIT[name][0][i][4] == True:
                remAll = False
                break
        if remAll == True: del self._PIT[name]        
        return list(set(nodes)) #Remove duplicates


    def setDirect(self, name):
        '''
        Message was send to application layer.
        @param name: Content name without timeout
        '''
        self._PIT[name][2] = True


    def removeSource(self, name, source):
        '''
        Remove source node from PIT
        @param name: Content name without timeout
        @param source: source node
        '''
        if name not in self._PIT: return
        idx = -1
        for i in range(len(self._PIT[name][0])): #Source already in PIT?
            if self._PIT[name][0][i][0] == source:
                idx = i
                break
        if idx > 0: del self._PIT[name][0][idx]                             


    def getStat(self):
        '''
        Get statistics
        '''
        return [len(self._PIT), self.numPITTimeouts, self.numPITIntHits, \
                self.numPITIntChecks, self.numPITDataHits, self.numPITDataChecks]
