'''
Created on Sep 2, 2012
@version: 0.1
@author: Dilum Bandara
@email: dilumb@engr.colostate.edu

   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 Config as Config
import Distance as Distance

class FIB(object):
    '''
    Forward Information Base (FIB) class
    '''


    def __init__(self, maxSize):
        '''
        Constructor
        @param maxSize: maximum size 
        '''
        self._maxSize = maxSize
        self._FIB = {} #Routing table for CCN
        self.numFIBUpdates = 0


    def __del__(self):
        '''
        Destructor
        '''
        self._FIB = None


    def put(self, NID, loc, zone):
        '''
        Add to FIB
        @param NID: Node ID
        @param loc: Location of node as [x, y, z, ...]
        @param zone: Zone assigned to node
        @return: False - unable to add, True - success   
        '''       
#        if inOverlay == True:
#            assert(zone), 'Zone can\'t be None if node is a zone'
        if NID in self._FIB: #Update
            self._FIB[NID] = [loc, zone] 
        else: #Add new one
            if (self._maxSize > 0) and (len(self._FIB) + 1 > self._maxSize):
                return False
            self._FIB[NID] = [loc, zone]
        self.numFIBUpdates += 1
        return True

    
    def remove(self, NID):
        '''
        Remove from FIB
        @param NID: Node ID
        @return: False - Failed, True - Success 
        '''
        if NID in self._FIB: 
            del self._FIB[NID]
            return True
        else: return False


    def getNextHop(self, loc):
        '''
        Find next hop to forward message.
        @param loc: Location to route to
        @return: next hop, -1 if not able to find         
        '''
        lowestDist = 999999.9
        idx = -1
        for i in self._FIB:
            if self._FIB[i][1] is None: continue #Not a zone
            if Distance.inZone(loc, self._FIB[i][1]) == True: #In neighbors zone
                return i
            dist = Distance.distPZ(loc, self._FIB[i][1])
            if (dist < lowestDist):
                lowestDist = dist 
                idx = i
        return idx


    def getLocalNextHop(self, loc):
        '''
        Get closet nodes among entries in FIB
        @param loc: Location
        @return: Node ID  
        '''
        lowestDist = 999999.9
        idx = -1
        for i in self._FIB:
            dist = Distance.distPP(loc, self._FIB[i][0])
            if (dist < lowestDist):
                lowestDist = dist 
                idx = i
        return [idx, lowestDist]


    def getNodeInRange(self, loc, nodeRange):
        '''
        Find any 1 node in my FIB that covers the given location
        @param loc: Middle point of region of interest
        @param nodeRange: Maximum distance to be acceptable as in range of node
        @return: Next hop or -1 if not  
        '''
        for i in self._FIB: #If any of radars in my FIB within coverage
            if Distance.inRange(loc, self._FIB[i][0], nodeRange) == True:
                return i
        return -1

   
    def nbrsToUpdate(self, myZone, newZone):
        '''
        Determine which neighbors to remove, add, move, & update based on newly
        formed zone.
        @param myZone: Zone of the node having the FIB 
        @param newZone: New neighbor's zone
        @return: List of new neighbors to add, old neighbors to remove, to move,
        & update
        '''
        nbrsToAdd = []
        nbrsToRemove = []
        nbrsToMove = []
        nbrsToUpdate = []
        for nID in self._FIB:            
            if self._FIB[nID][1] is None: #Not a zone. Move them, if required
                if Distance.inZone(self._FIB[nID][0], newZone) == True:
                    nbrsToMove.append(nID)
                    nbrsToRemove.append(nID)
                else:
                    nbrsToUpdate.append(nID)#Still in my zone. Inform my new zone
                continue
            #Overlap in 1 dimension & abut (adjacent) in the other
            if self.isNbrZone(newZone, self._FIB[nID][1]) == True:
                nbrsToAdd.append([nID] + self._FIB[nID])
            #Remove nodes that are no longer my neighbors 
            if self.isNbrZone(myZone, self._FIB[nID][1]) == True: 
                nbrsToUpdate.append(nID) #Inform my new zone  
            else: nbrsToRemove.append(nID) #No longer a neighbor. Remove
        
        for j in nbrsToRemove: del self._FIB[j]  #Remove old neighbors
        return [nbrsToAdd, nbrsToRemove, nbrsToMove, nbrsToUpdate]


    def routingTableSize(self):
        '''
        Get routing table size
        '''
        return len(self._FIB)
      

    def getNbrZones(self):
        '''
        Get list of neighboring zones
        @return: List of neighbor zones
        '''
        nbrs = []
        for i in self._FIB: #Is a zone & direct neighbor
            if self._FIB[i][1] is not None: nbrs.append(i)
        return nbrs


    def checkNode(self, NID):
        '''
        Check whether given node in routing table
        @param NID: NID to check
        @requires: Success or failure 
        '''
        if NID in self._FIB: return True
        else: return False


    def isNbrZone(self, zoneA, zoneB):
        '''
        Are given zones neighbors
        @param zoneA: First zone to compare
        @param zoneB: Second zone to compare
        @return: True or False
        '''
        if zoneA is None or zoneB is None: return False
        overlap = [False] * Config.NUM_DIMENSIONS
        #Overlap in 1 dimension & abut (adjacent) in the other
        for i in range(Config.NUM_DIMENSIONS):
            if (zoneA.min[i] <= zoneB.min[i] and zoneA.max[i] >= zoneB.max[i])\
                or (zoneA.min[i] >= zoneB.min[i] and \
                    zoneA.max[i] <= zoneB.max[i]):
                overlap[i] = True
        for i in range(Config.NUM_DIMENSIONS):
            if overlap[i] == False:
                if zoneA.min[i] == zoneB.max[i] or zoneA.max[i] == zoneB.min[i]\
                    or (zoneA.min[i] == 0 and zoneB.max[i] == \
                        Config.SIZE_TORUS[i]) or \
                        (zoneA.max[i] == Config.SIZE_TORUS[i] and \
                         zoneB.min[i] == 0): 
                    overlap[i] = True
                    break #No more than 1 overlap. So break
        if False in overlap: return False 
        else: return True


    def getStat(self):
        '''
        Get statistics
        @return: Stat        
        '''
        return [len(self._FIB), self.numFIBUpdates]
