//////////////////////////////////////////////////////////////////////
// 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 CHECK( _statement ) \
    progress << " ? " << __LINE__ << ":" << #_statement << ": " ; \
    if ( !(_statement) ) throw std::logic_error("Not true") ; \
    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.
        CHECK( rs.HasRows() ) ;

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

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

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

        // Make sure what we got back looks sensible.
        CHECK( indexedValue != NULL ) ;
        CHECK( 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::logic_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?
        CHECK( cursoredValue != NULL ) ;
        CHECK( strcmp(cursoredValue, "42") == 0 ) ;

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

        // Ensure that FetchRow() returns false attempting to fetch a second row.
        CHECK( !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.
        CHECK( rs.HasRows() && rs.FetchRow() ) ;

        CHECK( rs[0] != NULL && rs[1] != NULL && rs[2] != NULL && rs[3] != NULL && rs[4] != NULL ) ;
        CHECK( rs[5] == NULL ) ;

        CHECK( strcmp(rs[0], "Hello") == 0 ) ;
        CHECK( strcmp(rs[1], "world") == 0 ) ;
        CHECK( strcmp(rs[2], "A string of some length") == 0 ) ;
        CHECK( strcmp(rs[3], "42") == 0 ) ;
        CHECK( (float)atof(rs[4]) == (float)13.02 ) ;

        // 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 ) ;

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

        // 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) ) ;

        CHECK( rs.HasRows() ) ;
        if ( conn.HasAccurateRowCount() )
        {
            CHECK( rs.Rows() == 3 ) ;
        }
        else
        {
            CHECK( 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 ;

            CHECK( rs.FetchRow() ) ;

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

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

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

        // We should get false now if we try for another row.
        CHECK( !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 ;
}