# -*- coding: utf-8 -*-

"""Module containing network logic for dispatching operations"""

import globalv, tcpsender, time
import murray_crypto as crypto
from search import Search
from cache import KnownPeers, Waiting

my_identity = {'uid' : globalv.MURRAY_ID, 'ip': globalv.IP, 
            'ipversion' : globalv.IP_VERSION, 'port' : globalv.PORT}
            
out = tcpsender.send
knownPeers = KnownPeers()
waitingList = Waiting()

###required: Networking-Module (Kathrin)
class Peer2Peer:
    """Contains the logic for MURRAYs distributed name resolution and other murray-peer2peer operation"""
    __knownPeers = knownPeers
    __waitingList = waitingList

    def checkIncoming(self, argsIn_dict):
        """check if the murray_id given in an incoming message is really our Murray_ID.

        If not, a HELLO is sent to correct the sender's error

        returns True/False"""
        if argsIn_dict['source']['ip'] != argsIn_dict['receivedfrom'][0]:
            self.__knownPeers.update(argsIn_dict['source']['uid'], 
            ip = argsIn_dict['receivedfrom'][0])
            self.sendhello(argsIn_dict['source']['uid'])
            return False        
        if argsIn_dict['destination']['uid'] != my_identity['uid']:
            self.sendhello(argsIn_dict['source']['uid'])
            return False
        else:
            return True


    def receiveechorequest(self, argsIn_dict):
        """response to an echo-request
        sends an echo-reply, and - if necessary - updates known peers information
        
        returns nothing"""
        sourceClient = argsIn_dict['source']
        self.__knownPeers.update(sourceClient['uid'], sourceClient['ipversion'], sourceClient['ip'], sourceClient['port'])
        argsOut_dict = {'destination' : argsIn_dict['source'], 
                'source' : my_identity ,
                'request_ID' : argsIn_dict['request_ID']}
        self.sendechoresponse(argsOut_dict) 

    def receiveechoresponse(self, argsIn_dict):
        """Handles echo-Replies - drops them when they didn't originated here,     
        otherwise updates priorityList or Known Peers Cache
        
        returns nothing"""
        try:
            request = self.__waitingList.get(argsIn_dict['request_ID'])
            if request[0] == 'echo':
                self.__waitingList.remove(argsIn_dict['request_ID'])
                self.__knownPeers.toTop(argsIn_dict['source']['uid'])
            else:
                pass
        except ValueError:
            pass
        
    def receivecacherequest(self, argsIn_dict):
        """Handles incoming cache-requests
        
        returns nothing"""
        murray_id = argsIn_dict['source']['uid']
        ipversion = argsIn_dict['source']['ipversion']
        ip = argsIn_dict['source']['ip']
        port = argsIn_dict['source']['port']
        count = argsIn_dict['count']
        self.__knownPeers.update(murray_id, ipversion, ip, port)
        answer_list = self.__knownPeers.getByPriority(1, count)
        argsOut_dict = {'destination' : argsIn_dict['source'],
                'source' : my_identity}
        argsOut_dict['list'] = answer_list
        self.sendcacheresponse(argsOut_dict)
        
    def receivecacheresponse(self, argsIn_dict):
        """Handles incoming cache responses
        
        returns nothing"""
        try:
            request = self.__waitingList.get(argsIn_dict['requestid'])
            if request[0] == 'cache' or 'hello':
                self.__waitingList.remove(argsIn_dict['requestid'])
                sourceClient = argsIn_dict['source']
                self.__knownPeers.update(sourceClient['uid'], sourceClient['ipversion'], sourceClient['ip'], sourceClient['port'])
                liste = argsIn_dict['list']
                for item in liste.iteritems():
                    self.__knownPeers.update(item[0], item[1]['ipversion'], item[1]['ip'], item[1]['port'])
                if request[0] == 'hello' and my_identity['ip'] != argsIn_dict['destination']['ip']:
                    my_identity['ip'] = argsIn_dict['destination']['ip']
                    dsl_user(my_identity['ip'])    
            else:
                pass
        except ValueError:
            pass
        
        
            
    def receivehello(self, argsIn_dict):
        """Handles incoming "Hello" requests. 
        
        gets ip-address (if not given) -- and responds as when receiving a cache request"""
        if argsIn_dict['source']['ip'] == '':
            argsIn_dict['source']['ip'] = argsIn_dict['receivedfrom'][0]
        if argsIn_dict['count']: self.receivecacherequest(argsIn_dict)
    
    def receiveuserdata(self, argsIn_dict):
        """Handles incoming user data (Decryption, verification, authentication, delivery)
        
        returns nothing"""
        murray_id = argsIn_dict['source']['ui']
        if crypto.known(murray_id):
            clearMsg = crypto.decrypt(murray_id, argsIn_dict['data'])
            if not clearMsg:
                pass
            else:
                ###thread.start_new_thread($$$XMPPPFUNCTION$$$$, (clearMsg,))
                # p2p management stuff
                self.__knownPeers.update(murray_id, ipversion, ip, port)
        else:
            self.__cryptoBuffer.add(murray_id, ('in',argsIn_dict))
            self.sendpkrequest(murray_id)
            
    def receivesearchrequest(self, argsIn_dict):
        """Responsible for incoming search requests. At current state, it is only
        possible to search for a Murray ID
        
        returns nothing"""
        self.__knownPeers.update(argsIn_dict['source']['uid'], argsIn_dict['source']['ipversion'], argsIn_dict['source']['ip'], argsIn_dict['source']['port'])
        knownNodes = Search().search(argsIn_dict['searchfields'], argsIn_dict['operation'])
        if knownNodes:
            argsOut_dict = {'destination' : argsIn_dict['source'],
                    'source' : my_identity,
                    'responseto' : argsIn_dict['requestid'], 
                    'searchfields' : argsIn_dict['searchfields'],
                    'foundnodes' : knownNodes}
            self.sendsearchresponse(argsOut_dict)
        else:
            if argsIn_dict['ttl'] >= 10:
                pass
            elif argsIn_dict['ttl'] > 0:
                ttl = argsIn_dict['ttl'] - 1
                self.sendsearchrequest(argsIn_dict['searchfields'], ttl = ttl)
            else:
                pass
            
    def receivesearchresponse(self, argsIn_dict):
        """Handles incoming search responses -- performs some p2p actios and then
        forwards them to the jabber module.
        
        returns nothing"""
        self.__knownPeers.update(argsIn_dict['source']['uid'], argsIn_dict['source']['ipversion'], argsIn_dict['source']['ip'], argsIn_dict['source']['port'])
        try:
            request = self.__waitingList.get(argsIn_dict['requestid'])
            if request[0] == 'usersearch':
                for node in argsIn_dict['foundnodes'].iteritems():
                    self.__knownPeers.update(node[0], node[1]['ipversion'], node[1]['ip'], node[1]['port'])
                globalv.XMPPSERVER.informuser('Your search returned \
                the following results: ' +  str(foundnodes))
            else:
                if request[0] == 'murraysearch':
                #in case the search was sent automatically, resend the original packet to the first given node    
                    node = argsIn_dict['foundnodes'].iteritems()[0]
                    self.__knownPeers.update(node[0], node[1]['ipversion'], node[1]['ip'], node[1]['port'])
                    argsOut_dict = request[1]
                    argsOut_dict['destination']['ipversion'] = node[1]['ipversion']
                    argsOut_dict['destination']['ip'] = node[1]['ip']
                    argsOut_dict['destination']['port'] = node[1]['port']
                    xml.incoming(request[1]['type'], request[1]) ####
                else:
                    pass
        except ValueError:
            pass
                
    def receivepkrequest(self, argsIn_dict):
        """responds to Public-Key-requests
        
        returns nothing"""
        self.__knownPeers.update(argsIn_dict['source']['uid'], argsIn_dict['source']['ipversion'], argsIn_dict['source']['ip'], argsIn_dict['source']['port'])
        pk = crypto.getPK()        
        argsOut_dict = {}
        argsOut_dict['destination'] = argsIn_dict['source']
        argsOut_dict['source'] = my_identity
        argsOut_dict['publickey'] = pk[0]
        argsOut_dict['signature'] = pk[1]
        self.sendpkresponse(argsOut_dict)
        
    def receivepkresponse(self, argsIn_dict):
        """imports a received public key to the keyring
        
        returns nothing"""
        self.__knownPeers.update(argsIn_dict['source']['uid'], argsIn_dict['source']['ipversion'], argsIn_dict['source']['ip'], argsIn_dict['source']['port'])
        try:
            request = self.__waitingList.get(argsIn_dict['requestid'])
            if request[0] == 'pk':        
                crypto.storePK(argsIn_dict['publickey'])
                try:
                    data = self.__cryptoBuffer.get(argsIn_dict['source']['uid'])
                    if data[0] == 'in':
                        self.receiveuserdata(data[1])
                    else:
                        self.senduserdata(data[1])
                except KeyError:
                    pass
            else:
                pass            
        except ValueError:
            pass
        
    ###NOW the "sender" part
    def sendechorequest(self, murray_id):
        """sends an echo request and adds it to the waiting buffer
        
        returns nothing"""
        if self.__knownPeers.known(murray_id):
            peer = self.__knownPeers.getByMurray_ID(murray_id)
            destination = {'uid':murray_id, 'ipversion':peer[0], 'ip':peer[1], 'port':peer[2]}
            argsOut_dict = {'type':'echorequest', 'destination':destination, 'source':my_identity}
            argsOut_dict['requestid'] = self.__waitingList.add('echo', argsOut_dict)
            out('echorequest', argsOut_dict)
        else:
            destination = {'uid':murray_id}
            argsOut_dict = {'type':'echorequest', 'destination':destination, 'source':my_identity}
            self.sendsearchrequest(search = {'murray_id' : murray_id}, searchtype = 'murraysearch', request = argsOut_dict)
            
    def sendechoresponse(self, argsIn_dict):
        """sends an echo response
        
        returns nothing"""
        argsIn_dict['type'] = 'echoresponse'
        out('echoresponse', argsIn_dict)
        
    def sendcacherequest(self, murray_id, count=100):
        """sends a cache request to MURRAY_ID with a default count of 1000
        
        returns nothing"""
        if self.__knownPeers.known(murray_id):
            peer = self.__knownPeers.getByMurray_ID(murray_id)
            destination = {'uid':murray_id, 'ipversion':peer[0], 'ip':peer[1], 'port':peer[2]}
            argsOut_dict = {'type':'cacherequest', 'destination':destination, 'source':my_identity, 'count':count }
            argsOut_dict['requestid'] = self.__waitingList.add('cache', argsOut_dict)
            out('cacherequest', argsOut_dict)
        else:
            destination = {'uid':murray_id}
            argsOut_dict = {'type':'cacherequest', 'destination':destination, 'source' : my_identity, 'count':count}
            self.sendsearchrequest(search = {'murray_id' : murray_id}, searchtype = 'murraysearch', request = argsOut_dict)
            
    def sendcacheresponse(self, argsIn_dict):
        """sends a cache response.
        
        returns nothing"""
        argsIn_dict['type'] = 'cacheresponse'
        out('cacheresponse', argsIn_dict)

    def sendhello(self, murray_id, count=100):
        """sends an hello request to the specified murray-id

        returns nothing"""
        if self.__knownPeers.known(murray_id):
            peer = self.__knownPeers.getByMurray_ID(murray_id)
            destination = {'uid':murray_id, 'ipversion':peer[0], 'ip':peer[1], 'port':peer[2]}
            argsOut_dict = {'type':'hello', 'destination':destination, 'source':my_identity, 'count':count }
            argsOut_dict['requestid'] = self.__waitingList.add('hello', argsOut_dict)
            argsOut_dict['count'] = count
            out('cacherequest', argsOut_dict)
        else:
            if my_identity['ip']:
                destination = {'uid':murray_id}
                argsOut_dict = {'type':'hello', 'destination':destination, 'source' : my_identity, 'count':count}
                self.sendsearchrequest(search = {'murray_id' : murray_id}, searchtype = 'murraysearch', request = argsOut_dict)
            else:
                raise ValueError
            # raise a ValueError if no own IP is known and no known peer present
                
    def senduserdata(self, murray_id, message):
        """encrypts and sends a prepared message
        
        returns nothing"""
        try:
            if self.__knownPeers.known(murray_id):
                peer = self.__knownPeers.getByMurray_ID(murray_id)
                destination = {'uid':murray_id, 'ipversion':peer[0], 'ip':peer[1], 'port':peer[2]}
                argsOut_dict = {'type':'userdata', 'destination':destination, 'source':my_identity, 'data' : message}
                if crypto.known(murray_id):
                    enc_message = xml.incoming(murray_id, argsOut_dict['data'])
                    argsOut_dict['data'] = enc_message
                    out('userdata', argsOut_dict)
                else:
                    self.__cryptoBuffer.add(murray_id, ('out', argsOut_dict))
                    self.sendpkrequest(murray_id)
            else:
                destination = {'uid':murray_id}
                argsOut_dict = {'type':'userdata', 'destination':destination, 'source' : my_identity, 'data':message}
                self.sendsearchrequest(search = {'murray_id' : murray_id}, searchtype = 'murraysearch', request = argsOut_dict)
        except:
            thread.interrupt_main()
            
    def sendsearchrequest(self, search, searchtype='usersearch', request=None, operation = 'and', ttl = None,):
        """Sends a search request
        
        returns nothing"""
	number = globalv.SEARCH_ASK_PEERS if globalv.SEARCH_ASK_PEERS else 10
	if not ttl:
		ttl = globalv.SEARCH_TTL if globalv.SEARCH_TTL else 4
        dests = cache.getByPriority(1, number)
        argsOut_dict = { 'type' : 'searchrequest', 'source' : my_identity, 'ttl':ttl, 'searchfields' : search, 'operation' : operation }
        if searchtype == 'usersearch':
            argsOut_dict['requestid'] = self.__waitingList.add('usersearch', argsOut_dict)
        else:
            argsOut_dict['requestid'] = self.__waitingList.add('murraysearch', request)
        for dest in dests.iteritems():
            destination = {'uid' : dest[0], 'ipversion' : dest[1]['ipversion'], 'ip' : dest[1]['ip'], 'port' : dest[1]['port']}
            argsOut_dict['destination'] = destination
            argsOut_dict
            out('searchrequest', argsOut_dict)        

    def sendsearchresponse(self, argsIn_dict):
        """sends a search response.
        
        returns nothing"""
        argsIn_dict['type'] = 'searchresponse'
        out('searchresponse', argsIn_dict)    
    
    def sendpkrequest(self, murray_id):
        """sends out a public key request to a given murray-id
        
        returns nothing"""
        argsOut_dict = {'type' : 'pkrequest', 'source' : my_identity }
        if self.__knownPeers.known(murray_id):
            peer = self.__knownPeers.getByMurray_ID(murray_id)
            destination = {'uid':murray_id, 'ipversion':peer[0], 'ip':peer[1], 'port':peer[2]}
            argsOut_dict['destination'] = destination
            argsOut_dict['requestid'] = self.__waitingList.add('pk', argsOut_dict)
            out('pkrequest',argsOut_dict)
        else:
            destination = {'uid':murray_id}
            argsOut_dict['destination'] = destination
            self.sendsearchrequest(search = {'murray_id' : murray_id}, searchtype = 'murraysearch', request = argsOut_dict)
             
    def sendpkresponse(self, argsIn_dict):
        """sends a public key response
        
        returns nothing"""
        argsIn_dict['type'] = 'pkresponse'
        out('pkresponse', argsIn_dict)
        

