//////////////////////////////////////////////////////////////////////
// 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 <iostream>

#include "dbaConn.h"

#include <sstream>

// Controls verbose output.
static bool g_verbose = false ;

using namespace std;

#define ATTEMPT( _statement )	\
	progress << " . " << __LINE__ << ":" << #_statement << ": " ; \
	_statement ; \
	progress << "OK." << endl

#define ANNOTATE( _statement ) \
	if ( g_verbose ) progress << "   # " << _statement << "." << endl

template<typename DBAType>
bool
testDatabase(const char* name, const DBA::Credentials& creds)
{
	stringstream progress ;

	try
	{
		cout << "- Testing database type '" << name << "':" ;

		// Create an instance of the database connector.
		DBAType conn ;

		// Attempt to connect.
		ATTEMPT( conn.Connect(creds) ) ;

		ANNOTATE( "Connected to database '" << creds.database << "'" ) ;

		// Attempt a simple query.
		const char statement[] = "SELECT 40 + 2" ;
		ATTEMPT( DBA::ResultSet rs(conn, sizeof(statement) - 1, statement) ) ;

		// We know the query should have returned a row.
		ATTEMPT( rs.HasRows() ) ;

		// Can we retrieve it?
		ATTEMPT( rs.FetchRow() ) ;

		const char* indexedValue = NULL ;
		ATTEMPT( indexedValue = rs[0] ) ;

		ANNOTATE( "rs[0] gave '" << indexedValue << "'" ) ;

		// Make sure what we got back looks sensible.
		ATTEMPT( indexedValue != NULL && strcmp(indexedValue, "42") == 0 ) ;

		// Verify that an illegal array index throws an exception.
		progress << " . logic_error throw on illegal operator[] index: " ;
		try
		{
			indexedValue = rs[999] ;
			throw std::runtime_error("No exception.") ;
		}
		catch ( std::logic_error& e )
		{
			progress << "OK." << endl ;
		}

		// Now try with operator >>.
		const char* cursoredValue = NULL ;
		ATTEMPT( rs >> cursoredValue ) ;

		ANNOTATE( "rs >> cursoredValue gave '" << cursoredValue << "'" ) ;

		// And does that also look good?
		ATTEMPT( cursoredValue != NULL && strcmp(cursoredValue, "42") == 0 ) ;

		// Ensure that a further use of >> throws an exception.
		progress << " . logic_error throw at end of row: " ;
		try
		{
			rs >> cursoredValue ;
			throw std::runtime_error("No exception.") ;
		}
		catch ( std::logic_error& e )
		{
			progress << "OK." ;
		}

		// Ensure that FetchRow() returns false attempting to fetch a second row.
		ATTEMPT( !rs.FetchRow() ) ;

		// Test 'Do' and then use the result to test multiple columns.
		ATTEMPT( rs.Do("SELECT '%s', '%s', 'A string of some length', %u + %u, %f * %f, NULL", 200, "Hello", "world", 40, 2, 3.1, 4.2) ) ;

		// Retrieve the results.
		ATTEMPT( rs.HasRows() && rs.FetchRow() ) ;

		ATTEMPT( rs[0] != NULL && strcmp(rs[0], "Hello") == 0 ) ;
		ATTEMPT( rs[1] != NULL && strcmp(rs[1], "world") == 0 ) ;
		ATTEMPT( rs[2] != NULL && strcmp(rs[2], "A string of some length") == 0 ) ;
		ATTEMPT( rs[3] != NULL && strcmp(rs[3], "42") == 0 ) ;
		ATTEMPT( rs[4] != NULL && strcmp(rs[4], "13.0") == 0 ) ;
		ATTEMPT( rs[5] == NULL || rs[5][0] == 0 ) ;

		// Now attempt to access them via cursor.
		const char* hello = NULL ;
		const char* world = NULL ;
		const char* longstring = NULL ;
		unsigned int life = 0 ;
		float danger = 0.0 ;

		ATTEMPT( rs >> hello >> world >> longstring >> life >> danger ) ;

		ATTEMPT( hello != NULL && strcmp(hello, "Hello") == 0 ) ;
		ATTEMPT( world != NULL && strcmp(world, "world") == 0 ) ;
		ATTEMPT( longstring != NULL && strcmp(longstring, "A string of some length") == 0 ) ;
		ATTEMPT( life == 42 ) ;
		ATTEMPT( danger == 13.0 ) ;

		// And finally, some more advanced functionality.
		const char* strings[3] = { "hello", "world", "farewell" } ;
		static const char createTable[] = "CREATE TEMPORARY TABLE t_dba_test ( `an_int` int not null, `a_string` varchar(30) )" ;
		ATTEMPT( rs.Do(sizeof(createTable) - 1, createTable) ) ;
		ATTEMPT( rs.Do("INSERT INTO t_dba_test (`an_int`, `a_string`) VALUES (0, '%s')", 200, strings[0]) ) ;
		ATTEMPT( rs.Do("INSERT INTO t_dba_test (`an_int`, `a_string`) VALUES (1, '%s')", 200, strings[1]) ) ;
		ATTEMPT( rs.Do("INSERT INTO t_dba_test (`an_int`, `a_string`) VALUES (2, '%s')", 200, strings[2]) ) ;

		static const char selectRows[] = "SELECT `an_int`, `a_string`, NULL, 1 FROM `t_dba_test` ORDER BY `an_int`" ;
		ATTEMPT( rs.Do(sizeof(selectRows) - 1, selectRows) ) ;

		ATTEMPT( rs.HasRows() ) ;
		if ( conn.HasAccurateRowCount() )
		{
			ATTEMPT( rs.Rows() == 3 ) ;
		}
		else
		{
			ATTEMPT( rs.Rows() >= 1 ) ;
		}

		int row = 0 ;
		do
		{
			ANNOTATE( "Row " << row ) ;

			int theInt = ~0 ;		// So it won't match the 0 in the first row.
			const char* theString = NULL ;
			const char* null = NULL ;

			ATTEMPT( rs.FetchRow() ) ;

			ATTEMPT( rs[1] != NULL && strcmp(rs[1], strings[row]) == 0 ) ;
			ATTEMPT( rs[2] == NULL || rs[2][0] == 0 ) ;

			ATTEMPT( rs >> theInt ) ;
			ATTEMPT( theInt == row ) ;
			ATTEMPT( rs >> theString ) ;
			ATTEMPT( theString != NULL && strcmp(theString, strings[row]) == 0 ) ;
			ATTEMPT( rs >> null ) ;
			ATTEMPT( null == NULL || *null == 0 ) ;

			++row ;
		}
		while ( row < 3 ) ;

		// We should get false now if we try for another row.
		ATTEMPT( !rs.FetchRow() ) ;

		ATTEMPT( conn.Disconnect() ) ;
	}
	catch ( std::exception& e )
	{
		progress << "FAIL: " << e.what() << endl ;
		cout << "FAILED." << endl << progress.rdbuf()->str().c_str() ;
		return false ;
	}

	cout << "OK." << endl ;

	if ( g_verbose )
		cout << progress.rdbuf()->str().c_str() ;

	return true ;
}

int main(int argc, const char* argv[])
{
	// If called with "-v" or "/v", enable verbose output.
	if ( argc > 1 && (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "/v") == 0) )
		g_verbose = true ;

	bool success = true ;

#ifdef DBA_ENABLE_MYSQL
	// MySQL database credentials, I have a local database called 'test'
	// my username is 'osmith' with password 'database'.
	DBA::Credentials myCredentials("localhost", 0, "test", "osmith", "database") ;
	if ( !testDatabase<DBA::MySQLConnection>("MySQL", myCredentials) )
		success = false ;
#endif

#ifdef DBA_ENABLE_SQLITE
	DBA::Credentials sqCredentials("test") ;
	if ( !testDatabase<DBA::SQLiteConnection>("SQLite", sqCredentials) )
		success = false ;
#endif

	if ( success )
		cout << "SUCCESS. All tests passed." << endl ;
	else
		cout << "FAILED. Some tests failed." << endl ;

	return 0 ;
}

