# Path: subspace/utils.py
# def deferredDict(d):
#     """
#     Just like a :class:`defer.DeferredList` but instead accepts and returns a :class:`dict`.
# 
#     Args:
#         d: A :class:`dict` whose values are all :class:`defer.Deferred` objects.
# 
#     Returns:
#         :class:`defer.DeferredList` whose callback will be given a dictionary whose
#         keys are the same as the parameter :obj:`d` and whose values are the results
#         of each individual deferred call.
#     """
#     if len(d) == 0:
#         return defer.succeed({})
# 
#     def handle(results, names):
#         rvalue = {}
#         for index in range(len(results)):
#             rvalue[names[index]] = results[index][1]
#         return rvalue
# 
#     dl = defer.DeferredList(d.values())
#     return dl.addCallback(handle, d.keys())
# 
# Path: subspace/node.py
# class Node:
#     def __init__(self, id, ip=None, port=None):
#         self.id = id
#         self.ip = ip
#         self.port = port
#         self.long_id = long(id.encode('hex'), 16)
# 
#     def sameHomeAs(self, node):
#         return self.ip == node.ip and self.port == node.port
# 
#     def distanceTo(self, node):
#         """
#         Get the distance between this node and another.
#         """
#         return self.long_id ^ node.long_id
# 
#     def __iter__(self):
#         """
#         Enables use of Node as a tuple - i.e., tuple(node) works.
#         """
#         return iter([self.id, self.ip, self.port])
# 
#     def __repr__(self):
#         return repr([self.long_id, self.ip, self.port])
# 
#     def __str__(self):
#         return "%s:%s" % (self.ip, str(self.port))
# 
# class NodeHeap(object):
#     """
#     A heap of nodes ordered by distance to a given node.
#     """
#     def __init__(self, node, maxsize):
#         """
#         Constructor.
# 
#         @param node: The node to measure all distnaces from.
#         @param maxsize: The maximum size that this heap can grow to.
#         """
#         self.node = node
#         self.heap = []
#         self.contacted = set()
#         self.maxsize = maxsize
# 
#     def remove(self, peerIDs):
#         """
#         Remove a list of peer ids from this heap.  Note that while this
#         heap retains a constant visible size (based on the iterator), it's
#         actual size may be quite a bit larger than what's exposed.  Therefore,
#         removal of nodes may not change the visible size as previously added
#         nodes suddenly become visible.
#         """
#         peerIDs = set(peerIDs)
#         if len(peerIDs) == 0:
#             return
#         nheap = []
#         for distance, node in self.heap:
#             if node.id not in peerIDs:
#                 heapq.heappush(nheap, (distance, node))
#         self.heap = nheap
# 
#     def getNodeById(self, id):
#         for _, node in self.heap:
#             if node.id == id:
#                 return node
#         return None
# 
#     def allBeenContacted(self):
#         return len(self.getUncontacted()) == 0
# 
#     def getIDs(self):
#         return [n.id for n in self]
# 
#     def markContacted(self, node):
#         self.contacted.add(node.id)
# 
#     def popleft(self):
#         if len(self) > 0:
#             return heapq.heappop(self.heap)[1]
#         return None
# 
#     def push(self, nodes):
#         """
#         Push nodes onto heap.
# 
#         @param nodes: This can be a single item or a C{list}.
#         """
#         if not isinstance(nodes, list):
#             nodes = [nodes]
# 
#         for node in nodes:
#             if node.id not in [n.id for n in self]:
#                 distance = self.node.distanceTo(node)
#                 heapq.heappush(self.heap, (distance, node))
# 
#     def __len__(self):
#         return min(len(self.heap), self.maxsize)
# 
#     def __iter__(self):
#         nodes = heapq.nsmallest(self.maxsize, self.heap)
#         return iter(map(itemgetter(1), nodes))
# 
#     def getUncontacted(self):
#         return [n for n in self if n.id not in self.contacted]


