import Queue
import threading
import sys

# This file provides a class that will convert a function that
# takes a callback into a generator that returns an iterator.

# Based partially on http://stackoverflow.com/questions/9968592/

class IteratorizerThread(threading.Thread):
    def __init__(self, queue, function):
        """
        function: function to execute, which takes the
        callback (provided by this class) as an argument
        """
        threading.Thread.__init__(self)
        self.function = function
        self.queue = queue
        self.die = False

    def callback(self, data):
        if self.die:
            raise Exception("should die")
        self.queue.put((1, data))

    def run(self):
        try:
            result = self.function(self.callback)
        except:
            if sys is not None: # can be None during unclean shutdown
                self.queue.put((2, sys.exc_info()))
        else:
            self.queue.put((0, result))

class Iteratorizer(object):
    def __init__(self, function):
        """
        function: function to execute, which takes the
        callback (provided by this class) as an argument
        """
        self.function = function
        self.queue = Queue.Queue(maxsize = 1)
        self.thread = IteratorizerThread(self.queue, self.function)
        self.thread.daemon = True
        self.thread.start()

    def __del__(self):
        # If we get garbage collected, try to get rid of the
        # thread too by asking it to raise an exception, then
        # draining the queue until it's gone.
        self.thread.die = True
        while self.thread.isAlive():
            try:
                self.queue.get(True, 0.01)
            except: # pragma: no cover
                pass

    def __iter__(self):
        return self

    def next(self):
        (typ, data) = self.queue.get()
        if typ == 0:
            # function returned
            self.retval = data
            raise StopIteration
        elif typ == 1:
            # data available
            return data
        else:
            # exception
            raise data[0], data[1], data[2]
