'''
Created on Nov 2, 2011
Modified Feb 29, 2012
@version: 0.2
@author: Dilum Bandara
@email: dilumb@engr.colostate.edu
@license: Apache License v2.0

   Copyright 2012 H. M. N. Dilum Bandara and Anura P. Jayasumana, Colorado State
   University

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''

import math
import Config as Config
import PeerManager as PM
import Distance as Distance
from CCNData import CCNData
from Location import Location


class Radar(object):
    '''
    Radar class.
    '''

    def __init__(self, NID, loc):
        '''
        Constructor
        @param NID: Node ID 
        @param loc: Location 
        '''
        self.NID = NID
        self.loc = loc
        self.type = 'RADAR'
        self._lastGenTime = -1 #last time data was generated
        self._subs = {}
        self._penInter = {}
        self.TotalSubDataFwd = 0 #amount of subscription data forward by radar
        self.TotalAppDataFwd = 0 #amount of application level data forward by radar
        self.bitMapCache = {} #cache generated bitMaps
        self.radarsInRangeCache = {}

    
    def recMsg(self, msg):
        '''
        Receive message from CCN
        @param msg: Message
        '''
        if msg.msgType == 'CCNInt':
            if (msg.app == 'RADAR') and (msg.dataType == 'SUBS'):#Subscription
                bitMap = self._genBitMap(msg.minLoc, msg.maxLoc)
                if bitMap[1] == True: return #Not in my range, discard
                
                if msg.time > self._lastGenTime: #Interest for future data
                    if msg.time in self._subs:
                        self._subs[msg.time].append([msg, bitMap[0]])
                    else: self._subs[msg.time] = [[msg, bitMap[0]]]
                else:
                    newMsg = CCNData(msg.name, msg.realName, len(bitMap[0])\
                            , bitMap[0], self._lastGenTime, msg.hops, \
                            self.NID, self.loc)
                    PM.OVERLAY[self.NID].fwdData(newMsg)
                    self.TotalSubDataFwd += len(bitMap[0])
            else: #Data request
                if msg.time > self._lastGenTime: #Interest is for future data
                    if msg.time in self._penInter: 
                        self._penInter[msg.time].append(msg)
                    else: self._penInter[msg.time] = [msg]
                else:
                    data = math.ceil(msg.bitMap.count(1) * Config.PIXEL_DATA_SIZE)
                    newMsg = CCNData(msg.name, msg.realName, data, msg.bitMap, \
                                self._lastGenTime, msg.hops, self.NID, self.loc)
                    PM.OVERLAY[self.NID].fwdData(newMsg)
                    self.TotalAppDataFwd += data
        else: assert(False), 'Message type not handled ' + msg.type


    def genNewData(self, time):
        '''
        Generate new data now
        @param time: Current time  
        '''
        self._lastGenTime = time
        toRemove = []
        for i in self._subs: #Find subscriptions that can be satisfied
            if i > time: continue #for future
            for j in range(len(self._subs[i])):
                newMsg = CCNData(self._subs[i][j][0].name, \
                        self._subs[i][j][0].realName, \
                        len(self._subs[i][j][1]),\
                        self._subs[i][j][1], time, \
                        self._subs[i][j][0].hops, self.NID, self.loc)                
                PM.OVERLAY[self.NID].fwdData(newMsg)
                self.TotalSubDataFwd += len(self._subs[i][j][1])
            toRemove.append(i)
        for k in toRemove: del self._subs[k] #Remove answered subscriptions
        
        #Send respond to data requests
        toRemove = []
        for i in self._penInter: #Find interests that can be satisfied
            if i > time: continue #For future
            for j in range(len(self._penInter[i])):
                data = math.ceil(self._penInter[i][j].bitMap.count(1) * \
                                 Config.PIXEL_DATA_SIZE)
                newMsg = CCNData(self._penInter[i][j].name, \
                        self._penInter[i][j].realName, data, \
                        self._penInter[i][j].bitMap, time, \
                        self._penInter[i][j].hops, self.NID, self.loc)
                PM.OVERLAY[self.NID].fwdData(newMsg)
                self.TotalAppDataFwd += data
            toRemove.append(i)
        for k in toRemove: del self._penInter[k] #Remove answered interests


    def _genBitMap(self, minLoc, maxLoc):
        '''
        Generate bitMap of tiles that I can generate
        @param minLoc: Lower edge of area of interest
        @param maxLoc: Upper edge of area of interest
        @return: [bit map, are all entries 0]  
        '''
        tmpName = str(minLoc.x) + str(minLoc.y) + str(maxLoc.x) + str(maxLoc.y)
        if tmpName in self.bitMapCache: return self.bitMapCache[tmpName]
        bitMap = []
        allZero = True
        #Split to tiles
        numTilesX = int(math.ceil((maxLoc.x - minLoc.x)/Config.TILE_X_RADAR))
        numTilesY = int(math.ceil((maxLoc.y - minLoc.y)/Config.TILE_Y_RADAR))
        for i in range(numTilesX):
            for j in range(numTilesY):
                #ll-Lower left corner, lr-Lower right, ul-Upper left, ur-Upper right
                ll = Location(minLoc.x + i * Config.TILE_X_RADAR, \
                              minLoc.y + j * Config.TILE_Y_RADAR)
                lr = Location(minLoc.x + (i + 1) * Config.TILE_X_RADAR, \
                              minLoc.y + j * Config.TILE_Y_RADAR)
                ul = Location(minLoc.x + i * Config.TILE_X_RADAR, \
                              minLoc.y + (j + 1) * Config.TILE_Y_RADAR)
                ur = Location(minLoc.x + (i + 1) * Config.TILE_X_RADAR,\
                              minLoc.y + (j + 1) * Config.TILE_Y_RADAR)
                if Distance.inRange(ll, self.loc, Config.RADAR_RANGE) and\
                    Distance.inRange(lr, self.loc, Config.RADAR_RANGE) and\
                    Distance.inRange(ul, self.loc, Config.RADAR_RANGE) and\
                    Distance.inRange(ur, self.loc, Config.RADAR_RANGE):
                    bitMap.append(1) #In range
                    allZero = False
                else: bitMap.append(0) #Not in range
        self.bitMapCache[tmpName] = [bitMap, allZero]
        return [bitMap, allZero]
            

    def getRadarsInRange(self, minLoc, maxLoc):
        '''
        Find which neighboring radars at least partially cover given region
        @param minLoc: Lower edge of area of interest
        @param maxLoc: Upper edge of area of interest
        @return: [Radars fully/partially overlapped]
        '''
        tmpName = str(minLoc.x) + str(minLoc.y) + str(maxLoc.x) + str(maxLoc.y)
        if tmpName in self.radarsInRangeCache: 
            return self.radarsInRangeCache[tmpName]
        localRadars = PM.OVERLAY[self.NID].getConnectedApps(['RADAR'])
        radars = []
        #Split to tiles
        numTilesX = int(math.ceil((maxLoc.x - minLoc.x)/Config.TILE_X_RADAR))
        numTilesY = int(math.ceil((maxLoc.y - minLoc.y)/Config.TILE_Y_RADAR))
        for i in range(numTilesX):
            for j in range(numTilesY):
                #ll-Lower left corner, lr-Lower right, ul-Upper left, ur-Upper right
                ll = Location(minLoc.x + i * Config.TILE_X_RADAR, \
                              minLoc.y + j * Config.TILE_Y_RADAR)
                lr = Location(minLoc.x + (i + 1) * Config.TILE_X_RADAR, \
                              minLoc.y + j * Config.TILE_Y_RADAR)
                ul = Location(minLoc.x + i * Config.TILE_X_RADAR, \
                              minLoc.y + (j + 1) * Config.TILE_Y_RADAR)
                ur = Location(minLoc.x + (i + 1) * Config.TILE_X_RADAR,\
                              minLoc.y + (j + 1) * Config.TILE_Y_RADAR)
                for k in range(len(localRadars)): #Is in range of radar?
                    if localRadars[k][0] in radars: continue #Don't repeat
                    if Distance.inRange(ll, localRadars[k][1], Config.RADAR_RANGE)\
                        and Distance.inRange(lr, localRadars[k][1], \
                        Config.RADAR_RANGE) and Distance.inRange(ul, \
                        localRadars[k][1], Config.RADAR_RANGE) and \
                        Distance.inRange(ur, localRadars[k][1], Config.RADAR_RANGE):
                        radars.append(localRadars[k][0])
        self.radarsInRangeCache[tmpName] = radars
        return radars

            
    def getStat(self):
        '''
        Get statistics
        '''
        return [len(self._penInter), len(self._subs), self.TotalSubDataFwd,\
               self.TotalAppDataFwd]
