/****************************************************************************
**
** Implementation of SQLite driver classes.
**
** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
**
** This file is part of the sql module of the Qt GUI Toolkit.
** EDITIONS: FREE, ENTERPRISE
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include "qsql_sqlite3.h"

#include <qdatetime.h>
#include <qvaluevector.h>
#include <qregexp.h>
#include <qfile.h>
#include <sqlite3.h>

#if (QT_VERSION-0 < 0x030200)
#  include <qvector.h>
#  if !defined Q_WS_WIN32
#    include <unistd.h>
#  endif
#else
#  include <qptrvector.h>
#  if !defined Q_WS_WIN32
#    include <unistd.h>
#  endif
#endif

typedef struct sqlite3_stmt sqlite3_stmt;

#define QSQLITE3_DRIVER_NAME "QSQLITE3"

static QVariant::Type qSqliteType(int tp)
{
    switch (tp) {
    case SQLITE_INTEGER:
        return QVariant::Int;
    case SQLITE_FLOAT:
        return QVariant::Double;
    case SQLITE_BLOB:
        return QVariant::ByteArray;
    case SQLITE_TEXT:
    default:
        return QVariant::String;
    }
}

static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::Type type,
                            int errorCode = -1)
{
    return QSqlError(descr,
                     QString::fromUtf8(sqlite3_errmsg(access)),
                     type, errorCode);
}

class QSQLite3DriverPrivate
{
public:
    QSQLite3DriverPrivate();
    sqlite3 *access;
    bool utf8;
};

QSQLite3DriverPrivate::QSQLite3DriverPrivate() : access(0)
{
    utf8 = true;
}

class QSQLite3ResultPrivate
{
public:
    QSQLite3ResultPrivate(QSQLite3Result *res);
    void cleanup();
    bool fetchNext(QtSqlCachedResult::RowCache *row);
    bool isSelect();
    // initializes the recordInfo and the cache
    void initColumns(QtSqlCachedResult::RowCache **row = 0);
    void finalize();

    QSQLite3Result* q;
    sqlite3 *access;

    sqlite3_stmt *stmt;

    uint skippedStatus: 1; // the status of the fetchNext() that's skipped
    QtSqlCachedResult::RowCache *skipRow;

    uint utf8: 1;
    QSqlRecord rInf;
};

static const uint initial_cache_size = 128;

QSQLite3ResultPrivate::QSQLite3ResultPrivate(QSQLite3Result* res) : q(res), access(0),
    stmt(0), skippedStatus(false), skipRow(0), utf8(false)
{
}

void QSQLite3ResultPrivate::cleanup()
{
    finalize();
    rInf.clear();
    skippedStatus = false;
    delete skipRow;
    skipRow = 0;
    q->setAt(QSql::BeforeFirst);
    q->setActive(false);
    q->cleanup();
}

void QSQLite3ResultPrivate::finalize()
{
    if (!stmt)
        return;

    sqlite3_finalize(stmt);
    stmt = 0;
}

// called on first fetch
void QSQLite3ResultPrivate::initColumns(QtSqlCachedResult::RowCache** row)
{
    rInf.clear();

    int nCols = sqlite3_column_count(stmt);
    if (nCols <= 0)
        return;

    q->init(nCols);

    for (int i = 0; i < nCols; ++i) {
        QString colName = QString::fromUtf8(sqlite3_column_name(stmt, i));

        int dotIdx = colName.findRev('.');
        rInf.append(QSqlField(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1),
                qSqliteType(sqlite3_column_type(stmt, i))));
    }

    // skip the first fetch
    if (row && !*row) {
        *row = new QtSqlCachedResult::RowCache(nCols);
        skipRow = *row;
    }
}

bool QSQLite3ResultPrivate::fetchNext(QtSqlCachedResult::RowCache* row)
{
    int res;
    unsigned int i;

    if (skipRow) {
	// already fetched
	if (row)
	    *row = *skipRow;
	delete skipRow;
	skipRow = 0;
	return skippedStatus;
    }

    if (!stmt)
        return false;

    // keep trying while busy, wish I could implement this better.
    while ((res = sqlite3_step(stmt)) == SQLITE_BUSY) {
        // sleep instead requesting result again immidiately.
#if defined Q_OS_WIN
        Sleep(1000);
#else
        sleep(1);
#endif
    }

    switch(res) {
    case SQLITE_ROW:
        // check to see if should fill out columns
        if (rInf.isEmpty())
            // must be first call.
            initColumns(&row);
        if (!row)
            return true;
        for (i = 0; i < rInf.count(); ++i)
            // todo - handle other types
            (*row)[i] = QString::fromUtf8((char *)(sqlite3_column_text(stmt, i)));
 //           values[i + idx] = utf8 ? QString::fromUtf8(fvals[i]) : QString::fromAscii(fvals[i]);
        return true;
    case SQLITE_DONE:
        if (rInf.isEmpty())
            // must be first call.
            initColumns(&row);
        q->setAt(QSql::AfterLast);
        return false;
    case SQLITE_ERROR:
    case SQLITE_MISUSE:
    default:
        // something wrong, don't get col info, but still return false
        q->setLastError(qMakeError(access, "Unable to fetch row", QSqlError::Connection, res));
        finalize();
        q->setAt(QSql::AfterLast);
        return false;
    }
    return false;
}

QSQLite3Result::QSQLite3Result(const QSQLite3Driver* db)
    : QtSqlCachedResult(db)
{
    d = new QSQLite3ResultPrivate(this);
    d->access = db->d->access;
}

QSQLite3Result::~QSQLite3Result()
{
    d->cleanup();
    delete d;
}

/*
   Execute \a query.
*/
bool QSQLite3Result::reset (const QString &query)
{
    // this is where we build a query.
    if (!driver() || !driver()->isOpen() || driver()->isOpenError())
        return false;

    d->cleanup();

    setSelect(false);

    int res = sqlite3_prepare(d->access, query.utf8().data(), (query.length() + 1) * sizeof(QChar),
                                &d->stmt, 0);

    if (res != SQLITE_OK) {
        setLastError(qMakeError(d->access, "Unable to execute statement", QSqlError::Statement, res));
        d->finalize();
        return false;
    }

    d->skippedStatus = d->fetchNext(0);

    setSelect(!d->rInf.isEmpty());
    setActive(true);
    return true;
}

