#ifdef DBA_ENABLE_MYSQL
//////////////////////////////////////////////////////////////////////
// Copyright (c) 2010, Oliver 'kfs1' Smith <oliver@kfs.org>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// - Neither the name of KingFisher Software nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////
//

#include "dbaConn.h"                        // Main include file
#include <errmsg.h>                     // Include the MySQL error codes.
#include <sstream>                      // For stringstream.

namespace DBA
{

//////////////////////////////////////////////////////////////////////
// Constructor for a MySQLConnection - not a lot here :)

MySQLConnection::MySQLConnection()
    : Connection()
    , m_conn(NULL)
    , m_result(NULL)
    , m_columns(NULL)
{
}

//////////////////////////////////////////////////////////////////////
// Destructor - ensure cleanup.

MySQLConnection::~MySQLConnection()
{
    m_columns = NULL ;

    if ( m_currentResult != NULL )
        ReleaseResult() ;

    if ( m_conn != NULL )
        Disconnect() ;
}

//////////////////////////////////////////////////////////////////////
// Connect to a MySQL database.

void
MySQLConnection::Connect(const Credentials& credentials, unsigned int flags, bool autoReconnect /* = false */)
{
    // Initialize MySQL
    MYSQL* mysql = mysql_init(NULL) ;
    if ( mysql == NULL )
        throw std::runtime_error("mysql_init failed") ;     ///Terminal

    // Attempt to connect.
    m_conn = mysql_real_connect(mysql
                                , credentials.host, credentials.username, credentials.password, credentials.database, credentials.port
                                , NULL
                                , flags
                                ) ;

    if ( m_conn == NULL )
    {
        std::stringstream error ;
        error << "Unable to connect to database "
                    << credentials.username<<"@"<<credentials.host<<":"<<credentials.port<<"/"<<credentials.database
                    << ". Error #" << mysql_errno(mysql) << ": " << mysql_error(mysql) ;

        mysql_close(mysql) ;

        throw std::logic_error(error.rdbuf()->str()) ;
    }

    // Set the auto-reconnect flag on versions of MySQL that have it.
# if MYSQL_VERSION_ID >= 50003
    my_bool reconnect = (autoReconnect ? 1 : 0) ;
    mysql_options(m_conn, MYSQL_OPT_RECONNECT, &reconnect) ;
# endif

    // Force utf8 as the connection characterset.
    if ( mysql_set_character_set(m_conn, "utf8") != 0 )
    {
        std::stringstream error ;
        error << "Unable to set MySQL connection character set to UTF8. Error #" << mysql_errno(m_conn) << ": " << mysql_error(m_conn) ;

        mysql_close(m_conn) ;
        m_conn = NULL ;

        throw std::logic_error(error.rdbuf()->str()) ;
    }
}

//////////////////////////////////////////////////////////////////////
// Disconnect from database

void
MySQLConnection::Disconnect()
{
    if ( m_currentResult != NULL )
        m_currentResult->Release() ;

    if ( m_conn == NULL )
        return ;

    mysql_close(m_conn) ;
    m_conn = NULL ;
}

//////////////////////////////////////////////////////////////////////
// Clean up result sets

void
MySQLConnection::_releaseResultSet()
{
    // Clean up the MySQL result
    if ( m_result )
    {
        mysql_free_result(m_result) ;
        m_result = NULL ;
    }

    // Drain any remaining result sets (e.g. from a CALL query)
    if ( m_conn != NULL )
    {
        while ( mysql_next_result(m_conn) == 0 )
        {
            m_result = mysql_store_result(m_conn) ;
            if ( m_result != NULL )
            {
                mysql_free_result(m_result) ;
                m_result = NULL ;
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////
// Escape a string for any illegal SQL characters.

void
MySQLConnection::EscapeString(const char* src, char* dest, size_t destSize)
{
    size_t srcLen = strlen(src) ;
    const size_t destLen = destSize - 1 ;
    if ( srcLen > destLen / 2 )
        srcLen = destLen / 2 ;

    if ( m_conn )
        mysql_real_escape_string(m_conn, dest, src, srcLen) ;
    else
        mysql_escape_string(dest, src, srcLen) ;
}

//////////////////////////////////////////////////////////////////////
// Execute SQL statement

void
MySQLConnection::_execute(size_t stlen, const char* statement)
{
    unsigned int queryResult = mysql_real_query(m_conn, statement, stlen) ;

    // Unless we lost the connection, we did actual work.
    if ( queryResult != CR_SERVER_GONE_ERROR && queryResult != CR_SERVER_LOST )
        m_executed = true ;

    if ( queryResult != 0 )
    {
        // Failed
        m_error = mysql_errno(m_conn) ;

        ThrowError("Statement Error: ") ;
    }
}

//////////////////////////////////////////////////////////////////////
// Retrieve results from execution

void
MySQLConnection::_processResultSet()
{
    m_result = mysql_store_result(m_conn) ;

    if ( m_result == NULL )
    {
        // Should there have been a result?
        if (mysql_field_count(m_conn) != 0 )
        {
            m_error = mysql_errno(m_conn) ;
            if ( m_error != 0 )
            {
                ThrowError("MySQL error retrieving results from query: ") ;
            }
            else
                throw std::logic_error("Failed to retrieve result set") ;
        }
        m_rows = m_cols = 0 ;
    }
    else
    {
        m_rows = (size_t)mysql_num_rows(m_result) ;
        m_cols = (size_t)mysql_num_fields(m_result) ;
    }

    m_affectedRows = (size_t)mysql_affected_rows(m_conn) ;
    m_lastInsertID = (size_t)mysql_insert_id(m_conn) ;
}

//////////////////////////////////////////////////////////////////////
// Fetch next row, or return false.

bool
MySQLConnection::FetchRow()
{
    m_currentColumn = 0 ;

    // Conditions under which we shouldn't be getting called
    if ( !m_executed )
        throw std::logic_error("No query executed to fetch rows from") ;

    if ( m_error )
        throw std::logic_error("Can't fetch rows, query failed") ;

    if ( m_result == NULL )
        throw std::logic_error("No result set to query") ;

    // Have we reached the end of the data, or is the result
    // empty columns?
    if ( HasRows() == false || m_cols == 0 )
    {
        return false ;
    }

    m_columns = mysql_fetch_row(m_result) ;
    if ( m_columns == NULL )
    {
        m_error = mysql_errno(m_conn) ;
        ThrowError("Error retrieving row: ") ;
    }

    ++m_currentRow ;

    return true ;
}

//////////////////////////////////////////////////////////////////////
// Some queries (e.g. CALL) can return multiple result sets, it's
// also possible to generate multiple result sets by issuing multiple
// statements in one command (e.g. "SELECT 1; SELECT 2").

bool
MySQLConnection::NextResultSet()
{
    ReleaseResult() ;

    m_error = mysql_next_result(m_conn) ;
    if ( m_error > 0 )
        ThrowError("Error retrieving next result set") ;
    if ( m_error < 0 )
        return false ;

    m_executed = true ;

    _processResultSet() ;

    return true ;
}



} ; // namespace DBA

#endif // DBA_ENABLE_MYSQL