/* This file is part of the KDE project
   Copyright (C) 2006-2012 Jarosław Staniek <staniek@kde.org>

   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 "mysqldriver_global.h"
#include "mysqlpreparedstatement.h"
#include <kdebug.h>
#include <errmsg.h>

using namespace KexiDB;

// For example prepared MySQL statement code see:
// http://dev.mysql.com/doc/refman/4.1/en/mysql-stmt-execute.html

MySqlPreparedStatement::MySqlPreparedStatement(StatementType type, ConnectionInternal& conn,
        FieldList& fields)
        : KexiDB::PreparedStatement(type, conn, fields)
        , MySqlConnectionInternal(conn.connection)
#ifdef KEXI_USE_MYSQL_STMT
        , m_statement(0)
        , m_mysqlBind(0)
#endif
        , m_resetRequired(false)
{
// KexiDBDrvDbg << "MySqlPreparedStatement: Construction";

    mysql_owned = false;
    mysql = dynamic_cast<KexiDB::MySqlConnectionInternal&>(conn).mysql; //copy
    m_tempStatementString = generateStatementString();

    if (!init())
        done();
}

bool MySqlPreparedStatement::init()
{
    if (m_tempStatementString.isEmpty())
        return false;
#ifdef KEXI_USE_MYSQL_STMT
    m_statement = mysql_stmt_init(mysql);
    if (!m_statement) {
//! @todo err 'out of memory'
        return false;
    }
    res = mysql_stmt_prepare(m_statement,
                             (const char*)m_tempStatementString, m_tempStatementString.length());
    if (0 != res) {
//! @todo use mysql_stmt_error(stmt); to show error
        return false;
    }

    m_realParamCount = mysql_stmt_param_count(m_statement);
    if (m_realParamCount <= 0) {
//! @todo err
        return false;
    }
    m_mysqlBind = new MYSQL_BIND[ m_realParamCount ];
    memset(m_mysqlBind, 0, sizeof(MYSQL_BIND)*m_realParamCount); //safe?
#endif
    return true;
}


MySqlPreparedStatement::~MySqlPreparedStatement()
{
    done();
}

void MySqlPreparedStatement::done()
{
#ifdef KEXI_USE_MYSQL_STMT
    if (m_statement) {
//! @todo handle errors of mysql_stmt_close()?
        mysql_stmt_close(m_statement);
        m_statement = 0;
    }
    delete m_mysqlBind;
    m_mysqlBind = 0;
#endif
}

#ifdef KEXI_USE_MYSQL_STMT
#define BIND_NULL { \
        m_mysqlBind[arg].buffer_type = MYSQL_TYPE_NULL; \
        m_mysqlBind[arg].buffer = 0; \
        m_mysqlBind[arg].buffer_length = 0; \
        m_mysqlBind[arg].is_null = &dummyNull; \
        m_mysqlBind[arg].length = &str_length; }
#endif

bool MySqlPreparedStatement::execute()
{
#ifdef KEXI_USE_MYSQL_STMT
    if (!m_statement || m_realParamCount <= 0)
        return false;
    if (mysql_stmt_errno(m_statement) == CR_SERVER_LOST) {
        //sanity: connection lost: reconnect
//! @todo KexiDB::Connection should be reconnected as well!
        done();
        if (!init()) {
            done();
            return false;
        }
    }

    if (m_resetRequired) {
        mysql_stmt_reset(m_statement);
        res = sqlite3_reset(prepared_st_handle);
        if (SQLITE_OK != res) {
            //! @todo msg?
            return false;
        }
        m_resetRequired = false;
    }

    int arg = 0;
    bool dummyNull = true;
    unsigned long str_length;

    //for INSERT, we're iterating over inserting values
    //for SELECT, we're iterating over WHERE conditions
    Field::List *fieldList = 0;
    if (m_type == SelectStatement)
        fieldList = m_whereFields;
    else if (m_type == InsertStatement)
        fieldList = m_fields->fields();
    else
        assert(0); //impl. error

    Field::ListIterator itFields(fieldList->constBegin());
    for (QList<QVariant>::ConstIterator it(m_args.constBegin());
            itFields != fieldList->constEnd() && arg < m_realParamCount; ++it, ++itFields, ++arg) {
        KexiDB::Field *field = *itFields;
        if (it == m_args.constEnd() || (*it).isNull()) {//no value to bind or the value is null: bind NULL
            BIND_NULL;
            continue;
        }
        if (field->isTextType()) {
//! @todo optimize
            m_stringBuffer[ 1024 ]; ? ? ?
            char *str = qstrncpy(m_stringBuffer, (const char*)(*it).toString().toUtf8(), 1024);
            m_mysqlBind[arg].buffer_type = MYSQL_TYPE_STRING;
            m_mysqlBind[arg].buffer = m_stringBuffer;
            m_mysqlBind[arg].is_null = (my_bool*)0;
            m_mysqlBind[arg].buffer_length = 1024; //?
            m_mysqlBind[arg].length = &str_length;
        } else {
            switch (field->type()) {
            case KexiDB::Field::Byte:
            case KexiDB::Field::ShortInteger:
            case KexiDB::Field::Integer: {
                //! @todo what about unsigned > INT_MAX ?
                bool ok;
                const int value = (*it).toInt(&ok);
                if (ok) {
                    if (field->type() == KexiDB::Field::Byte)
                        m_mysqlBind[arg].buffer_type = MYSQL_TYPE_TINY;
                    else if (field->type() == KexiDB::Field::ShortInteger)
                        m_mysqlBind[arg].buffer_type = MYSQL_TYPE_SHORT;
                    else if (field->type() == KexiDB::Field::Integer)
                        m_mysqlBind[arg].buffer_type = MYSQL_TYPE_LONG;

                    m_mysqlBind[arg].is_null = (my_bool*)0;
                    m_mysqlBind[arg].length = 0;

                    res = sqlite3_bind_int(prepared_st_handle, arg, value);
                    if (SQLITE_OK != res) {
                        //! @todo msg?
                        return false;
                    }
                } else
                    BIND_NULL;
                break;
            }
            case KexiDB::Field::Float:
            case KexiDB::Field::Double:
                res = sqlite3_bind_double(prepared_st_handle, arg, (*it).toDouble());
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            case KexiDB::Field::BigInteger: {
                //! @todo what about unsigned > LLONG_MAX ?
                bool ok;
                qint64 value = (*it).toLongLong(&ok);
                if (ok) {
                    res = sqlite3_bind_int64(prepared_st_handle, arg, value);
                    if (SQLITE_OK != res) {
                        //! @todo msg?
                        return false;
                    }
                } else {
                    res = sqlite3_bind_null(prepared_st_handle, arg);
                    if (SQLITE_OK != res) {
                        //! @todo msg?
                        return false;
                    }
                }
                break;
            }
            case KexiDB::Field::Boolean:
                res = sqlite3_bind_text(prepared_st_handle, arg,
                                        QString::number((*it).toBool() ? 1 : 0).toLatin1(),
                                        1, SQLITE_TRANSIENT /*??*/);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            case KexiDB::Field::Time:
                res = sqlite3_bind_text(prepared_st_handle, arg,
                                        (*it).toTime().toString(Qt::ISODate).toLatin1(),
                                        sizeof("HH:MM:SS"), SQLITE_TRANSIENT /*??*/);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            case KexiDB::Field::Date:
                res = sqlite3_bind_text(prepared_st_handle, arg,
                                        (*it).toDate().toString(Qt::ISODate).toLatin1(),
                                        sizeof("YYYY-MM-DD"), SQLITE_TRANSIENT /*??*/);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            case KexiDB::Field::DateTime:
                res = sqlite3_bind_text(prepared_st_handle, arg,
                                        (*it).toDateTime().toString(Qt::ISODate).toLatin1(),
                                        sizeof("YYYY-MM-DDTHH:MM:SS"), SQLITE_TRANSIENT /*??*/);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            case KexiDB::Field::BLOB: {
                const QByteArray byteArray((*it).toByteArray());
                res = sqlite3_bind_blob(prepared_st_handle, arg,
                                        (const char*)byteArray, byteArray.size(), SQLITE_TRANSIENT /*??*/);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
                break;
            }
            default:
                KexiDBWarn << "PreparedStatement::execute(): unsupported field type: "
                << field->type() << " - NULL value bound to column #" << arg;
                res = sqlite3_bind_null(prepared_st_handle, arg);
                if (SQLITE_OK != res) {
                    //! @todo msg?
                    return false;
                }
            } //switch
        }//else
    }//for

    //real execution
    res = sqlite3_step(prepared_st_handle);
    m_resetRequired = true;
    if (m_type == InsertStatement && res == SQLITE_DONE) {
        return true;
    }
    if (m_type == SelectStatement) {
        //fetch result

//! @todo
    }
#else
    m_resetRequired = true;
    const int missingValues = m_fields->fieldCount() - m_args.count();
    if (missingValues > 0) {
//! @todo can be more efficient
        for (int i = 0; i < missingValues; i++) {
            m_args.append(QVariant());
        }
    }
    if (connection->insertRecord(*m_fields, m_args)) {
        return true;
    }

#endif //KEXI_USE_MYSQL_STMT
    return false;
}