bool QSQLite3Result::gotoNext(QtSqlCachedResult::RowCache* row)
{
    return d->fetchNext(row);
}

int QSQLite3Result::size()
{
    return -1;
}

int QSQLite3Result::numRowsAffected()
{
    return sqlite3_changes(d->access);
}

/////////////////////////////////////////////////////////

QSQLite3Driver::QSQLite3Driver(QObject * parent, const char *name)
    : QSqlDriver(parent, name)
{
    d = new QSQLite3DriverPrivate();
}

QSQLite3Driver::QSQLite3Driver(sqlite3 *connection, QObject *parent, const char *name)
    : QSqlDriver(parent, name)
{
    d = new QSQLite3DriverPrivate();
    d->access = connection;
    setOpen(true);
    setOpenError(false);
}


QSQLite3Driver::~QSQLite3Driver()
{
    delete d;
}

bool QSQLite3Driver::hasFeature(DriverFeature f) const
{
    switch (f) {
    case Transactions:
    case Unicode:
    case BLOB:
        return true;
    default:
        break;
    }
    return false;
}

/*
   SQLite dbs have no user name, passwords, hosts or ports.
   just file names.
*/
bool QSQLite3Driver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &)
{
    if (isOpen())
        close();

    if (db.isEmpty())
        return false;

    if (sqlite3_open(QFile::encodeName(db), &d->access) == SQLITE_OK) {
        setOpen(true);
        setOpenError(false);
        return true;
    } else {
        setLastError(qMakeError(d->access, "Error opening database",
                     QSqlError::Connection));
        setOpenError(true);
        return false;
    }
}

void QSQLite3Driver::close()
{
    if (isOpen()) {
        if (sqlite3_close(d->access) != SQLITE_OK)
            setLastError(qMakeError(d->access, "Error closing database",
                                    QSqlError::Connection));
        d->access = 0;
        setOpen(false);
        setOpenError(false);
    }
}

bool QSQLite3Driver::ping()
{
    if ( !isOpen() ) {
        return FALSE;
    }

    // FIXME
    // Implement ping if available
    return TRUE;
}

QSqlQuery QSQLite3Driver::createQuery() const
{
    return QSqlQuery(new QSQLite3Result(this));
}

