/* This file is part of the KDE project
   Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "pqxxcursor.h"
#include "pqxxconnection.h"
#include "pqxxconnection_p.h"

#include <db/error.h>
#include <db/global.h>

#include <klocale.h>
#include <kdebug.h>

using namespace KexiDB;

unsigned int pqxxSqlCursor_trans_num = 0; //!< debug helper

static QByteArray pgsqlByteaToByteArray(const pqxx::result::field& r)
{
    return KexiDB::pgsqlByteaToByteArray(r.c_str(), r.size());
}

//==================================================================================
//Constructor based on query statement
pqxxSqlCursor::pqxxSqlCursor(KexiDB::Connection* conn, const QString& statement, uint options):
        Cursor(conn, statement, options)
{
// KexiDBDrvDbg << "Constructor for query statement";
    my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql;
    m_options = Buffered;
    m_res = 0;
    m_implicityStarted = false;
}

//==================================================================================
//Constructor base on query object
pqxxSqlCursor::pqxxSqlCursor(Connection* conn, QuerySchema& query, uint options)
        : Cursor(conn, query, options)
{
// KexiDBDrvDbg << "Constructor for query schema";
    my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql;
    m_options = Buffered;
    m_res = 0;
    m_implicityStarted = false;
}

//==================================================================================
//Destructor
pqxxSqlCursor::~pqxxSqlCursor()
{
    close();
}

//==================================================================================
//Create a cursor result set
bool pqxxSqlCursor::drv_open()
{
// KexiDBDrvDbg << m_sql;
    if (!my_conn->is_open()) {
//! @todo this check should be moved to Connection! when drv_prepareQuery() arrive
        //should never happen, but who knows
        setError(ERR_NO_CONNECTION, kexidb_i18n("No connection for cursor open operation specified."));
        return false;
    }

    //Set up a transaction
    try {
        if (!((pqxxSqlConnection*)connection())->m_trans) {
            (void)new pqxxTransactionData((pqxxSqlConnection*)connection(), true);
            m_implicityStarted = true;
        }

        m_res = new pqxx::result(((pqxxSqlConnection*)connection())->m_trans->data->exec(std::string(m_sql.toUtf8())));
        ((pqxxSqlConnection*)connection())->drv_commitTransaction(((pqxxSqlConnection*)connection())->m_trans);
//  KexiDBDrvDbg << "trans. committed: " << cur_name;

        //We should now be placed before the first row, if any
        m_fieldsToStoreInRow = m_res->columns();
        m_fieldCount = m_fieldsToStoreInRow - (m_containsROWIDInfo ? 1 : 0);
        m_afterLast = false;
        m_records_in_buf = m_res->size();
        m_buffering_completed = true;
        return true;
    } catch (const std::exception &e) {
        setError(ERR_DB_SPECIFIC, QString::fromUtf8(e.what()));
        KexiDBDrvWarn << "exception -" << QString::fromUtf8(e.what());
    } catch (...) {
        setError();
    }
    if (m_implicityStarted) {
        delete((pqxxSqlConnection*)connection())->m_trans;
        m_implicityStarted = false;
    }
// KexiDBDrvDbg << "trans. rolled back! - " << cur_name;
    return false;
}

//==================================================================================
//Delete objects
bool pqxxSqlCursor::drv_close()
{
    delete m_res;
    m_res = 0;
    return true;
}

//==================================================================================
//Gets the next record...does not need to do much, just return fetchend if at end of result set
void pqxxSqlCursor::drv_getNextRecord()
{
// KexiDBDrvDbg << "size is" <<m_res->size() << " Current Position is" << (long)at();
    if (at() < qint64(m_res->size()) && at() >= 0) {
        m_result = FetchOK;
    } else if (at() >= qint64(m_res->size())) {
        m_result = FetchEnd;
    } else {
        // control will reach here only when at() < 0 ( which is usually -1 )
        // -1 is same as "1 beyond the End"
        m_result = FetchEnd;
    }
}

//==================================================================================
//Check the current position is within boundaries
void pqxxSqlCursor::drv_getPrevRecord()
{
// KexiDBDrvDbg;
    if (at() < qint64(m_res->size()) && at() >= 0) {
        m_result = FetchOK;
    } else if (at() >= qint64(m_res->size())) {
        m_result = FetchEnd;
    } else {
        m_result = FetchError;
    }
}

//==================================================================================
//Return the value for a given column for the current record
QVariant pqxxSqlCursor::value(uint pos)
{
    if (pos < m_fieldCount)
        return pValue(pos);
    else
        return QVariant();
}

//==================================================================================
//Return the value for a given column for the current record - Private const version
QVariant pqxxSqlCursor::pValue(uint pos)const
{
    if (m_res->size() <= 0) {
        KexiDBDrvWarn << "pqxxSqlCursor::value - ERROR: result size not greater than 0";
        return QVariant();
    }

    if (pos >= m_fieldsToStoreInRow) {
//  KexiDBDrvWarn << "ERROR: requested position is greater than the number of fields";
        return QVariant();
    }

// KexiDBDrvDbg << pos;
    //from most to least frequently used types:
    const Field *f = (m_visibleFieldsExpanded && pos < uint(m_visibleFieldsExpanded->count()))
                     ? m_visibleFieldsExpanded->at(pos)->field : 0;
    if (f) { //We probably have a schema type query so can use kexi to determin the row type
        const Field::Type type = f->type(); // cache: evaluating type of expressions can be expensive
        if (type == Field::Null || type == Field::InvalidType) {
            return QVariant();
        } else if (Field::isIntegerType(type) || (/*ROWID*/!f && m_containsROWIDInfo && pos == m_fieldCount)) {
            return (*m_res)[at()][pos].as(int());
        } else if (Field::isTextType(type)) {
            return QString::fromUtf8((*m_res)[at()][pos].c_str()); //utf8?
        } else if (Field::isFPNumericType(type)) {
            return (*m_res)[at()][pos].as(double());
        } else if (type == Field::Boolean) {
            return QString((*m_res)[at()][pos].c_str()).toLower() == "t" ? QVariant(true) : QVariant(false);
        } else if (Field::typeGroup(type) == Field::BLOBGroup) {
//   pqxx::result::field r = (*m_res)[at()][pos];
//   kDebug() << r.name() << ", " << r.c_str() << ", " << r.type() << ", " << r.size();
            return ::pgsqlByteaToByteArray((*m_res)[at()][pos]);
        } else {
            return pgsqlCStrToVariant((*m_res)[at()][pos]);
        }
    } else { // We probably have a raw type query so use pqxx to determin the column type
        return pgsqlCStrToVariant((*m_res)[at()][pos]);
    }

    return QString::fromUtf8((*m_res)[at()][pos].c_str(), (*m_res)[at()][pos].size()); //utf8?
}

