'''
Created on Nov 2, 2011
Modified on May 25, 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 Location import Location
from CCNInterest import CCNInterest
from UserSubscriptions import UserSubscriptions
from ProxySubscriptions import ProxySubscriptions


class Proxy(object):
    '''
    Proxy Class
    '''

    def __init__(self, NID, loc):
        '''
        Constructor
        @param NID: Proxy ID 
        @param loc: Location
        '''
        self.NID = NID
        self.loc = loc
        self.type = 'PROXY'
        self._pendInter = {}
        self._pendSubInter = {} #Pending interests for tiles
        self._results = [] #Summary of time took to answer interests
        self._userSub = UserSubscriptions() #User subscriptions
        self._proxySub = ProxySubscriptions(self.NID) #User subscriptions        
        self._expireSubscriptions = Config.FIRST_QUERY + Config.SUB_EXPIRE_TIME
        self.numInterMsgsGen = 0 #Number of messages generated
        self.numInterMsgsSend = 0
        self.numTotalInterMsgsSend = 0 #in bits
        self.numDataMsgsRec = 0
        self.numTotalDataMsgsRec = 0 #In bits


    def genInterests(self, userLoc, query, currentTime, validTime, userID, \
                     interval = -1):
        '''
        Generate set of interest messages for given query
        @param userLoc: User location
        @param query: Query 
        @param currentTime: Current time
        @param validTime: Time interval that past content is considered valid 
        @param userID: User issuing query
        @param interval: Interval that queries are generated  
        '''
        tmp = query.split('/')[1:-1]
        time = currentTime - validTime
        if time < 0.0 : time = 0.0
        messages = []
        
        if Config.USE_SUBSCRIPTIONS == True: #Using query subscriptions
            #Remove expired entries. Here assume all expire at same time
            if PM.EVENTS.now > self._expireSubscriptions:
                self._userSub.reset()
                self._proxySub.reset()
                self._expireSubscriptions = currentTime + Config.SUB_EXPIRE_TIME
                print self._expireSubscriptions, '--xx'
            result = self._userSub.handleQuery(userID, query, time, interval, \
                                                                currentTime,)
            if len(result) > 0: #Not the 1st time
                #If result is already received add that to stat. Else wait
                if result[0] == 'Que': self._results.append(result)
                return

            #This user subscription is received for 1st time
            subs = self._genSubInterests(Location(tmp[0], tmp[1]), \
                                     Location(tmp[2], tmp[3]), tmp[4])
            self._userSub.addSubInter(userID, query, subs)
            for i in range(len(subs)): #For each sub-query interest message
                name = subs[i][0]
                result = self._proxySub.add(name, currentTime, time, interval, \
                                userID, query, subs[i][1], subs[i][2], tmp[4])
                if result[0] == True: #Need to change other users' validTime
                    self._userSub.updateValidTime(self._proxySub.get(name), \
                                        name, result[1], result[2], result[3])
                else: #Update only new user
                    self._userSub.updateValidTime({userID: query}, name, \
                                                result[1], result[2], result[3])
                realName = name + str(time) + '/'
                if name in self._pendSubInter:
                    self._pendSubInter[name].append([currentTime, time])
                else: self._pendSubInter[name] = [[currentTime, time]]
                messages.append(CCNInterest(name, realName, subs[i][1], \
                            subs[i][2], tmp[4], time))
        else:
            #[[sub queries], time, totalData, hop, latency, delay, userID(make unique)
            if query in self._pendInter: 
                self._pendInter[query].append([[], time, 0, 0, 0, 999999.9, \
                                               userID])
            else: 
                self._pendInter[query] = [[[], time, 0, 0, 0, 999999.9, userID]]
            #Maintain individual elements of name to speed up
            subs = self._genSubInterests(Location(tmp[0], tmp[1]), \
                                            Location(tmp[2], tmp[3]), tmp[4])
            for i in range(len(subs)): #For each sub interest message
                name = subs[i][0]
                realName = name + str(time) + '/'
                self._pendInter[query][len(self._pendInter[query]) -\
                                        1][0].append(name) #Just added entry
                if name in self._pendSubInter:
                    self._pendSubInter[name].append([currentTime, time, \
                                                     userLoc, query, userID])
                else:
                    self._pendSubInter[name] = [[currentTime, time, userLoc, \
									                           query, userID]]
                messages.append(CCNInterest(name, realName, subs[i][1], \
                                            subs[i][2], tmp[4], time))
        
        for k in range(len(messages)):
            result = PM.OVERLAY[self.NID].fwdInter(messages[k])                
            self.numInterMsgsGen += 1
            if result != None: #Data on local node
                if len(result.data) > 1:
                    print 'Proxy::genInterests More than 1 cache result'
                else:
                    if Config.USE_SUBSCRIPTIONS == False: #No query subscription
                        self._handleInterestReply(result)
                    else: self._handleSubscriptionReply(result)#Query subscription
            else: #Messages was send through network
                self.numInterMsgsSend += 1
                self.numTotalInterMsgsSend += messages[k].size


    def genSubInterests(self, name, validDelay, currentTime):
        '''
        Time to handle subscription interest
        @param name: Sub-query
        @param validDelay: Delay that data is considered valid 
        @param currentTime: Current time  
        '''
        subData = self._proxySub.getSubInfo(name, currentTime)
        realName = name + str(currentTime - validDelay) + '/'
        if name in self._pendSubInter:
            self._pendSubInter[name].append([currentTime, \
                                                    currentTime - validDelay])
        else: self._pendSubInter[name] = [[currentTime, \
                                                    currentTime - validDelay]]
        newMsg = CCNInterest(name, realName, subData[0], subData[1], subData[2]\
                             , currentTime - validDelay)
        result = PM.OVERLAY[self.NID].fwdInter(newMsg)
        self.numInterMsgsGen += 1
        if result != None: #Data on local node
            if len(result.data) > 1:
                print 'Proxy::genInterests More than 1 cache result'
            else: 
                if Config.USE_SUBSCRIPTIONS == False: #No query subscriptions
                    self._handleInterestReply(result)
                else: self._handleSubscriptionReply(result) #Query subscriptions
        else: #Messages was send through network
            self.numInterMsgsSend += 1
            self.numTotalInterMsgsSend += newMsg.size
        

    def recMsg(self, msg):
        '''
        Receive message from overlay/CCN
        @param msg: Message
        '''
        if msg.msgType != 'CCNData': 
            print 'Proxy:recMsg Wrong message type', msg.msgType
            return
        if msg.name not in self._pendSubInter:
            print 'Proxy:recMsg No pending interest', msg.name
            return
        self.numDataMsgsRec += 1
        self.numTotalDataMsgsRec += msg.size
        if Config.USE_SUBSCRIPTIONS == False: #Not using query subscriptions
            self._handleInterestReply(msg)
        else: self._handleSubscriptionReply(msg) #Use query subscriptions


    def _handleInterestReply(self, msg):
        '''
        Handle reply to interest messages
        @param msg: Message
        '''
        toRemoveSub = []
        for i in range(len(self._pendSubInter[msg.name])):
            if msg.genTime < self._pendSubInter[msg.name][i][1]: continue
            #Delay to receive data
            delay = PM.EVENTS.now - self._pendSubInter[msg.name][i][0]\
                + self._latency(msg.name, self._pendSubInter[msg.name][i][2],\
                                                                msg.size)
            #How stale the data is
            genDelay = msg.genTime - self._pendSubInter[msg.name][i][0]
            self._results.append(['SubQue', PM.EVENTS.now, delay, genDelay, \
                                  msg.size, msg.hops])
            toRemove = []
            originalQuery = self._pendSubInter[msg.name][i][3]
            if originalQuery in self._pendInter:
                for j in range(len(self._pendInter[originalQuery])):
                    if msg.name in self._pendInter[originalQuery][j][0]:
                        #Don't remove original queries from other nodes
                        if self._pendInter[originalQuery][j][6] !=\
                            self._pendSubInter[msg.name][i][4]: continue
                        self._pendInter[originalQuery][j][2] += msg.dataSize
                        if self._pendInter[originalQuery][j][4] < delay: 
                            self._pendInter[originalQuery][j][4] = delay            
                        if genDelay < self._pendInter[originalQuery][j][5]: 
                            self._pendInter[originalQuery][j][5] = genDelay
                        if self._pendInter[originalQuery][j][3] < msg.hops: 
                            self._pendInter[originalQuery][j][3] = msg.hops
                        del self._pendInter[originalQuery][j][0]\
                            [self._pendInter[originalQuery][j][0].index(msg.name)]
                        #All sub queries satisfied?
                        if len(self._pendInter[originalQuery][j][0]) == 0:
                            self._results.append(['Que', PM.EVENTS.now, \
                                    self._pendInter[originalQuery][j][4], \
                                    self._pendInter[originalQuery][j][5], \
                                    self._pendInter[originalQuery][j][2], \
                                    self._pendInter[originalQuery][j][3]])
                            toRemove.append(self._pendInter[originalQuery][j])
                for k in toRemove:
                    del self._pendInter[originalQuery]\
                            [self._pendInter[originalQuery].index(k)]
                if len(self._pendInter[originalQuery]) == 0:
                    del self._pendInter[originalQuery]
            toRemoveSub.append(self._pendSubInter[msg.name][i])
        for k in toRemoveSub: 
            del self._pendSubInter[msg.name][self._pendSubInter[msg.name].index(k)]
        if len(self._pendSubInter[msg.name]) == 0:
            del self._pendSubInter[msg.name]


    def _handleSubscriptionReply(self, msg):
        '''
        Handle reply to subscription messages
        @param msg: Message
        '''
        if msg.name not in self._pendSubInter: return #No such interest
        toRemoveSub = []
        for i in range(len(self._pendSubInter[msg.name])):
            #Is content fresh. Else discard?
            if msg.genTime < self._pendSubInter[msg.name][i][1]: continue
            #How stale the data is. Can't find delay as no explicit interest send
            genDelay = PM.EVENTS.now - msg.genTime
            self._results.append(['SubQue', PM.EVENTS.now, 0, genDelay, \
                                  msg.size, msg.hops])
            toRemoveSub.append(self._pendSubInter[msg.name][i])
        for k in toRemoveSub: 
            del self._pendSubInter[msg.name][self._pendSubInter[msg.name].index(k)]
            if len(self._pendSubInter[msg.name]) == 0:
                del self._pendSubInter[msg.name]
            
        #Try each user's query
        users = self._proxySub.get(msg.name, msg.genTime)
        for i in users:
            result = self._userSub.handleData(i, users[i], msg.name, \
                            msg.genTime, msg.dataSize, msg.hops, PM.EVENTS.now)
            if len(result) > 0: #Query answered
                if result[0] == 'Que': self._results.append(result) #Has result


    def _latency(self, query, userLoc, dataSize):
        '''
        Time required to receive query from user & send data to user
        @param query: Original query 
        @param userLoc: Location of user
        @param dataSize: Size of data  
        '''
        delay = 2 * Distance.dist(self.loc, userLoc)/Config.SPEED_LIGHT
        delay += len(query * 8)/float(Config.BANDWIDTH)
        delay += dataSize/float(Config.BANDWIDTH)
        return delay


    def _genSubInterests(self, locMin, locMax, app):
        '''
        Generate subscription interests 
        @param locMin: Location of lower left
        @param locMax: Location of upper right
        @param app: Application
        @return: List of [name, min location, max location] 
        '''
        subs = []
        numTilesX = int(math.ceil((locMax.x - locMin.x)/Config.TILE_X_PROXY))
        numTilesY = int(math.ceil((locMax.y - locMin.y)/Config.TILE_Y_PROXY))
        for i in range(numTilesX):
            for j in range(numTilesY):
                newMinLoc = Location(locMin.x + i * Config.TILE_X_PROXY, \
                                     locMin.y + j * Config.TILE_Y_PROXY)
                newMaxLoc = Location(locMin.x + (i + 1) * Config.TILE_X_PROXY, \
                                     locMin.y + (j + 1) * Config.TILE_Y_PROXY)
                name = '/' + str(newMinLoc.x) + '/' + str(newMinLoc.y) + '/' +\
                        str(newMaxLoc.x) + '/' + str(newMaxLoc.y) + '/'+ app +'/'
                subs.append([name, newMinLoc, newMaxLoc])
        return subs


    def dumpResults(self):
        '''
        Dump current results & reset collector
        @return: Results
        '''
        tmp = self._results
        self._results = []
        return tmp


    def getStat(self):
        '''
        Get statistics 
        '''
        return [len(self._pendInter), len(self._pendSubInter), \
                self._userSub.getLength(), self._proxySub.getLength(), \
                self.numInterMsgsGen, self.numInterMsgsSend, \
                self.numDataMsgsRec, self.numTotalInterMsgsSend, \
                self.numTotalDataMsgsRec]