bool QSQLite3Driver::beginTransaction()
{
    if (!isOpen() || isOpenError())
        return false;

    QSqlQuery q(createQuery());
    if (!q.exec("BEGIN")) {
        setLastError(QSqlError("Unable to begin transaction",
                               q.lastError().databaseText(), QSqlError::Transaction));
        return false;
    }

    return true;
}

bool QSQLite3Driver::commitTransaction()
{
    if (!isOpen() || isOpenError())
        return false;

    QSqlQuery q(createQuery());
    if (!q.exec("COMMIT")) {
        setLastError(QSqlError("Unable to begin transaction",
                               q.lastError().databaseText(), QSqlError::Transaction));
        return false;
    }

    return true;
}

bool QSQLite3Driver::rollbackTransaction()
{
    if (!isOpen() || isOpenError())
        return false;

    QSqlQuery q(createQuery());
    if (!q.exec("ROLLBACK")) {
        setLastError(QSqlError("Unable to begin transaction",
                               q.lastError().databaseText(), QSqlError::Transaction));
        return false;
    }

    return true;
}

QStringList QSQLite3Driver::tables(const QString &typeName) const
{
    QStringList res;
    if (!isOpen())
        return res;
    int type = typeName.toInt();

    QSqlQuery q = createQuery();
    q.setForwardOnly(TRUE);
#if (QT_VERSION-0 >= 0x030200)
    if ((type & (int)QSql::Tables) && (type & (int)QSql::Views))
        q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
    else if (typeName.isEmpty() || (type & (int)QSql::Tables))
        q.exec("SELECT name FROM sqlite_master WHERE type='table'");
    else if (type & (int)QSql::Views)
        q.exec("SELECT name FROM sqlite_master WHERE type='view'");
#else
        q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
#endif


    if (q.isActive()) {
        while(q.next())
            res.append(q.value(0).toString());
    }

#if (QT_VERSION-0 >= 0x030200)
    if (type & (int)QSql::SystemTables) {
        // there are no internal tables beside this one:
        res.append("sqlite_master");
    }
#endif

    return res;
}

QSqlIndex QSQLite3Driver::primaryIndex(const QString &tblname) const
{
    QSqlRecordInfo rec(recordInfo(tblname)); // expensive :(

    if (!isOpen())
        return QSqlIndex();

    QSqlQuery q = createQuery();
    q.setForwardOnly(TRUE);
    // finrst find a UNIQUE INDEX
    q.exec("PRAGMA index_list('" + tblname + "');");
    QString indexname;
    while(q.next()) {
	if (q.value(2).toInt()==1) {
	    indexname = q.value(1).toString();
	    break;
	}
    }
    if (indexname.isEmpty())
	return QSqlIndex();

    q.exec("PRAGMA index_info('" + indexname + "');");

    QSqlIndex index(indexname);
    while(q.next()) {
	QString name = q.value(2).toString();
	QSqlVariant::Type type = QSqlVariant::Invalid;
	if (rec.contains(name))
	    type = rec.find(name).type();
	index.append(QSqlField(name, type));
    }
    return index;
}

QSqlRecordInfo QSQLite3Driver::recordInfo(const QString &tbl) const
{
    if (!isOpen())
        return QSqlRecordInfo();

    QSqlQuery q = createQuery();
    q.setForwardOnly(TRUE);
    q.exec("SELECT * FROM " + tbl + " LIMIT 1");
    return recordInfo(q);
}

QSqlRecord QSQLite3Driver::record(const QString &tblname) const
{
    if (!isOpen())
        return QSqlRecord();

    return recordInfo(tblname).toRecord();
}

QSqlRecord QSQLite3Driver::record(const QSqlQuery& query) const
{
    if (query.isActive() && query.driver() == this) {
        QSQLite3Result* result = (QSQLite3Result*)query.result();
        return result->d->rInf;
    }
    return QSqlRecord();
}

QSqlRecordInfo QSQLite3Driver::recordInfo(const QSqlQuery& query) const
{
    if (query.isActive() && query.driver() == this) {
        QSQLite3Result* result = (QSQLite3Result*)query.result();
        return QSqlRecordInfo(result->d->rInf);
    }
    return QSqlRecordInfo();
}
