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

from BaseClasses.BaseOverlay import BaseOverlay
import BaseClasses.BaseConfig as BaseConfig
import Config
import random

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(len(list)): 
                if int(list[i]) == 1: tmp_list.append(i) #Add neighbor IDs
            self._distances.append(tmp_list)
        fdDis.close()


    def route_new_ads(self, source, atts):
        '''
        Route resource ads to given destination & return no of overlay hops. 
        Use random walk
        @param source: Source node ID
        @param atts: Attributes
        @return: Success/Failure
        '''
        if Config.SEND_ADS == False: #If ads are not send. Just add to itself
            result = BaseConfig.PEERS[source].add_to_index(source, atts) #Add to index
            if result == False:
                print 'add_ to_index failed. Atts: ' + str(atts) + ' node: ' + str(source)
                return []
            return [0]
        else:
            print 'ERROR: Forwarding ads to other nodes not implemented. Terminating...'
            return []


    def route_old_ads(self, source, atts):
        '''
        Route resource remove msgs to given destination & return no of overlay hops
        @param source: Source node ID
        @param atts: Attributes
        @return: Success/Failure
        '''
        return [0] #No remove message routing

    
    def route_query(self, que_msg):
        '''
        Route query to given destination & return no of overlay hops   
        @param que_msg: Query message
        @return: Modified query message
        '''
        current = que_msg.source        
        while True: #Check neighbors starting with myself. Until hop count exceeds
            if current not in que_msg.visited_peers: #Don't query same node twice
                result = BaseConfig.PEERS[current].query_index(que_msg)
                if result.num_res_found >= result.n: #Have I found enough resources?
                    result.success = True
                    result.hops = [result.hops]
                    return result
                que_msg = result
            #Need to find more resources
            que_msg.visited_peers.append(current) #Visited myself
            next = self.get_unvisited_neighbor(current, que_msg)
            if next == -1: #Unable to find next node to forward
                print 'ERROR: Unable to find a peer to forward query'
                que_msg.hops = -1 #Indicate error 
                return que_msg #No neighbor
            if que_msg.hops == Config.MAX_HOPS: #Already reached max hops
                que_msg.hops = [que_msg.hops]
                return que_msg
            else: #Query will be forwarded
                BaseConfig.PEERS[current].msg_forwarded() #Indicate msg is being fwd
                que_msg.hops += 1
                current = next
        return que_msg


    def get_unvisited_neighbor(self, nid, que_msg):
        '''
        Find a random unvisited neighbor of given node. If unable to find, 
        pick node before me        
        @param nid: Current node ID
        @param que_msg: Query message
        '''
        if len(self._distances[nid]) == 0: return -1 #No neighbor
        idx = random.randint(0, len(self._distances[nid]) - 1) #Random index
        next = self._distances[nid][idx]
        while next in que_msg.visited_peers: #Don't go to same node
            already_in = True
            for i in range (len(self._distances[nid])):
                if self._distances[nid][i] not in que_msg.visited_peers: #If neighbor is not visited
                    already_in = False
                    break
            if already_in == False: #There are new ones to explore
                idx = random.randint(0, len(self._distances[nid]) - 1)
                next = self._distances[nid][idx]
            else: #No new ones. Return my predecessor
                next = que_msg.visited_peers[que_msg.visited_peers.index(nid) - 1]
                break
        return next