p2p = Peer2Peer()
def incoming(type_str, args_dict):
    """dispatches incoming MURRAY-management packets to the according functions
    
    returns nothing"""
    if p2p.checkIncoming(args_dict):
        try:
            function = getattr(p2p, 'receive' + type_str)
            function(args_dict)
        except AttributeError:
            raise NotImplementedError

def sendXMPPMessage(murray_ids, messageText):
    """sends data generated by the JabberServer module
    
    returns nothing"""
    for murray_id in list(murray_ids):
        thread.start_new_thread(p2p.senduserdata, (murray_id, messageText))
        
def extendSearch(args_dict):
    """asks the user whether he wants to extend a search an - if it is requested - does so
    
    returns nothing"""
    ttl = args_dict['ttl'] * 2

    if globalv.XMPPServer.askUserBoolean(\
    'Your search request ' + str(search['searchfields']) + \
    ' returned no further results. Do you want \
    to extent your search?'):
        if ttl <= 64:
            args_dict['ttl'] *= 2
            out(args_dict)
        else:
            globalv.XMPPSERVER.informUser('Your search request ' + \
            str(args_dict['searchfields']) + \
            ' has been extended too many times. He\'s not there! Live with it!')
            waitingList.remove(args_dict['requestid'])
            
def updateKnownPeers(sleeptime = 5, count = 2):
    """updates the known Peers cache with fresh info from count peers 
    every sleeptime seconds"""
    index = 0
    while globalv.RUNNING:
        time.sleep(sleeptime)
        targets = knownPeers.getMurray_IDByPriority(index, count)
        if targets:
            for uid in targets:
                thread.start_new_thread(net.Peer2Peer().sendcacherequest, (uid))
            index += count
        
