//////////////////////////////////////////////////////////////////////
// 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_nextRow(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::runtime_error("stlen argument does not match statement length") ;
	}

	if ( statement[0] == 0 )
		throw std::runtime_error("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::runtime_error("Result set is not associated with any connection") ;
	if ( m_conn->m_currentResult != this )
		throw std::runtime_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 ;
		}
	}
}

} ;
