'''
Created on Sep 12, 2012
@author: Dilum Bandara
@version: 0.1
@copyright: Copyright(c) 2012, Dilum Bandara
@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 copy
import Distance as Distance


class ResourceIndex(object):
    '''
    Resource index class.
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self._idx = {}
        self._idxSorted = {} #Sorted version of Index
        self._numQueFwdPrede = 0 #No of queries forwarded by predecessor
        self._numQueFwdSucce = 0 #No of Forward queries forwarded to successor


    def __del__(self):
        '''
        Destructor
        '''
        self._idx = {}
        self._idxSorted = {}


    def add(self, key, attributeName, ress, source, genTime, validTime):
        '''
        Add given resource to index. Update if already exists
        @param key: Key use for indexing
        @param attributeName: Attribute name used to index resource  
        @param ress: List of resource attribute values. Given as a list of lists
        @param source: Resource ID
        @param genTime: Time that data was generated
        @param validTime: Entry is valid till this time  
        '''
        if source.NID in self._idx: #Entry in
            found = False
            for i in range(len(self._idx[source.NID][0])):#Is name in
                if self._idx[source.NID][0][i][1] == attributeName:
                    self._idx[source.NID][0][i][0] = key #Update key
                    found = True
            if found == False:
                self._idx[source.NID][0].append([key, attributeName])
            #Update values as they may have changed. Specially timers
            self._idx[source.NID][1] = copy.deepcopy(ress)
            self._idx[source.NID][2] = copy.deepcopy(source)
            self._idx[source.NID][3] = genTime
            self._idx[source.NID][4] = validTime
        else: #Not in 
            self._idx[source.NID] = [[[key, attributeName]], \
                copy.deepcopy(ress), copy.deepcopy(source), genTime, validTime]
        
        if attributeName in self._idxSorted: #Add to sorted index
            for i in range(len(self._idxSorted[attributeName])):
                if self._idxSorted[attributeName][i][0] >= key:
                    self._idxSorted[attributeName].insert(i, [key, source.NID])
                    return
            self._idxSorted[attributeName].append([key, source.NID])
        else: self._idxSorted[attributeName] = [[key, source.NID]] #1st one


    def query(self, attToQue, resQuery, cTime, vTime, currentResources, minKey,\
              maxKey, myKey, lastNode = False):
        '''
        Find list of entries that satisfy given resource query
        @param n: How many resources required
        @param attToQue: Attribute use to issue query (in SADQ or sub-query)
        @param resQuery: actual resource query
        @param cTime: curent time 
        @param vime: Valid time 
        @param currentResources: List of resources that are already selected
        @param minKey: Minimum key query specified (set only if IN query-direct)
        @param maxKey: Maximum key query specified
        @param myKey: Global key of this node 
        @param lastNode: This will be the last node query will reach  
        @return: List of resources   
        '''
        results = []
        numResults = 0
        toRemove = []
        if attToQue not in self._idxSorted:
            if lastNode == False: self._numQueFwdSucce += 1 #FWD to successor
            return results
        
        for entry in self._idxSorted[attToQue]: #Check sorted entries
            if not(Distance.inRangeLR(maxKey, entry[0], myKey)) and \
                                        (lastNode == True): #Reached max key
                return results
            #Skip same resource if already selected at a previous node
            nID = entry[1]
            if nID in currentResources: continue
            
            if self._idx[nID][3] < vTime: continue #Too old
            if self._idx[nID][4] < cTime: #Expired
                toRemove.append(nID)
                continue
            
            #Assume that queries have only the attributes that are used to index
            attribsMatch = False
            for j in range(len(resQuery)): #Check each attribute in query
                for k in range(len(self._idx[nID][1])):#each attribute in entry
                    if resQuery[j][0] == self._idx[nID][1][k][0]: #Att match
                        if (resQuery[j][1] <= self._idx[nID][1][k][1] <= \
                                                                resQuery[j][2]):
                            attribsMatch = True #Match
                        else: attribsMatch = False#At least 1 attribute mismatch
                        break
                if attribsMatch == False: break #Break outer loop as well
            if attribsMatch == True: #If all required attributes match
                results.append(entry[1])
                numResults += 1
        
        for nID in toRemove: #Remove expired entries
            del self._idx[nID]
            for j in range(len(self._idxSorted[attToQue])):
                if self._idxSorted[attToQue][j][1] == nID:
                    del self._idxSorted[attToQue][j]
                    break
        return results


    def getStat(self):
        '''
        Get stat
        @return: [index size, In query load, Out query load, Fwd query load]
        '''
        return [len(self._idx), self._numQueFwdPrede, self._numQueFwdSucce]