#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "psDB.h"
#include "psleak.h"

#define TEST_SETUP\
    int testsRun    = 0;\
    int testsPassed = 0;

#define TEST_SUMMARY\
    printf("\n\n### TEST SUMMARY ###\n\n    %d of %d tests succeeded\n\n", \
        testsPassed, testsRun);

#define OK(test, name) \
    testsRun++; \
    printf("Test #%d: %s... ", testsRun, name); \
    if (test) { \
        testsPassed++; \
        printf("OK\n"); \
    } else { \
        printf("FAILED\n"); \
    }

int main (int argv, char **argc)
{ 
    TEST_SETUP;

    psMemAllocateCallbackSet(stacMemPrint);
    //psMemAllocateCallbackSetID(224);
    psMemFreeCallbackSet(stacMemPrint);
    psMemProblemCallbackSet(stacMemoryProblem);
    atexit(stacCheckMemory);

    psDB            *dbh;

    // open db
    dbh = psDBInit("localhost", "test", NULL, "test");
    OK(dbh, "psDBInit() - connect");

    {
        psDB        *foo;

        foo = psDBInit("tsohlacol", "tset", NULL, "tset");
        OK(!foo, "psDBInit(), bad DSN");
    }

    // the next 3 tests will fail as the MySQL test account has insufficent
    // permissions
    OK(psDBCreate(dbh, "foobar"), "psDBCreate()");
    OK(psDBChange(dbh, "foobar"), "psDBChange()");
    OK(psDBDrop(dbh, "foobar"), "psDBDrop()");

    // psDBCreateTable() & psDBDropTable()
    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "foo", PS_TYPE_S32, PS_META_PRIMITIVE, "Primary Key"); 
        psMetadataAdd(md, 1, "bar", PS_TYPE_BOOL,PS_META_PRIMITIVE, "Key"); 
        psMetadataAdd(md, 2, "baz", PS_TYPE_F64, PS_META_PRIMITIVE, NULL);
        psMetadataAdd(md, 3, "boing", PS_TYPE_PTR, PS_META_STR, NULL, "60");

        OK(psDBCreateTable(dbh, "bar", md), "psDBCreateTable() - create table");

        psFree(md);
    }

    OK(psDBDropTable(dbh, "bar"), "psDBDropTable() - drop table");

    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "foo", PS_TYPE_S32, PS_META_PRIMITIVE, "Primary Key"); 

        psDBCreateTable(dbh, "bar", md);

        OK(!psDBCreateTable(dbh, "bar", md), "psDBCreateTable() - table already exists");

        psFree(md);

        psDBDropTable(dbh, "bar");
    }

    OK(!psDBDropTable(dbh, "fubar"), "psDBDropTable() - non-existant table");

    // setup yak table for later tests
    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "hair", PS_TYPE_F32, PS_META_PRIMITIVE, NULL);

        psDBCreateTable(dbh, "yak", md);

        psFree(md);
    }

    // setup horse table for later tests
    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "100");

        psDBCreateTable(dbh, "horse", md);

        psFree(md);
    }

    // psDBInsertOneRow()
    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "hair", PS_TYPE_F32, PS_META_PRIMITIVE, NULL, 10e3);

        OK(psDBInsertOneRow(dbh, "yak", md),"psDBInsertOneRow() - number");

        psFree(md);
    }

    {
        psMetadata      *md;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "hair", PS_TYPE_F32, PS_META_PRIMITIVE, NULL, NAN);

        OK(psDBInsertOneRow(dbh, "yak", md),"psDBInsertOneRow() - nan");

        psFree(md);
    }

    // psDBInsertRows()
    {
        psArray         *rowSet;
        psMetadata      *row;

        rowSet = psArrayAlloc(3);
        rowSet->n = 0;

        row = psMetadataAlloc();
        psMetadataAdd(row, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "brown"); 
        psArrayAdd(rowSet, 0, row);
        // FIXME
        // psFree(row);

        row = psMetadataAlloc();
        psMetadataAdd(row, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, ""); 
        psArrayAdd(rowSet, 0, row);
        // FIXME
        // psFree(row);

        row = psMetadataAlloc();
        psMetadataAdd(row, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "pink"); 
        psArrayAdd(rowSet, 0, row);
        // FIXME
        // psFree(row);

        OK(psDBInsertRows(dbh, "horse", rowSet), "psDBInsertRows() - multi-row insert");

        psFree(rowSet);
    }

    // psDBSelectColumn()
    {
        psArray         *column;
        int             i;

        column = psDBSelectColumn(dbh, "horse", "color", 42);
        OK(column, "psDBSelectColumn() - select");
        OK(column->n == 3, "psDBSelectColumn() - number of elements");

        for (i = 0; i < column->n; i++) {
            printf("\thorse color is: %s\n", (char *)column->data[i]);
        }

        psFree(column);
    }

    // psDBSelectColumnNum()
    {
        psVector        *column;
        int             i;

        column = psDBSelectColumnNum(dbh, "yak", "hair", PS_TYPE_F32, 42);
        OK(column, "psDBSelectColumnNum() - select");
        OK(column->n == 2, "psDBSelectColumnNum() - number of elements");

        for (i = 0; i < column->n; i++) {
            printf("\tyak hair count is: %f\n", column->data.F32[i]);
            if (isnan(column->data.F32[i])){
                printf("\t\tNO yak hair!!! (it's a nan)\n");
            }
        }

        psFree(column);
    }

    // psDBSelectRows()
    {
        psMetadata      *md;
        psMetadataItem  *item;
        psArray         *resultSet;
        psListIterator  *cursor;
        int             i;

        md = psMetadataAlloc();
        psMetadataAdd(md, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "brown"); 

        resultSet = psDBSelectRows(dbh, "horse", md, 0);
        OK(resultSet, "psDBSelectRows() - select");
        OK(resultSet->n == 1, "psDBSelectRows() - number of rows");

        psFree(md);
        for (i = 0; i < resultSet->n; i++) {
            md = (psMetadata *)resultSet->data[0];

            cursor = psListIteratorAlloc(md->list, 0);

            while ((item = psListGetNext(cursor))) {
                printf("\tcolum name is: %s, value is: %s\n", item->name, (char *)item->data.V);
            }

            psFree(cursor);
        }
        psFree(resultSet);

    }


    // psDBDumpRows()
    {
        psArray         *resultSet;

        resultSet = psDBDumpRows(dbh, "horse");
        OK(resultSet, "psDBDumpRows() - dump");
        OK(resultSet->n == 3, "psDBDumpRows() - number of rows");
        psFree(resultSet);
    }

    // psDBDumpCols()
    {
        psMetadata      *columns;
        psMetadataItem  *item;
        psListIterator  *cursor;

        columns = psDBDumpCols(dbh, "horse");
        OK(columns, "psDBDumpCols() - dump");
        OK(columns->list->size == 1, "psDBDumpCols() - number of columns");

        cursor = psListIteratorAlloc(columns->list, 0);

        while ((item = psListGetNext(cursor))) {
            printf("\tcolumn name is: %s\n", item->name);
        }

        psFree(cursor);
        psFree(columns);
    }

    // psDBUpdateRows()
    {
        psMetadata      *where;
        psMetadata      *value;
        psU64           rowsAffected;
        psArray         *resultSet;
        int             i;
        psListIterator  *cursor;
        psMetadata      *md;
        psMetadataItem  *item;

        where = psMetadataAlloc();
        psMetadataAdd(where, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "pink"); 

        value = psMetadataAlloc();
        psMetadataAdd(value, 0, "color", PS_TYPE_PTR, PS_META_STR, NULL, "HOT pink"); 

        rowsAffected = psDBUpdateRows(dbh, "horse", where, value);
        psFree(where);
        psFree(value);

        OK(rowsAffected, "psDBUpdateRows() - pink -> HOT pink");

        resultSet = psDBSelectRows(dbh, "horse", value, 0);

        for (i = 0; i < resultSet->n; i++) {
            md = (psMetadata *)resultSet->data[0];

            cursor = psListIteratorAlloc(md->list, 0);

            while ((item = psListGetNext(cursor))) {
                if (strcmp((char *)item->data.V, "HOT pink") == 0) {
                    OK(1, "psDBUpdateRows() - psDBSelectRows() found HOT pink");
                    printf("\thorse color is: %s\n", (char *)item->data.V);
                }
            }

            psFree(cursor);
        }

        psFree(resultSet);
    }

    // psDBDeleteRows()
    OK(psDBDeleteRows(dbh, "yak", NULL), "psDBDeleteRows()");
    
    // cleanup other tests
    psDBDropTable(dbh, "horse");
    psDBDropTable(dbh, "yak");

    // close db
    psDBCleanup(dbh);

    TEST_SUMMARY;

    //psErrorStackPrint(stderr, "### Error stack dump ###\n");

    exit(0);
}
