import sys
import ffilib


sq3 = ffilib.open("libsqlite3")

sqlite3_open = sq3.func("i", "sqlite3_open", "sp")
#int sqlite3_close(sqlite3*);
sqlite3_close = sq3.func("i", "sqlite3_close", "p")
#int sqlite3_prepare(
#  sqlite3 *db,            /* Database handle */
#  const char *zSql,       /* SQL statement, UTF-8 encoded */
#  int nByte,              /* Maximum length of zSql in bytes. */
#  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
#  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
#);
sqlite3_prepare = sq3.func("i", "sqlite3_prepare", "psipp")
#int sqlite3_finalize(sqlite3_stmt *pStmt);
sqlite3_finalize = sq3.func("i", "sqlite3_finalize", "p")
#int sqlite3_step(sqlite3_stmt*);
sqlite3_step = sq3.func("i", "sqlite3_step", "p")
#int sqlite3_column_count(sqlite3_stmt *pStmt);
sqlite3_column_count = sq3.func("i", "sqlite3_column_count", "p")
#int sqlite3_column_type(sqlite3_stmt*, int iCol);
sqlite3_column_type = sq3.func("i", "sqlite3_column_type", "pi")
sqlite3_column_int = sq3.func("i", "sqlite3_column_int", "pi")
# using "d" return type gives wrong results
sqlite3_column_double = sq3.func("d", "sqlite3_column_double", "pi")
sqlite3_column_text = sq3.func("s", "sqlite3_column_text", "pi")
#sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
# TODO: should return long int
sqlite3_last_insert_rowid = sq3.func("i", "sqlite3_last_insert_rowid", "p")
#const char *sqlite3_errmsg(sqlite3*);
sqlite3_errmsg = sq3.func("s", "sqlite3_errmsg", "p")

# Too recent
##const char *sqlite3_errstr(int);
#sqlite3_errstr = sq3.func("s", "sqlite3_errstr", "i")


SQLITE_OK         = 0
SQLITE_ERROR      = 1
SQLITE_BUSY       = 5
SQLITE_MISUSE     = 21
SQLITE_ROW        = 100
SQLITE_DONE       = 101

SQLITE_INTEGER  = 1
SQLITE_FLOAT    = 2
SQLITE_TEXT     = 3
SQLITE_BLOB     = 4
SQLITE_NULL     = 5


class Error(Exception):
    pass


def check_error(db, s):
    if s != SQLITE_OK:
        raise Error(s, sqlite3_errmsg(db))


class Connections:

    def __init__(self, h):
        self.h = h

    def cursor(self):
        return Cursor(self.h)

    def close(self):
        s = sqlite3_close(self.h)
        check_error(self.h, s)


class Cursor:

    def __init__(self, h):
        self.h = h
        self.stmnt = None

    def execute(self, sql, params=None):
        if params:
            params = [quote(v) for v in params]
            sql = sql % tuple(params)
        print(sql)
        b = bytearray(4)
        s = sqlite3_prepare(self.h, sql, -1, b, None)
        check_error(self.h, s)
        self.stmnt = int.from_bytes(b, sys.byteorder)
        #print("stmnt", self.stmnt)
        self.num_cols = sqlite3_column_count(self.stmnt)
        #print("num_cols", self.num_cols)
        # If it's not select, actually execute it here
        # num_cols == 0 for statements which don't return data (=> modify it)
        if not self.num_cols:
            v = self.fetchone()
            assert v is None
            self.lastrowid = sqlite3_last_insert_rowid(self.h)

    def close(self):
        s = sqlite3_finalize(self.stmnt)
        check_error(self.h, s)

    def make_row(self):
        res = []
        for i in range(self.num_cols):
            t = sqlite3_column_type(self.stmnt, i)
            #print("type", t)
            if t == SQLITE_INTEGER:
                res.append(sqlite3_column_int(self.stmnt, i))
            elif t == SQLITE_FLOAT:
                res.append(sqlite3_column_double(self.stmnt, i))
            elif t == SQLITE_TEXT:
                res.append(sqlite3_column_text(self.stmnt, i))
            else:
                raise NotImplementedError
        return tuple(res)

    def fetchone(self):
        res = sqlite3_step(self.stmnt)
        #print("step:", res)
        if res == SQLITE_DONE:
            return None
        if res == SQLITE_ROW:
            return self.make_row()
        check_error(self.h, res)


def connect(fname):
    b = bytearray(4)
    sqlite3_open(fname, b)
    h = int.from_bytes(b, sys.byteorder)
    return Connections(h)


def quote(val):
    if isinstance(val, str):
        return "'%s'" % val
    return str(val)