from collections import Counter
from subspace.log import Logger
from subspace.utils import deferredDict
from subspace.node import Node, NodeHeap
            rpcmethod: The protocol's callfindValue or callFindNode.

        The process:
          1. calls find_* to current ALPHA nearest not already queried nodes,
             adding results to current nearest list of k nodes.
          2. current nearest list needs to keep track of who has been queried already
             sort by nearest, keep KSIZE
          3. if list is same as last time, next call should be to everyone not
             yet queried
          4. repeat, unless nearest list has all been queried, then ur done
        """
        self.log.info("crawling with nearest: %s" % str(tuple(self.nearest)))
        count = self.alpha
        if self.nearest.getIDs() == self.lastIDsCrawled:
            self.log.info("last iteration same as current - checking all in list now")
            count = len(self.nearest)
        self.lastIDsCrawled = self.nearest.getIDs()

        ds = {}
        for peer in self.nearest.getUncontacted()[:count]:
            ds[peer.id] = rpcmethod(peer, self.node)
            self.nearest.markContacted(peer)
        return deferredDict(ds).addCallback(self._nodesFound)


class ValueSpiderCrawl(SpiderCrawl):
    def __init__(self, protocol, node, peers, ksize, alpha):
        SpiderCrawl.__init__(self, protocol, node, peers, ksize, alpha)
        # keep track of the single nearest node without value - per
        # section 2.3 so we can set the key there if found

# Path: subspace/utils.py
# def deferredDict(d):
#     """
#     Just like a :class:`defer.DeferredList` but instead accepts and returns a :class:`dict`.
# 
#     Args:
#         d: A :class:`dict` whose values are all :class:`defer.Deferred` objects.
# 
#     Returns:
#         :class:`defer.DeferredList` whose callback will be given a dictionary whose
#         keys are the same as the parameter :obj:`d` and whose values are the results
#         of each individual deferred call.
#     """
#     if len(d) == 0:
#         return defer.succeed({})
# 
#     def handle(results, names):
#         rvalue = {}
#         for index in range(len(results)):
#             rvalue[names[index]] = results[index][1]
#         return rvalue
# 
#     dl = defer.DeferredList(d.values())
#     return dl.addCallback(handle, d.keys())
# 
# Path: subspace/node.py
# class Node:
#     def __init__(self, id, ip=None, port=None):
#         self.id = id
#         self.ip = ip
#         self.port = port
#         self.long_id = long(id.encode('hex'), 16)
# 
#     def sameHomeAs(self, node):
#         return self.ip == node.ip and self.port == node.port
# 
#     def distanceTo(self, node):
#         """
#         Get the distance between this node and another.
#         """
#         return self.long_id ^ node.long_id
# 
#     def __iter__(self):
#         """
#         Enables use of Node as a tuple - i.e., tuple(node) works.
#         """
#         return iter([self.id, self.ip, self.port])
# 
#     def __repr__(self):
#         return repr([self.long_id, self.ip, self.port])
# 
#     def __str__(self):
#         return "%s:%s" % (self.ip, str(self.port))
# 
# class NodeHeap(object):
#     """
#     A heap of nodes ordered by distance to a given node.
#     """
#     def __init__(self, node, maxsize):
#         """
#         Constructor.
# 
#         @param node: The node to measure all distnaces from.
#         @param maxsize: The maximum size that this heap can grow to.
#         """
#         self.node = node
#         self.heap = []
#         self.contacted = set()
#         self.maxsize = maxsize
# 
#     def remove(self, peerIDs):
#         """
#         Remove a list of peer ids from this heap.  Note that while this
#         heap retains a constant visible size (based on the iterator), it's
#         actual size may be quite a bit larger than what's exposed.  Therefore,
#         removal of nodes may not change the visible size as previously added
#         nodes suddenly become visible.
#         """
#         peerIDs = set(peerIDs)
#         if len(peerIDs) == 0:
#             return
#         nheap = []
#         for distance, node in self.heap:
#             if node.id not in peerIDs:
#                 heapq.heappush(nheap, (distance, node))
#         self.heap = nheap
# 
#     def getNodeById(self, id):
#         for _, node in self.heap:
#             if node.id == id:
#                 return node
#         return None
# 
#     def allBeenContacted(self):
#         return len(self.getUncontacted()) == 0
# 
#     def getIDs(self):
#         return [n.id for n in self]
# 
#     def markContacted(self, node):
#         self.contacted.add(node.id)
# 
#     def popleft(self):
#         if len(self) > 0:
#             return heapq.heappop(self.heap)[1]
#         return None
# 
#     def push(self, nodes):
#         """
#         Push nodes onto heap.
# 
#         @param nodes: This can be a single item or a C{list}.
#         """
#         if not isinstance(nodes, list):
#             nodes = [nodes]
# 
#         for node in nodes:
#             if node.id not in [n.id for n in self]:
#                 distance = self.node.distanceTo(node)
#                 heapq.heappush(self.heap, (distance, node))
# 
#     def __len__(self):
#         return min(len(self.heap), self.maxsize)
# 
#     def __iter__(self):
#         nodes = heapq.nsmallest(self.maxsize, self.heap)
#         return iter(map(itemgetter(1), nodes))
# 
#     def getUncontacted(self):
#         return [n for n in self if n.id not in self.contacted]


# Path: subspace/crawling.py
from collections import Counter
from subspace.log import Logger
from subspace.utils import deferredDict
from subspace.node import Node, NodeHeap

            rpcmethod: The protocol's callfindValue or callFindNode.

        The process:
          1. calls find_* to current ALPHA nearest not already queried nodes,
             adding results to current nearest list of k nodes.
          2. current nearest list needs to keep track of who has been queried already
             sort by nearest, keep KSIZE
          3. if list is same as last time, next call should be to everyone not
             yet queried
          4. repeat, unless nearest list has all been queried, then ur done
        """
        self.log.info("crawling with nearest: %s" % str(tuple(self.nearest)))
        count = self.alpha
        if self.nearest.getIDs() == self.lastIDsCrawled:
            self.log.info("last iteration same as current - checking all in list now")
            count = len(self.nearest)
        self.lastIDsCrawled = self.nearest.getIDs()

        ds = {}
        for peer in self.nearest.getUncontacted()[:count]:
            ds[peer.id] = rpcmethod(peer, self.node)
            self.nearest.markContacted(peer)
        return deferredDict(ds).addCallback(self._nodesFound)


class ValueSpiderCrawl(SpiderCrawl):
    def __init__(self, protocol, node, peers, ksize, alpha):
        SpiderCrawl.__init__(self, protocol, node, peers, ksize, alpha)
        # keep track of the single nearest node without value - per
        # section 2.3 so we can set the key there if found

        self.nearestWithoutValue = NodeHeap(self.node, 1)