import binascii
import collections
import logging
import time

from twisted.internet import defer, reactor


log = logging.getLogger(__name__)


class DHTHashAnnouncer(object):
    """This class announces to the DHT that this peer has certain blobs"""
    def __init__(self, dht_node, peer_port):
        self.dht_node = dht_node
        self.peer_port = peer_port
        self.suppliers = []
        self.next_manage_call = None
        self.hash_queue = collections.deque()
        self._concurrent_announcers = 0

    def run_manage_loop(self):

        from twisted.internet import reactor

        if self.peer_port is not None:
            self._announce_available_hashes()
        self.next_manage_call = reactor.callLater(60, self.run_manage_loop)

    def stop(self):
        log.info("Stopping %s", self)
        if self.next_manage_call is not None:
            self.next_manage_call.cancel()
            self.next_manage_call = None

    def add_supplier(self, supplier):
        self.suppliers.append(supplier)

    def immediate_announce(self, blob_hashes):
        if self.peer_port is not None:
            return self._announce_hashes(blob_hashes)
        else:
            return defer.succeed(False)

    def _announce_available_hashes(self):
        log.debug('Announcing available hashes')
        ds = []
        for supplier in self.suppliers:
            d = supplier.hashes_to_announce()
            d.addCallback(self._announce_hashes)
            ds.append(d)
        dl = defer.DeferredList(ds)
        return dl

    def _announce_hashes(self, hashes):
        if not hashes:
            return
        log.debug('Announcing %s hashes', len(hashes))
        # TODO: add a timeit decorator
        start = time.time()
        ds = []

        for h in hashes:
            announce_deferred = defer.Deferred()
            ds.append(announce_deferred)
            self.hash_queue.append((h, announce_deferred))

        def announce():
            if len(self.hash_queue):
                h, announce_deferred = self.hash_queue.popleft()
                log.debug('Announcing blob %s to dht', h)
                d = self.dht_node.announceHaveBlob(binascii.unhexlify(h), self.peer_port)
                d.chainDeferred(announce_deferred)
                d.addBoth(lambda _: reactor.callLater(0, announce))
            else:
                self._concurrent_announcers -= 1

        for i in range(self._concurrent_announcers, 5):
            # TODO: maybe make the 5 configurable
            self._concurrent_announcers += 1
            announce()
        d = defer.DeferredList(ds)
        d.addCallback(lambda _: log.debug('Took %s seconds to announce %s hashes',
                                          time.time() - start, len(hashes)))
        return d


class DHTHashSupplier(object):
    """Classes derived from this class give hashes to a hash announcer"""
    def __init__(self, announcer):
        if announcer is not None:
            announcer.add_supplier(self)
        self.hash_announcer = announcer
        self.hash_reannounce_time = 60 * 60  # 1 hour

    def hashes_to_announce(self):
        pass