//////////////////////////////////////////////////////////////////////
// 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"
#include <stdarg.h>

namespace DBA
{

//////////////////////////////////////////////////////////////////////
// Constructor for the Connection baseclase.

Connection::Connection()
    : m_executed(false)
    , m_error(0)
    , m_affectedRows(0)
    , m_lastInsertID(0)
    , m_rows(0)
    , m_currentRow(0)
    , m_cols(0)
    , m_currentColumn(0)
    , m_currentResult(NULL)
{
}

//////////////////////////////////////////////////////////////////////
// Destructor for the Connection baseclase. Make sure any current
// query is destructed.

Connection::~Connection()
{
    if ( m_currentResult != NULL )
        m_currentResult->Release() ;
}

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

void
Connection::Execute(size_t stlen, const char* statement)
{
    // Make sure any prior result goes away.
    ReleaseResult() ;

    m_executed = false ;
    m_error = 0 ;
    m_affectedRows = 0 ;
    m_lastInsertID = 0 ;
    m_rows = 0 ;
    m_currentRow = 0 ;
    m_cols = 0 ;
    m_currentColumn = 0 ;

    CheckConnected() ;

    if ( statement == NULL )
        throw std::invalid_argument("NULL query statement") ;

    if ( stlen == 0 )
        throw std::invalid_argument("0 length statement") ;

    // Find the first non-space characters in the query
    while ( stlen > 0 && *statement && isspace(*statement) )
    {
        ++statement ;
        --stlen ;
    }

    // Validate the statement length argument.
    if ( (stlen == 0 && statement[0] != 0)
#ifdef DEBUG
        || strlen(statement) != stlen
#endif
        )
    {
        throw std::invalid_argument("stlen argument does not match statement length") ;
    }

    if ( statement[0] == 0 )
        throw std::invalid_argument("Empty statement") ;

    _execute(stlen, statement) ;

    _processResultSet() ;
}

//////////////////////////////////////////////////////////////////////
// Clean up the last set of results.

void
Connection::ReleaseResult()
{
    _releaseResultSet() ;

    m_executed = false ;
    m_error = 0 ;
    m_rows = m_affectedRows = m_currentRow = 0 ;
    m_cols = m_currentColumn = 0 ;
    m_lastInsertID = 0 ;
}


//////////////////////////////////////////////////////////////////////
// Constructor for the ResultSet interface class. Attaches us to the
// parent connection and ensures only one ResultSet is in use per
// connection at a time.

ResultSet::ResultSet(Connection& conn, size_t stlen, const char* statement)
    : m_conn(NULL)
{
    if ( conn.m_currentResult != NULL )
        throw std::logic_error("Cannot have multiple concurrent result sets active per connection.") ;

    m_conn = &conn ;
    m_conn->ReleaseResult() ;

    m_conn->m_currentResult = this ;

    m_conn->Execute(stlen, statement) ;
}

//////////////////////////////////////////////////////////////////////
// Varadic, snprintf style constructor. maxStatementLen is the maximum
// length of the resulting statement you wish to allow. The inclusion of
// this parameter helps (a) efficiency, (b) disambiguate the resulting
// function fingerprint from that of the future ResultSet(conn, statement) ;

ResultSet::ResultSet(Connection& conn, const char* format, size_t maxStatementLen, ...)
{
    // Input validation.
    if ( format == NULL )
        throw std::invalid_argument("format specifier must not be NULL") ;
    if ( maxStatementLen == 0 )
        throw std::invalid_argument("maxStatementLen must be non-zero") ;
#ifndef NDEBUG
    size_t formatLen = strlen(format) ;
    if ( maxStatementLen < formatLen )
        throw std::invalid_argument("maxStatementLen must be >= length of the format mask") ;
#endif

    // G++ allows dynamic array sizes on the stack.
#ifdef __GNUC__
    char statement[maxStatementLen + 1] ;
#else
    char* statement = new char[maxStatementLen + 1] ;
#endif

    va_list args ;
    va_start(args, maxStatementLen) ;
    int stLen = vsnprintf(statement, maxStatementLen, format, args) ;
    va_end(args) ;

    // Ensure we add the trailing 0.
    statement[stLen] = 0 ;

    ResultSet::ResultSet(conn, stLen, statement) ;

#ifndef __GNUC__
    delete [] statement ;
#endif
}

//////////////////////////////////////////////////////////////////////
// Destructor for the ResultSet interface class: Ensures we detach
// from the parent connection and release any resources used by the
// query we are associated with.

ResultSet::~ResultSet()
{
    Release() ;
    if ( m_conn )
    {
        if ( m_conn->m_currentResult == this )
        {
            m_conn->ReleaseResult() ;
            m_conn->m_currentResult = NULL ;
        }
    }
}

//////////////////////////////////////////////////////////////////////
// Re-use a result set for a second query.

void
ResultSet::Do(size_t stlen, const char* statement)
{
    if ( m_conn == NULL )
        throw std::logic_error("Result set is not associated with any connection") ;
    if ( m_conn->m_currentResult != this )
        throw std::logic_error("Database connection is no-longer associated with this result set") ;

    m_conn->Execute(stlen, statement) ;
}

void
ResultSet::Do(const char* format, size_t maxStatementLen, ...)
{
    // G++ allows dynamic array sizes on the stack.
#ifdef __GNUC__
    char statement[maxStatementLen + 1] ;
#else
    char* statement = new char[maxStatementLen + 1] ;
#endif

    va_list args ;
    va_start(args, maxStatementLen) ;
    int stLen = vsnprintf(statement, maxStatementLen, format, args) ;
    va_end(args) ;

    // Ensure we add the trailing 0.
    statement[stLen] = 0 ;

    m_conn->Execute(stLen, statement) ;

#ifndef __GNUC__
    delete [] statement ;
#endif
}

//////////////////////////////////////////////////////////////////////
// Release resources used by a result set and detach it from the
// connection.

void
ResultSet::Release()
{
    if ( m_conn )
    {
        if ( m_conn->m_currentResult == this )
        {
            m_conn->ReleaseResult() ;
            m_conn->m_currentResult = NULL ;
        }
    }
}

}