# This module contains all the features that are only available if we're
# running under Python 2.2 or later.
from __future__ import generators
from Queue import Queue, Empty, Full
import warnings, threading
from main import BaseCursor, make_PgResultSetClass

# Singleton to mark that the last element of a queue. If this is put into a
# queue, the consumer will stop reading from the queue.
END_OF_QUEUE = 1

class Columns:
    def __init__(self, val):
        self.val = val

    def get_value(self):
        return self.val

class Producer(threading.Thread):
    """A separate producer thread that wraps sqlite_exec, provides all callback
    function for sqlite_exec and fills a queue with the result rows for the
    consumer to read from.""" 
    def __init__(self, *args, **kwargs):
        self.stopflag = 0
        self.queue = kwargs["queue"]
        self.conn = kwargs["conn"]
        self.sql = kwargs["sql"]
        del kwargs["queue"]
        del kwargs["conn"]
        del kwargs["sql"]
        threading.Thread.__init__(self, *args, **kwargs)

        self.have_produced_colnames = 0

    def run(self):
        def callback(arg1, items, colnames):
            """Callback function for sqlite_exec."""
            if self.stopflag == 1:
                self.queue.put(END_OF_QUEUE, 1)
                # We want to abort the query:
                self.stopflag = 2
                return 0
            elif self.stopflag == 2:
                return 0
            else:
                if not self.have_produced_colnames:
                    self.queue.put(Columns(colnames), 1)
                    self.have_produced_colnames = 1
                self.queue.put(items, 1)
            return 0
            
        self.conn.db.sqlite_exec(self.sql, callback, None)
        self.queue.put(END_OF_QUEUE, 1)
            
class IterCursor(BaseCursor):
    def __init__(self, conn):
        self.queue = None
        self.producer = None

        BaseCursor.__init__(self, conn)  
        self._invalidate()

    def _invalidate(self):
        """Invalidate is called when a new query is processed with execute or
        executemany or the cursor is closed or the cursor object gets out of
        scope."""
        if self.producer is not None:
            self.producer.stopflag = 1

            # Try to empty the queue, so the producer can insert its
            # END_OF_QUEUE and won't block forever.
            try:
                while 1:
                    item = self.queue.get(0)
            except Empty, reason:
                pass

            self.producer.join()
            self.producer = None
        self.queue = None

    def _execute_sql(self, sql):
        self._invalidate()
        self.__execute(sql)

    def _after_execute_sql(self):
        self.current_recnum = 0

    def __execute(self, sql):
        """Build a queue and start the producer thread."""

        # We keep max 10 result rows in memory at any given point in time. The
        # number was chosen arbitrarily - there should be no reason to make it
        # modifiable by the user. Perhaps a higher number can lead to better
        # performance, perhaps a lower number works just as well. I (GH)
        # haven't tried.
        self.queue = Queue(10)

        self.producer = Producer(conn=self.con, sql=sql, queue=self.queue)
        self.producer.start()

        columns = self.queue.get(1)
        if columns is END_OF_QUEUE:
            self.queue.put(END_OF_QUEUE, 1)
            return
             
        # We're building the description attribute here.
        template = ['colname', 254, 1, 1, 0, 0, 1]
        self.description = []
        for col in columns.get_value():
            item = template[:]
            item[0] = col
            self.description.append(item)
        self.PgResultSetClass = make_PgResultSetClass(self.description[:])

    #
    # DB-API methods:
    #
    
    def executemany(self, query, parm_seq):
        raise _sqlite.InterfaceError, \
            "executemany failed - not supported for IterCursor"

    def fetchone(self):
        self._checkNotClosed("fetchone")

        item = self.queue.get(1)
        if item is END_OF_QUEUE:
            return None
        else:
            retval = self.PgResultSetClass(self._convert_types(item))
            return retval

    def fetchmany(self, howmany=None):
        self._checkNotClosed("fetchmany")

        if howmany is None:
            howmany = self.arraysize

        for i in range(howmany):
            item = self.queue.get(1)
            if item is END_OF_QUEUE:
                raise StopIteration
            retval = self.PgResultSetClass(self._convert_types(item))
            yield retval

    def fetchall(self):
        self._checkNotClosed("fetchall")

        while 1:
            item = self.queue.get(1)
            if item is END_OF_QUEUE:
                raise StopIteration
            retval = self.PgResultSetClass(self._convert_types(item))
            yield retval

    def __iter__(self):
        warnings.warn("DB-API extension cursor.__iter__() used")
        return self

    def next(self):
        warnings.warn("DB-API extension cursor.next() used")
        self._checkNotClosed("next")

        item = self.queue.get(1)
        if item is END_OF_QUEUE:
            raise StopIteration
        else:
            retval = self.PgResultSetClass(
                self._convert_types(item))
            return retval
