import logging
from zope.interface import implements
from lbrynet import interfaces
from lbrynet.core.BlobInfo import BlobInfo
from lbrynet.core.client.BlobRequester import BlobRequester
from lbrynet.core.client.ConnectionManager import ConnectionManager
from lbrynet.core.client.DownloadManager import DownloadManager
from lbrynet.core.Error import InvalidBlobHashError, DownloadSDTimeout
from lbrynet.core.utils import is_valid_blobhash, safe_start_looping_call, safe_stop_looping_call
from twisted.python.failure import Failure
from twisted.internet import defer
from twisted.internet.task import LoopingCall

log = logging.getLogger(__name__)


class SingleBlobMetadataHandler(object):
    implements(interfaces.IMetadataHandler)

    def __init__(self, blob_hash, download_manager):
        self.blob_hash = blob_hash
        self.download_manager = download_manager

    ######## IMetadataHandler #########

    def get_initial_blobs(self):
        log.debug("Returning the blob info")
        return defer.succeed([BlobInfo(self.blob_hash, 0, None)])

    def final_blob_num(self):
        return 0


class SingleProgressManager(object):
    def __init__(self, download_manager, finished_callback, timeout_callback, timeout):
        self.finished_callback = finished_callback
        self.timeout_callback = timeout_callback
        self.download_manager = download_manager

        self.timeout = timeout
        self.timeout_counter = 0
        self.checker = LoopingCall(self._check_if_finished)

    def start(self):
        safe_start_looping_call(self.checker, 1)
        return defer.succeed(True)

    def stop(self):
        safe_stop_looping_call(self.checker)
        return defer.succeed(True)

    def _check_if_finished(self):
        if self.stream_position() == 1:
            blob_downloaded = self.download_manager.blobs[0]
            log.debug("The blob %s has been downloaded. Calling the finished callback",
                        str(blob_downloaded))
            safe_stop_looping_call(self.checker)
            self.finished_callback(blob_downloaded)
        elif self.timeout is not None:
            self.timeout_counter += 1
            if self.timeout_counter >= self.timeout:
                safe_stop_looping_call(self.checker)
                self.timeout_callback()

    def stream_position(self):
        blobs = self.download_manager.blobs
        if blobs and blobs[0].get_is_verified():
            return 1
        return 0

    def needed_blobs(self):
        blobs = self.download_manager.blobs
        assert len(blobs) == 1
        return [b for b in blobs.itervalues() if not b.get_is_verified()]


class DummyBlobHandler(object):
    def __init__(self):
        pass

    def handle_blob(self, blob, blob_info):
        pass


class StandaloneBlobDownloader(object):
    def __init__(self, blob_hash, blob_manager, peer_finder,
                 rate_limiter, payment_rate_manager, wallet,
                 timeout=None):
        self.blob_hash = blob_hash
        self.blob_manager = blob_manager
        self.peer_finder = peer_finder
        self.rate_limiter = rate_limiter
        self.payment_rate_manager = payment_rate_manager
        self.wallet = wallet
        self.timeout = timeout
        self.download_manager = None
        self.finished_deferred = None

    def download(self):
        if not is_valid_blobhash(self.blob_hash):
            return defer.fail(Failure(InvalidBlobHashError(self.blob_hash)))

        def cancel_download(d):
            self.stop()

        self.finished_deferred = defer.Deferred(canceller=cancel_download)
        self.download_manager = DownloadManager(self.blob_manager)
        self.download_manager.blob_requester = BlobRequester(self.blob_manager, self.peer_finder,
                                                             self.payment_rate_manager, self.wallet,
                                                             self.download_manager)
        self.download_manager.blob_info_finder = SingleBlobMetadataHandler(self.blob_hash,
                                                                           self.download_manager)
        self.download_manager.progress_manager = SingleProgressManager(self.download_manager,
                                                                       self._blob_downloaded,
                                                                       self._download_timedout,
                                                                       self.timeout)
        self.download_manager.blob_handler = DummyBlobHandler()
        self.download_manager.wallet_info_exchanger = self.wallet.get_info_exchanger()
        self.download_manager.connection_manager = ConnectionManager(
            self, self.rate_limiter,
            [self.download_manager.blob_requester],
            [self.download_manager.wallet_info_exchanger]
        )
        d = self.download_manager.start_downloading()
        d.addCallback(lambda _: self.finished_deferred)
        return d

    def stop(self):
        return self.download_manager.stop_downloading()

    def _blob_downloaded(self, blob):
        self.stop()
        if not self.finished_deferred.called:
            self.finished_deferred.callback(blob)

    def _download_timedout(self):
        self.stop()
        if not self.finished_deferred.called:
            self.finished_deferred.errback(DownloadSDTimeout(self.blob_hash))

    def insufficient_funds(self, err):
        self.stop()
        if not self.finished_deferred.called:
            self.finished_deferred.errback(err)