'''
Created on Jan 25, 2011
Modified on Apr 25, 2011
@author: Dilum Bandara
@version: 0.22
@copyright: Copyright(c) 2011, Dilum Bandara
@license: GPL
'''

import BaseConfig
from BaseQueryMessage import BaseQueryMessage
import math
import copy

class BasePeer(object):
    '''
    Base Node class. Contains node ID, key, its attribute set, & list of resources
    indexed at node
    '''
    def __init__(self, id, keys, num_rings):
        '''
        @param id: Node ID
        @param keys: List of keys of the node
        @param num_rings: Number of rings. 
        '''
        self._id = id
        self.keys = keys
        self._last_att_vals = []
        if num_rings == 0: num_rings = 1 #At least 1 ring will be used to maintain index
        self._idx = []
        self._max_idx_size = []
        for i in range(num_rings): 
            self._idx.append([])
            self._max_idx_size.append(0)
        self._num_updates = 0
        self._num_updates_rec = 0
        self._num_que_send = 0
        self._num_que_fwd = 0
        self._num_que_ans = 0


    def advertise(self, res, use_thresholds, need_all_attri):
        '''
        Advertise attribute values that are different from last update   
        @param res: Resource attributes to advertise
        @param use_thresholds: Use threshold to decide whether attribute changed
        @param need_all_attri: Do we need to advertise all attribs
        '''
        contri_atts = []
        atts = self._has_res_changed(res, use_thresholds, need_all_attri)
        if len(atts) > 0: #Need to advertise            
            hops = [[0], [0]]                     
            self._num_updates += 1
            #First remove then add
            if len(atts[1]) > 0: #Need to remove
                result = BaseConfig.OVERLAY.route_old_ads(self._id, atts[1])
                if len(result) == 0:
                    print 'ERROR: Peer ' + self._id + ' unable to route rem'
                    return []
                hops[1] = result
            result = BaseConfig.OVERLAY.route_new_ads(self._id, atts[0])
            if len(result) == 0:
                print 'ERROR: Peer ' + self._id + ' unable to route adv'
                return []
            hops[0] = result
            #Track which attribute contribute to any update

            if len(atts[0]) > 0 and len(atts[1]) == 0: #1st time update
                for i in range(len(atts[0])): contri_atts.append(atts[0][i][0])
            elif len(atts[1]) > 0 : #caused by changing attribute value(s)
                for i in range(len(atts[1])): contri_atts.append(atts[1][i][0])
            hops.append(contri_atts)
            return hops                
        return [[0], [0], contri_atts] #Update not send so no cost
        

    def query(self, que):
        '''
        Forward query to overlay
        @param que: Query given as a list
        @return: Success/failure as [hops, success]. If hops = -1 error
        '''
        que_msg = BaseQueryMessage(self._id, que[0][1], copy.deepcopy(que[1:]))
        self._num_que_send += 1
        result = BaseConfig.OVERLAY.route_query(que_msg) #Query superpeer
        if result.hops == -1: print 'ERROR: Peer ' + self._id + ' unable to route query'
        #Track which attribute contribute to query cost
        contri_atts = []
        for i in range(len(que_msg.query)): contri_atts.append(que_msg.query[i][0])
        return [result.hops, result.success, contri_atts] #Return hop count & query success/fail


    def add_to_index(self, source, ress, ring_num = -1):
        '''
        Add given resource to index. Update if already exists
        @param source: Node ID
        @param ress: List of resource attribute values. Given as a list of lists
        @param ring_num: Which ring to use
        @return: Success/failure
        '''
        if source != self._id: self._num_updates_rec += 1
        if ring_num == -1: ring = 0
        else: ring = ring_num

        for i in range(len(self._idx[ring])): #Find existing entry
            if self._idx[ring][i][0] == source:
                self._idx[ring][i][1] = copy.deepcopy(ress) #update with new values
                return True
        #Doesn't exist. Add new one
        self._idx[ring].append([source, copy.deepcopy(ress)])
        #Update index size
        if self._max_idx_size[ring] < len(self._idx[ring]): 
            self._max_idx_size[ring] = len(self._idx[ring])
        return True


    def remove_from_index(self, source, res, ring_num = -1):
        '''
        Remove given entry from index   
        @param source: Source node ID
        @param res: Resource
        @param ring_num: Which ring to use
        '''
        if ring_num == -1: ring = 0
        else: ring = ring_num
        for i in range(len(self._idx[ring])): #Find existing entry
            if self._idx[ring][i][0] == source:
                del self._idx[ring][i]  #Remove old value
                return True
        return False #Unable to find resource


    def query_index(self, que_msg, ring_num = -1):
        '''
        Check whether index has what is need to satisfy query
        @param que_msg: Query message
        @param ring_num: Which ring to use
        @return: Modified query message
        '''
        results = []
        att_to_que = []
        if ring_num == -1: ring = 0
        else: ring = ring_num
        
        n = que_msg.n - que_msg.num_res_found #How many resources required
        for i in range(len(que_msg.query)): #List of attributes to query
            att_to_que.append(que_msg.query[i][0])
        for i in range(len(self._idx[ring])): #Check all entries
            attribs_match = False
            for j in range(len(self._idx[ring][i][1])): #For each entry check all attribs
                tmp_att_name = self._idx[ring][i][1][j][0]
                if tmp_att_name in att_to_que: #Does the query ask this attribute
                    if (que_msg.query[att_to_que.index(tmp_att_name)][1] \
                            <= self._idx[ring][i][1][j][1]) and (self._idx[ring][i][1][j][1] <= \
                            que_msg.query[att_to_que.index(tmp_att_name)][2]):
                        attribs_match = True
                    else: #At least one attribute mismatch
                        attribs_match = False
                        break
            if attribs_match == True: #If all required attributes match
                results.append(self._idx[ring][i])
                if len(results) == n: break #found enough resources
        if len(results) > 0:
            self._num_que_ans += 1
            que_msg.num_res_found += len(results)
        return que_msg 


    def _has_res_changed(self, res, use_threshold, need_all_att):
        '''
        Has the resource attributes changed 
        @param res: Resource attribute set
        @param use_threshold: Use threshold to overcome minor variations
        @param need_all_att: Do we need to return all attributes, even ones that didn't changed
        @return: New list of attribute values 
        '''
        atts = copy.deepcopy(res)
        self._att_vals = atts
        atts_to_adv = []  #Attributes to be advertised
        atts_to_remove = []  #Attributes to be re-advertised
        #Never addvertised or no threshold used. Just send as it is
        if len(self._last_att_vals) == 0 or use_threshold == False: 
            if self._last_att_vals != atts: #Changed from last value
                tmp = self._last_att_vals
                self._last_att_vals = atts
                return [self._last_att_vals, tmp]
            else: return []
        else:
            for i in range(len(atts)):  #For each attribute check
            #Even static attributes can change once in a while
                if atts[i][0] == 'RspT':  #If attrib value has changed significantly
                    respTime = atts[i][1]
                    if (respTime >= self._last_att_vals[i][1] + BaseConfig.T_RESP_TIME) \
                        or (respTime <= self._last_att_vals[i][1] - BaseConfig.T_RESP_TIME):
                        #This needs to be advertised
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = respTime
                elif atts[i][0] == 'SIn': 
                    swapIn = atts[i][1]
                    if (swapIn >= self._last_att_vals[i][1] + BaseConfig.T_SWAP_IN) \
                        or (swapIn <= self._last_att_vals[i][1] - BaseConfig.T_SWAP_IN):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = swapIn
                elif atts[i][0] == 'SOut': 
                    swapOut = atts[i][1]
                    if (swapOut >= self._last_att_vals[i][1] + BaseConfig.T_SWAP_OUT) \
                        or (swapOut <= self._last_att_vals[i][1] - BaseConfig.T_SWAP_OUT):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = swapOut
                elif atts[i][0] == 'DIn':
                    diskIn = atts[i][1]
                    if (diskIn >= self._last_att_vals[i][1] + BaseConfig.T_DISK_IN) \
                        or (diskIn <= self._last_att_vals[i][1] - BaseConfig.T_DISK_IN):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskIn
                elif atts[i][0] == 'DOut':
                    diskOut = atts[i][1]
                    if (diskOut >= self._last_att_vals[i][1] + BaseConfig.T_DISK_OUT) \
                        or (diskOut <= self._last_att_vals[i][1] - BaseConfig.T_DISK_OUT):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskOut
                elif atts[i][0] == 'DSvc':
                    diskSvc = atts[i][1]
                    if (diskSvc >= self._last_att_vals[i][1] + BaseConfig.T_DISK_SVC) \
                        or (diskSvc <= self._last_att_vals[i][1] - BaseConfig.T_DISK_SVC):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskSvc
                elif atts[i][0] == 'DUtil':
                    diskUtil = atts[i][1]
                    if (diskUtil >= self._last_att_vals[i][1] + BaseConfig.T_DISK_UTIL) \
                        or (diskUtil <= self._last_att_vals[i][1] - BaseConfig.T_DISK_UTIL):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskUtil
                elif atts[i][0] == 'UpT':
                    upTime = atts[i][1]
                    if (upTime >= self._last_att_vals[i][1] + BaseConfig.T_UPTIME) \
                        or (upTime <= self._last_att_vals[i][1] - BaseConfig.T_UPTIME):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = upTime
                elif atts[i][0] == '1mLd':
                    oneMLoad = atts[i][1]
                    if (oneMLoad >= self._last_att_vals[i][1] + BaseConfig.T_1_MIN_LOAD) \
                        or (oneMLoad <= self._last_att_vals[i][1] - BaseConfig.T_1_MIN_LOAD):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = oneMLoad
                elif atts[i][0] == '5mLd': 
                    fiveMLoad = atts[i][1]
                    if (fiveMLoad >= self._last_att_vals[i][1] + BaseConfig.T_5_MIN_LOAD) \
                        or (fiveMLoad <= self._last_att_vals[i][1] - BaseConfig.T_5_MIN_LOAD):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = fiveMLoad
                elif atts[i][0] == '15mLd':
                    fifteenMLoad = atts[i][1]
                    if (fifteenMLoad >= self._last_att_vals[i][1] + BaseConfig.T_15_MIN_LOAD) \
                        or (fifteenMLoad <= self._last_att_vals[i][1] - BaseConfig.T_15_MIN_LOAD):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = fifteenMLoad
                elif atts[i][0] == 'TMax':
                    timerMax = atts[i][1]
                    if (timerMax >= self._last_att_vals[i][1] + BaseConfig.T_TIMER_MAX) \
                        or (timerMax <= self._last_att_vals[i][1] - BaseConfig.T_TIMER_MAX):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = timerMax
                elif atts[i][0] == 'TAve':
                    timerAve = atts[i][1]
                    if (timerAve >= self._last_att_vals[i][1] + BaseConfig.T_TIMER_AVE) \
                        or (timerAve <= self._last_att_vals[i][1] - BaseConfig.T_TIMER_AVE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = timerAve
                elif atts[i][0] == 'MFree':
                    memFree = atts[i][1]
                    if (memFree >= self._last_att_vals[i][1] + BaseConfig.T_MEM_FREE) \
                        or (memFree <= self._last_att_vals[i][1] - BaseConfig.T_MEM_FREE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = memFree
                elif atts[i][0] == 'DUsed': 
                    diskUsed = atts[i][1]
                    if (diskUsed >= self._last_att_vals[i][1] + BaseConfig.T_DISK_USED) \
                        or (diskUsed <= self._last_att_vals[i][1] - BaseConfig.T_DISK_USED):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskUsed
                elif atts[i][0] == 'DFree':
                    diskFree = atts[i][1]
                    if (diskFree >= self._last_att_vals[i][1] + BaseConfig.T_DISK_FREE) \
                        or (diskFree <= self._last_att_vals[i][1] - BaseConfig.T_DISK_FREE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskFree
                elif atts[i][0] == 'DSize':
                    diskSize = atts[i][1]
                    if math.fabs(diskSize - self._last_att_vals[i][1]) > 0.001 :
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = diskSize
                        #print 'Static attribute Disk Size changed'
                elif atts[i][0] == 'MSize':
                    memSize = atts[i][1]
                    if math.fabs(memSize - self._last_att_vals[i][1]) > 0.001:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = memSize
                        #print 'Static attribute Memory Size changed'
                elif atts[i][0] == 'MAct':
                    memActive = atts[i][1]
                    if (memActive >= self._last_att_vals[i][1] + BaseConfig.T_MEM_ACTIVE) \
                        or (memActive <= self._last_att_vals[i][1] - BaseConfig.T_MEM_ACTIVE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = memActive
                elif atts[i][0] == 'SUsed':
                    swapUsed = atts[i][1]
                    if (swapUsed >= self._last_att_vals[i][1] + BaseConfig.T_SWAP_USED) \
                        or (swapUsed <= self._last_att_vals[i][1] - BaseConfig.T_SWAP_USED):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = swapUsed
                elif atts[i][0] == 'KVer':
                    kernVer = copy.copy(atts[i][1])
                    if kernVer != self._last_att_vals[i][1]:
                        #print 'Static attribute KernelVer changed %d %d %d' % \
                        #        (self.id, self._last_att_vals[i][1], kernVer)
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = kernVer
                elif atts[i][0] == 'CFree':
                    cpuFree = copy.copy(atts[i][1])
                    if (cpuFree >= self._last_att_vals[i][1] + BaseConfig.T_CPU_FREE) \
                        or (cpuFree <= self._last_att_vals[i][1] - BaseConfig.T_CPU_FREE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = cpuFree
                elif atts[i][0] == 'CSp':
                    cpuSpeed = copy.copy(atts[i][1])
                    if math.fabs(cpuSpeed - self._last_att_vals[i][1]) > 0.001:
                        #print 'Static attribute CPUSpeed changed %d %f %f' % \
                        #(self.id, self._last_att_vals[i][1], cpuSpeed)
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = cpuSpeed
                elif atts[i][0] == 'NCore':
                    numCores = atts[i][1]
                    if numCores != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = numCores
                        #print 'Static attribute No of Cores changed'
                elif atts[i][0] == 'CorePC':
                    corePCPU = atts[i][1]
                    if corePCPU != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = corePCPU
                        #print 'Static attribute Cores per CPU changed'
                elif atts[i][0] == 'FCName':
                    fcNamex = copy.copy(atts[i][1])
                    if fcNamex != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = fcNamex
                        #print 'Static attribute FC Name changed'
                elif atts[i][0] == 'Tx':
                    txRate = atts[i][1]
                    if (txRate >= self._last_att_vals[i][1] + BaseConfig.T_TX_RATE) \
                        or (txRate <= self._last_att_vals[i][1] - BaseConfig.T_TX_RATE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = txRate
                elif atts[i][0] == 'Rx':
                    rxRate = atts[i][1]
                    if (rxRate >= self._last_att_vals[i][1] + BaseConfig.T_RX_RATE) \
                        or (rxRate <= self._last_att_vals[i][1] - BaseConfig.T_RX_RATE):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = rxRate
                elif atts[i][0] == 'Boot':
                    boot = atts[i][1]
                    if boot != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = boot
                        #print 'Static attribute Boot State changed'
                elif atts[i][0] == 'NType':
                    nodeType = atts[i][1]
                    if nodeType != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = nodeType
                        #print 'Static attribute Node Type changed'
                elif atts[i][0] == 'BWLim': 
                    bwLimit = atts[i][1]
                    if bwLimit != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = bwLimit
                        # print 'Static attribute BW Limit changed'
                elif atts[i][0] == 'Lati':
                    lati = atts[i][1]
                    if math.fabs(lati - self._last_att_vals[i][1]) > 0.001:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = lati
                        #print 'Static attribute Latitude changed'
                elif atts[i][0] == 'Longi':
                    longi = atts[i][1]
                    if math.fabs(longi - self._last_att_vals[i][1]) > 0.001:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = longi
                        #print 'Static attribute Longitude changed'
                elif atts[i][0] == 'Loc':
                    location = atts[i][1]
                    if location != self._last_att_vals[i][1]:
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = location
                        #print 'Static attribute Location changed'
                elif atts[i][0] == 'Drift':
                    drift = atts[i][1]
                    if (drift >= self._last_att_vals[i][1] + BaseConfig.T_DRIFT) \
                        or (drift <= self._last_att_vals[i][1] - BaseConfig.T_DRIFT):
                        atts_to_adv.append(atts[i])
                        atts_to_remove.append(copy.deepcopy(self._last_att_vals[i]))
                        self._last_att_vals[i][1] = drift
                else: 
                    print 'ERROR: Unmatched attribute ' + atts[i][0] + '\n'
                    return []
                
        if len(atts_to_adv) > 0: #Need to advertise
            #Do we need all attributes or only the ones that changed?
            if need_all_att == True:
                self._last_att_vals = atts #1st copy newest values then send
                return [self._last_att_vals, atts_to_remove]
            else :
                return [atts_to_adv, atts_to_remove]            
        else: return [] #Not changed


    def msg_forwarded(self):
        '''
        Increase no of fwd messages
        '''
        self._num_que_fwd += 1


    def get_stat(self):
        '''
        Dump node statistics
        '''
        tmp_stat = [self._id, self._num_updates, self._num_que_send, self._num_que_fwd, \
                    self._num_que_ans, self._max_idx_size, self._num_updates_rec]
        return tmp_stat