'''
Created on Nov 2, 2011
Modified Dec 29, 2011
@version: 0.11
@author: Dilum Bandara
@email: dilumb@engr.colostate.edu
@copyright: Copyright(c) 2011, Dilum Bandara
@license: GPL
'''

import sys
import math
import copy
import Config as Config
import PeerManager as PM
from CS import CS
from PIT import PIT
from CANNode import CANNode
from CCNData import CCNData

 
class CCNNode(object):
    '''
    Content centric networking class
    '''


    def __init__(self, NID, loc, nodeType, sizeCS, sizePIT, sizeFIB, \
                                                        inOverlay = True):
        '''
        Constructor
        @param NID: Node ID
        @param loc: Location
        @param nodeType: Node type (radar, application, proxy, etc.)   
        @param sizeCS: Size of content store
        @param sizePIT: Size of Pending Interest Table
        @param sizeFIB: Size of Forwarding Information Base
        @param inOverlay: Add to overlay or not 
        '''
        self.NID = NID
        self.loc = loc
        self.type = nodeType
        self._CS = CS(sizeCS, Config.CACHING_POLICY)
        self._PIT = PIT(sizePIT)        
        self._CAN = CANNode(NID, loc, nodeType, sizeFIB)
        self.inOverlay = inOverlay
        if self._CAN.joinOverlay(inOverlay) == False: #Join overlay
            print 'CCNNode:init Unable to join overlay. Terminating'
            sys.exit()
        self.numInterestsRec = 0
        self.numInterestsFwd = 0
        self.numDataPacketsRec = 0
        self.numDataPacketsFwd = 0
        self.totalInterestsRec = 0
        self.totalInterestsFwd = 0
        self.totalDataRec = 0
        self.totalDataFwd = 0
         
                    
    def fwdInter(self, msg, nextHop = -1):
        '''
        Forward interest message. Called by application layer
        @param msg: Message
        @param nextHop: Next hop node if known
        @return: 
        '''
        if msg.msgType != 'CCNInt':
            print 'CCNNode::fwdInter Unknown msg type. Terminating', msg.type
            sys.exit()
        if nextHop != -1: msg.setDirect() #Send direct
        return self._handleIntMsg(msg, self.NID, nextHop)
    
        
    def fwdData(self, msg):
        '''
        Forward data. Call by application layer
        @param msg: Data message
        '''
        if msg.msgType != 'CCNData':
            print 'CCNNode::fwdData Unknown msg type. Terminating', msg.type
            sys.exit()        
        nodes = self._PIT.get(msg.name, PM.EVENTS.now, msg.genTime, msg.data)
        if len(nodes) == 0: return
            #print 'CCNNode::fwdData PIT timeout or unknown interest at', self.NID
        else: #Forward to each face that had an interest
            for i in nodes:
                if i == self.NID: PM.APPS[self.NID].recMsg(msg) 
                else:
                    self.numDataPacketsFwd += 1
                    self.totalDataFwd += msg.size 
                    PM.UNDERLAY[self.NID].fwdMsg(msg, i)
        if self.type != 'RADAR': #Radar should not cache its own data, its source
            result = self._CS.put(msg.name, msg.dataSize, msg.data, msg.genTime,\
                                  msg.source, msg.sourceLoc, PM.EVENTS.now)
            if result == -1: pass#Add to CS
                #print 'CCNNode::fwdData data size does not match', self.NID
            elif result == -2:
                print 'CCNNode::fwdData unknown caching policy', \
                                                            Config.CACHING_POLICY          
        
        
    def recMsg(self, msg, sourceNID):
        '''
        Receive message from underlay. Handle accordingly
        @param msg: Message
        @param sourceNID: Node ID of neighbor that send/fwd message
        '''
        if msg.msgType == 'OvJoin' or msg.msgType == 'OvJoinReply' or msg.msgType\
            == 'OvAddNbr' or msg.msgType == 'OvRemoveNbr' or msg.msgType == \
            'OvAppNode' or msg.msgType == 'OvRejoin': #Related to underlay
            self._CAN.recMsg(msg, sourceNID)
        elif msg.msgType == 'CCNInt': self._handleIntMsg(msg, sourceNID)
        elif msg.msgType == 'CCNData': self._handleDataMsg(msg, sourceNID)
        else: print 'CCNNode::recMsg Unknown msg type', msg.msgType
          
        
    def _handleIntMsg(self, msg, sourceNID, destNID = -1):
        '''
        Handle interest message. First check CS & PIT. If failed find next hop 
        neighbor & forward to it. If message for me, forward to app layer
        @param msg: Message
        @param sourceNID: source node
        @param destNID: Destination node, if known. - If so send direct   
        @return: CCNData message if in cache
        '''
        self.numInterestsRec += 1
        self.totalInterestsRec += msg.size
        data = self._CS.get(msg.name, msg.time, PM.EVENTS.now)
        if len(data) > 0: #All or part of data in cache.            
            if (msg.app == 'RADAR') and (msg.dataType == 'SUBS'):
                tmpData = copy.copy(data[0][3])
                for i in range(len(data) - 1):
                    if len(tmpData) != len(data[i + 1][3]):
                        print 'CCNNode::_handleIntMsg print BitMap size different'\
                            , len(tmpData), data[i + 1][3]
                        sys.exit()
                    for j in range(len(data[i + 1][3])): 
                        tmpData[j] += data[i + 1][3][j]
                allComplete = True
                for i in range(len(tmpData)): #Check whether all pixels are there
                    if tmpData[i] < msg.numRadars:
                        allComplete = False
                        break
                if allComplete == True:  #Have all the data
                    oldest = 999999.9
                    for i in range(len(data)):
                        if data[i][4] < oldest: oldest = data[i][4]
                    newMsg = CCNData(msg.name, msg.realName, data[0][2],\
                            data, oldest, msg.hops, -1, None)
                    if sourceNID == self.NID: #To my application layer
                        return newMsg
                    else: #To a remote node
                        if PM.UNDERLAY[self.NID].fwdMsg(newMsg, sourceNID) == \
                                                                        False:
                            print 'CCNNode::_handleIntMsg Unable to send at', \
                                self.NID
                        return None #Found all
            else: #Other data types. Suppose to cache data as 1 item
                newMsg = CCNData(msg.name, msg.realName, data[0][2],\
                         data, data[0][4], msg.hops, data[0][0], data[0][1])
                if sourceNID == self.NID: #To my application layer 
                    return newMsg                  
                else: #To a remote node
                    if PM.UNDERLAY[self.NID].fwdMsg(newMsg, sourceNID) == False:
                        print 'CCNNode::_handleIntMsg Unable to send at', self.NID
                return None
        #Not in cache. First find how to forward, then add to PIT only if needed
        if (msg.app == 'RADAR') and (msg.dataType == 'SUBS'): #Subscription
            #Direct message & not from my app layer
            if msg.direct == True and sourceNID != self.NID:
                result = self._PIT.put(msg.name, sourceNID, PM.EVENTS.now, \
                                       msg.time)
                if result == 0: return None #Already in PIT 
                elif result == 1: #Need to forward
                    PM.APPS[self.NID].recMsg(msg)
                    return None
                elif result == -1 : 
                    print 'CCNNode::_handleIntMsg PIT capacity exceed, Node', \
                        self.NID
                    sys.exit()
            nextHop = self._CAN.getNextHopRadar(msg.loc, Config.RADAR_RANGE)
            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))
            
            if nextHop != -1: #Local radar
                radars = PM.APPS[nextHop].getRadarsInRange(msg.minLoc, msg.maxLoc)
                result = self._PIT.put(msg.name, sourceNID, PM.EVENTS.now, \
                        msg.time, msg.numRadars, [0] * (numTilesX * numTilesY))
                for k in radars:
                    if k == self.NID: PM.APPS[self.NID].recMsg(msg)
                    else:
                        msg.setDirect()  #Send direct
                        msg.incrementHops()
                        self.numInterestsFwd += 1
                        self.totalInterestsFwd += msg.size
                        if PM.UNDERLAY[self.NID].fwdMsg(msg, k) == False:
                            print 'CCNNode::_handleIntMsg Unable to send direct at',\
                             self.NID
            else:
                result = self._PIT.put(msg.name, sourceNID, PM.EVENTS.now,\
                        msg.time, msg.numRadars, [0] * (numTilesX * numTilesY))
                if result == 0: return None #Already in PIT 
                elif result == 1: #Need to forward
                    msg.incrementHops()
                    self.numInterestsFwd += 1
                    self.totalInterestsFwd += msg.size
                    nextHop = self._CAN.getNextHop(msg.loc)
                    if PM.UNDERLAY[self.NID].fwdMsg(msg, nextHop[0]) == False:
                        print 'CCNNode::_handleIntMsg Unable to forward at', \
                            self.NID
                elif result == -1 : 
                    print 'CCNNode::_handleIntMsg PIT capacity exceed, Node', \
                        self.NID
                    sys.exit()
        else: #Data request interest from Proxy or App
            result = self._PIT.put(msg.name, sourceNID, PM.EVENTS.now, msg.time,\
                                   -1, [], msg.direct)
            if result == 0: return None #Already in PIT 
            elif result == 1: #Need to forward
                #Direct message & not from my app layer
                if msg.direct == True and sourceNID != self.NID:
                    PM.APPS[self.NID].recMsg(msg)
                elif destNID != -1: #Send direct
                    if destNID == self.NID: #For me. Forward to app layer 
                        self._PIT.setDirect(msg.name) 
                        PM.APPS[self.NID].recMsg(msg)
                    else:
                        msg.incrementHops()
                        self.numInterestsFwd += 1
                        self.totalInterestsFwd += msg.size
                        if PM.UNDERLAY[self.NID].fwdMsg(msg, destNID) == False:
                            print 'CCNNode::_handleIntMsg Unable to send direct at',\
                                self.NID
                else:
                    nextHop = self._CAN.getNextHop(msg.loc, msg.app)
                    assert(nextHop[0] != -1), 'CCNNode::_handleIntMsg No next hop'
                    if nextHop[0] == self.NID: #For me. Forward to app layer
                        self._PIT.setDirect(msg.name) 
                        PM.APPS[self.NID].recMsg(msg)
                    else:
                        msg.incrementHops()
                        self.numInterestsFwd += 1
                        self.totalInterestsFwd += msg.size
                        #If no longer greedy forwarding, send direct
                        if nextHop[1] == False: msg.setDirect()
                        if nextHop[0] == sourceNID: #resend to sender
                            self._PIT.removeSource(msg.name, sourceNID) #Don't add to PIT                         
                        if PM.UNDERLAY[self.NID].fwdMsg(msg, nextHop[0]) == False:
                            print 'CCNNode::_handleIntMsg Unable to send at',\
                                self.NID
            elif result == 2: #Need to forward to app
                if msg.direct == True and sourceNID != self.NID: #Not from my app
                    PM.APPS[self.NID].recMsg(msg)
            elif result == -1 : 
                print 'CCNNode::_handleIntMsg PIT capacity exceed, Node', self.NID
                sys.exit()    
        return None


    def _handleDataMsg(self, msg, sourceNID):
        '''
        Handle data from overlay
        @param msg: Message
        @param sourceNID: Source node  
        '''
        self.numDataPacketsRec += 1
        self.totalDataRec += msg.size
        nodes = self._PIT.get(msg.name, PM.EVENTS.now, msg.genTime, msg.data)
        if len(nodes) == 0: return
            #print 'CCNNode::_handleDataMsg PIT timeout or unknown interest at',\
            #    self.NID
        else: #Forward to each face that had an interest
            for i in nodes:
                if i == self.NID: PM.APPS[self.NID].recMsg(msg) 
                else:
                    self.numDataPacketsFwd += 1
                    self.totalDataFwd += msg.size
                    PM.UNDERLAY[self.NID].fwdMsg(msg, i)
        #Add to cache. Check whether data is from remote cache        
        if (len(msg.data) == 0) or (type(msg.data[0]).__name__ == 'int'):
            self._CS.put(msg.name, msg.dataSize, msg.data, msg.genTime,\
                              msg.source, msg.sourceLoc, PM.EVENTS.now)
        else:
            for i in range(len(msg.data)):
                self._CS.put(msg.name, msg.data[i][2], msg.data[i][3],\
                msg.data[i][4], msg.data[i][0], msg.data[i][1], PM.EVENTS.now)
        #if result == -1: pass#Add to CS
            #print 'CCNNode::_handleDataMsg data size does not match', self.NID

    
    
    def addNodeToFIB(self, NID, loc, zone, nodeType, inOverlay):
        '''
        Add this node to routing table
        @param NID: Node ID of radar to add
        @param loc: Location 
        @param zone: Zone of node
        @param nodeType: Node type
        @param inOverlay: Is a fully functional overlay node    
        '''
        self._CAN.addNodeToFIB(NID, loc, zone, nodeType, inOverlay)


    def advAppsInFIB(self):
        '''
        Advertise applications in my FIB to neighbors
        '''
        self._CAN.advAppsInFIB()
        

    def getConnectedApps(self, appList):
        '''
        Get list of applications that are directly connected to this node.
        Function is supposed to be called by radar
        @param appList: List of applications to get info about 
        @result: List of NIDs of applications
        '''
        return self._CAN.getConnectedApps(appList)
 
 
    def rejoinOverlay(self):
        '''
        Rejoin overlay
        '''
        self._CAN.joinOverlay(self.inOverlay)
        

    def getZone(self):
        '''
        @return: Get the zone
        '''
        return self._CAN.zone
        
    
    def getStat(self):
        '''
        Get statistics
        @return: results
        '''
        result = ['CS']
        tmp = self._CS.getStat()
        for i in range(len(tmp)): result.append(tmp[i])
        result.append('PIT')
        tmp = self._PIT.getStat()
        for i in range(len(tmp)): result.append(tmp[i])
        result.append('CAN')
        tmp = self._CAN.getStat()
        for i in range(len(tmp)): result.append(tmp[i])
        result.append('CCN')
        result.append(self.numInterestsRec)
        result.append(self.numInterestsFwd)
        result.append(self.numDataPacketsRec)
        result.append(self.numDataPacketsFwd)
        result.append(self.totalInterestsRec)
        result.append(self.totalInterestsFwd)
        result.append(self.totalDataRec)
        result.append(self.totalDataFwd)
        return result