//==================================================================================
//Return the current record as a char**
//who'd have thought we'd be using char** in this day and age :o)
const char** pqxxSqlCursor::rowData() const
{
// KexiDBDrvDbg;
    const char** row = (const char**)malloc(m_res->columns() + 1);
    row[m_res->columns()] = NULL;
    if (at() >= 0 && at() < qint64(m_res->size())) {
        for (int i = 0; i < (int)m_res->columns(); i++) {
            row[i] = (char*)malloc(strlen((*m_res)[at()][i].c_str()) + 1);
            strcpy((char*)(*m_res)[at()][i].c_str(), row[i]);
//   KexiDBDrvDbg << row[i];
        }
    } else {
        KexiDBDrvWarn << "m_at is invalid";
    }
    return row;
}

//==================================================================================
//Store the current record in [data]
bool pqxxSqlCursor::drv_storeCurrentRow(RecordData &data) const
{
// KexiDBDrvDbg << "POSITION IS" << (long)m_at;
    if (m_res->size() <= 0)
        return false;

    for (uint i = 0; i < m_fieldsToStoreInRow; i++)
        data[i] = pValue(i);
    return true;
}

//==================================================================================
//
void pqxxSqlCursor::drv_clearServerResult()
{
//! @todo pqxxSqlCursor: stuff with server results
}

//==================================================================================
//Add the current record to the internal buffer
//Implementation required but no need in this driver
//Result set is a buffer so do not need another
void pqxxSqlCursor::drv_appendCurrentRecordToBuffer()
{
}

//==================================================================================
//Move internal pointer to internal buffer +1
//Implementation required but no need in this driver
void pqxxSqlCursor::drv_bufferMovePointerNext()
{
}

//==================================================================================
//Move internal pointer to internal buffer -1
//Implementation required but no need in this driver
void pqxxSqlCursor::drv_bufferMovePointerPrev()
{
}

//==================================================================================
//Move internal pointer to internal buffer to N
//Implementation required but no need in this driver
void pqxxSqlCursor::drv_bufferMovePointerTo(qint64 to)
{
    Q_UNUSED(to);
}
