__author__ = 'Dilum Bandara'
__version__ = '0.1'
__date__ = '1/24/2011'
__copyright__ = 'Copyright(c) 2011, Dilum Bandara'
__license__ = 'GPL'

import sys
sys.path.append('../../BaseClasses')
from BaseOverlay import BaseOverlay
from BaseHash import BaseHash
import BaseConfig
import Config
import math
import random
import copy

class Overlay(BaseOverlay):
    """
    Overlay class. 
    """
    def __init__(self):
        fdDis = open(BaseConfig.DISTANCE_FILE, 'r') #Extract from distance file
        self._distances = []
        for line in fdDis:
            list = line.split()
            tmp_list = []   
            for i in range(0, len(list)): tmp_list.append(int(list[i]))
            self._distances.append(tmp_list)
        fdDis.close()
	self._key_list = []
	fdKey = open(BaseConfig.KEY_FILE, 'r') #extract from key file
	for line in fdKey: self._key_list.append(long(line, 16))
        fdKey.close()
        #generate node key list
        tmp_key_list = copy.deepcopy(self._key_list)
        shuffled_key_list = [copy.deepcopy(tmp_key_list)] #Shuffled key list for each ring
        for i in range(1, Config.NUM_RINGS):
            random.shuffle(tmp_key_list)
            shuffled_key_list.append(copy.deepcopy(tmp_key_list))
        self.node_key_list = []
        for j in range(0, Config.NUM_NODES): #Assign keys to nodes
            keys = []
            for k in range(0, Config.NUM_RINGS):
                keys.append(shuffled_key_list[k][j])
            self.node_key_list.append(keys)
        self._my_hash = BaseHash()
        self._neighbor_list = self.find_all_neighbors()


    def route_new_ads(self, source, attribs, node_list):
    #Route resource ads to given destination & return no of overlay hops
        hops = []
        for i in range(0, len(attribs)): #For each attribute
            key = self._my_hash.PULP_hash(attribs[i])  #Get hash
            ring = 0 #Which ring?
            next_node = self.find_successor(key, ring) #Successor of given key
            result = node_list[next_node].add_to_index(source, [attribs[i]], Config.NUM_RINGS, Config.ATT_LIST) #Add to index
            if result == False: 
                print 'add_ to_index failed. Attribs: ' + str(attribs[i]) + ' node: ' + str(source)
                sys.exit(0)
            #source_index = self._key_list.index(node_list[source].keys[ring])
            #hops.append(self.get_distance(source_index, next_node)) #Collect hops
            hops.append(self.get_distance(source, next_node)) #Collect hops
        return  hops


    def route_old_ads(self, source, attribs, node_list):
    #Route resource ads to given destination & return no of overlay hops
        hops = []
        for i in range(0, len(attribs)): #For each attribute
            key = self._my_hash.PULP_hash(attribs[i])  #Get hash
            ring = 0 #Which ring?
            next_node = self.find_successor(key, ring) #Successor of given key
            result = node_list[next_node].remove_from_index(source, attribs[i], Config.NUM_RINGS, Config.ATT_LIST) #Remove From index
            if result == False: 
                print 'remove_from_index failed. Check unless multiple attribs map to same node. Attribs: ' + str(attribs[i]) + ' node: ' + str(source)
            #source_index = self._key_list.index(node_list[source].keys[ring])
            #hops.append(self.get_distance(source_index, next_node)) #Collect hops
            hops.append(self.get_distance(source, next_node)) #Collect hops
        return  hops


    def route_query(self, source, query, node_list):
    #Route query to given destination & return no of overlay hops 
        n = query[0][1]
        attribs = query[1:]
        hops = []
        for i in range(0, len(attribs)): #For each attribute
            min_key = self._my_hash.PULP_hash([attribs[i][0], attribs[i][1]]) #MIN hash value
            ring = 0 #Which ring?
            next_node = self.find_successor(min_key, ring) #Destination node
            results = node_list[next_node].query_index(- 1, [attribs[i]], Config.NUM_RINGS, Config.ATT_LIST) #Query index
            #source_index = self._key_list.index(node_list[source].keys[ring])
            #hops.append(self.get_distance(source_index, next_node)) #Collect hops
            hops.append(self.get_distance(source, next_node)) #Collect hops

            max_key = self._my_hash.PULP_hash([attribs[i][0], attribs[i][2]]) #MAX hash value
            max_node = self.find_successor(max_key, ring) #Destination node
            max_node_key = node_list[max_node].keys[ring]
            last_node = next_node
            if last_node != max_node: #More to explore
                while 1:
                    next_node = self.get_neighbor(last_node, ring) #Destination node
                    new_results = node_list[next_node].query_index(-1, [attribs[i]], Config.NUM_RINGS, Config.ATT_LIST) #Query neighbor
                    hops[i] = hops[i] + 1 #Increment hops
                    #Return actual cost so far
                    if node_list[next_node].keys[ring] == max_node_key: break #MAX-key exceed
                    last_node = next_node
        return hops


    def find_successor(self, key, ring_num):
    #Find successor for a given key in given ring
        min_dis = self._my_hash.max_key
        successor = -1
        for i in range(0, Config.NUM_NODES):
            dis = 0
            if (self.node_key_list[i][ring_num] < key): dis = self._my_hash.max_key + 1 + \
                self.node_key_list[i][ring_num] - key
            else: dis = self.node_key_list[i][ring_num] - key
            if dis < min_dis:
                min_dis = dis
                successor = i
        if successor == -1:
            print 'Unable to find successor'
            sys.exit(0)
            return -1
        else: return successor


    def get_distance(self, source, destination):
    #Get overlay distance between 2 nodes
        return self._distances[source][destination]


    def get_neighbor(self, nid, ring):
    #Find neighbor of given node
        return self._neighbor_list[nid][ring]


    def find_all_neighbors(self):
    #Find all neighbors for each node in each ring
        neighbor_list = []
        for i in range(0, Config.NUM_NODES):
            tmp_list = []
            for j in range(0, Config.NUM_RINGS):
                key = self.node_key_list[i][j]
                key = (key + 1) % (self._my_hash.max_key + 1) #increment key
                tmp_list.append(self.find_successor(key, j))
            neighbor_list.append(tmp_list)
        return neighbor_list