def sir_pingALot(sleeptime = 2, count = 100):
    """pings the "low ranks" in the known Peers cache."""
    index = -1
    while globalv.RUNNING:
        time.sleep(sleeptime)
        target = knownPeers.getMurray_IDByPriority(index, 1)
        if target:
            p2p.sendechorequest(target)
            if (index > (-1) * count):
                index -= 1
            else:
                index = -1
            
def makeClean(sleeptime = 10):
    """cleans up knwon Peers cache and waiting List"""
    while globalv.RUNNING:
        time.sleep(sleeptime)
        knownPeers.clean()
        time.sleep(sleeptime)
        extendable = waitingList.cleanUp()
        if extendable:
            for search in extendable:
                thread.start_new_thread(extendSearch, search)
                
def bootstrap(number):
    """gets into the network by sending hello-packets to number peers"""
    print 'restoring list of known peers'
    knownPeers.restoreOnStartup(globalv.KNOWN_PEERS_FILE)
    for entry in globalv.KNOWN_PEERS:
        knownPeers.update(entry[0], entry[1], entry[2], entry[3])    
    print 'Getting some more peer info'
    knownUids = knownPeers.getAllMurray_IDs()
    if len(knownUids) == 0:
        print 'You ain\'t got no Friends... this sucks!'
    elif len(knownUids) <= number:
        for entry in knownUids:
            thread.start_new_thread(p2p.sendhello, (entry,))
    else:
        for i in range(number):
            thread.start_new_thread(p2p.sendhello, \
            (kp.getMurray_IDByPriority,))
            
def shutdown():
    """does everythin necessary for shutting down"""
    knownPeers.saveOnShutdown(globalv.KNOWN_PEERS_FILE)


