'''
Created on Mar 6, 2012
Modified on May 25, 2012
@version: 0.1
@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 PeerManager as PM
from PSubscription import PSubscription


class ProxySubscriptions(object):
    '''
    Table that tracks interest messages at a proxy.
    '''

    def __init__(self, NID):
        '''
        Constructor
        @param nID: Node ID 
        '''
        self.NID = NID
        self._ST = {} #{name: PSubscription}


    def add(self, name, currentTime, validTime, interval, userID, query, \
                                                        minLoc, maxLoc, app):
        '''
        Add to interest message table
        @param name: Name of interest message
        @param validTIme: Data need to be newer than this time
        @param interval: Subscription interval
        @param userID: User ID
        @param query: Original query issued by user
        @param minLoc: Lower left corner location
        @param maxLoc: Upper right corner location
        @param app: Application type  
        @return: [Did valid time changed, new valid time, current interval] 
        '''
        if name not in self._ST: #1st time
            self._ST[name] = PSubscription(name, currentTime, validTime, \
                        interval, userID, query, minLoc, maxLoc, app)
            #Time for next interest message. Timers are set based on query 
            #time not the valid time.
            if (300.0 <= self._ST[name].nextQueryTime <= 1020.0) or \
                    (2100.0 <= self._ST[name].nextQueryTime <= 2820.0) or \
                    (3900.0 <= self._ST[name].nextQueryTime <= 4620.0) or \
                    (5700.0 <= self._ST[name].nextQueryTime <= 6420.0) or \
                    (7500.0 <= self._ST[name].nextQueryTime <= 8220.0 ) or \
                    (9300.0 <= self._ST[name].nextQueryTime <= 10020.0) or \
                    (11100.0 <= self._ST[name].nextQueryTime <= 11820.0) or \
                    (12900.0 <= self._ST[name].nextQueryTime <= 13620.0):            
                PM.EVENTS.addEvent(self._ST[name].nextQueryTime, 'QSUB', \
                                    self.NID, [name, currentTime - validTime])
            #No change to current validTime
            return [False, self._ST[name].nextQueryTime, \
                            self._ST[name].interval, self._ST[name].validDelay]
        
        #Same sub-query from a different user
        self._ST[name].addUser(userID, query) #Add new user
        if validTime > self._ST[name].validTime:
            self._ST[name].pendingValidTime.append(validTime)

        if self._ST[name].nextQueryTime == currentTime: #Same time
            return [False, self._ST[name].nextQueryTime, \
                            self._ST[name].interval, self._ST[name].validDelay]
                
        #Take decision based on pending event
        assert(self._ST[name].nextQueryTime > currentTime), \
                    str(self._ST[name].nextQueryTime) + '\t' + str(currentTime)\
                    + '\t' + str(self._ST[name].interval) + '\t' + \
                    str(self._ST[name].validDelay) + '\t' + name
        result = self._findNextQueryTime(self._ST[name].nextQueryTime, \
                        self._ST[name].interval, self._ST[name].validDelay, \
                        currentTime, interval, currentTime - validTime)

        if self._ST[name].interval > interval: #New interval is shorter 
            self._ST[name].interval = interval
            self._ST[name].validDelay = currentTime - validTime
            self._ST[name].nextValidTime = result - self._ST[name].validDelay
        
        if result < self._ST[name].nextQueryTime: #Push back next query time
            #First remove current timer & then add new one
            PM.EVENTS.removeEvent(self._ST[name].nextQueryTime, 'QSUB', \
                                                                self.NID, name)
            self._ST[name].nextQueryTime = result
            if (300.0 <= result <= 1020.0) or \
                    (2100.0 <= result <= 2820.0) or \
                    (3900.0 <= result <= 4620.0) or \
                    (5700.0 <= result <= 6420.0) or \
                    (7500.0 <= result <= 8220.0 ) or \
                    (9300.0 <= result <= 10020.0) or \
                    (11100.0 <= result <= 11820.0) or \
                    (12900.0 <= result <= 13620.0):                        
                PM.EVENTS.addEvent(result, 'QSUB', self.NID, [name, \
                                                    self._ST[name].validDelay])
            #Need to change valid time of existing users subscriptions
            return [True, self._ST[name].nextQueryTime, \
                            self._ST[name].interval, self._ST[name].validDelay]
        else:
            return [False, self._ST[name].nextQueryTime, \
                            self._ST[name].interval, self._ST[name].validDelay]


    def get(self, name, genTime = -1):
        '''
        Get matching entries from subscription table
        @param name: Name in interest message
        @param genTime: Time data was generated
        @return: Dictionary of {user:query}
        '''
        if name not in self._ST: return {} #No such interest
        if genTime > -1: #Generation time given
            useful = False
            toRemove = []
            for i in range(len(self._ST[name].pendingValidTime)):
                if genTime >= self._ST[name].pendingValidTime[i]: 
                    useful = True
                    toRemove.append(self._ST[name].pendingValidTime[i])
            for j in toRemove: #Remove those
                del self._ST[name].pendingValidTime[\
                                    self._ST[name].pendingValidTime.index(j)]
            if genTime >= self._ST[name].validTime: 
                useful = True
                self._ST[name].setNextValidTime() #Prepare for next round         
            if useful == False: return {} 
        return self._ST[name].users


    def getSubInfo(self, name, currentTime):
        '''
        Get subscription information to generate new interest. Also set timer 
        for next round
        @param name: Interest name
        @param validTime: Current next valid time 
        @return: [minLoc, maxLoc, app]
        '''
        self._ST[name].nextQueryTime += self._ST[name].interval 
        assert(self._ST[name].nextQueryTime > currentTime), \
                    str(self._ST[name].nextQueryTime) + '\t' + str(currentTime)\
                    + '\t' + str(self._ST[name].interval) + '\t' + \
                    str(self._ST[name].validDelay) + '\t' + name
        if (300.0 <= self._ST[name].nextQueryTime <= 1020.0) or \
                (2100.0 <= self._ST[name].nextQueryTime <= 2820.0) or \
                (3900.0 <= self._ST[name].nextQueryTime <= 4620.0) or \
                (5700.0 <= self._ST[name].nextQueryTime <= 6420.0) or \
                (7500.0 <= self._ST[name].nextQueryTime <= 8220.0 ) or \
                (9300.0 <= self._ST[name].nextQueryTime <= 10020.0) or \
                (11100.0 <= self._ST[name].nextQueryTime <= 11820.0) or \
                (12900.0 <= self._ST[name].nextQueryTime <= 13620.0):            
            PM.EVENTS.addEvent(self._ST[name].nextQueryTime, 'QSUB', self.NID, \
                                            [name, self._ST[name].validDelay])
        return [self._ST[name].minLoc, self._ST[name].maxLoc, self._ST[name].app]


    def reset(self):
        '''
        Reset subscription table
        '''
        self._ST = {}


    def _findNextQueryTime(self, cNextTime, cInterval, cDelay, cTime, \
                            newInterval, newDelay):
        if cNextTime - cDelay <= cTime + newInterval - newDelay:
            return cNextTime
        else:
            return cTime + newInterval


    def getLength(self):
        '''
        @return: size of subscription table 
        '''
        return len(self._ST)