/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* use sequental numbers printed to strings
 * to store lots and lots of entries in the
 * database.
 *
 * Start with 100 entries, put them and then
 * read them out.  Then delete the first
 * half and verify that all of the first half
 * is gone and then verify that the second
 * half is still there.
 * Then add the first half back and verify
 * again.  Then delete the middle third
 * and verify again.
 * Then increase the size by 1000 and do
 * the whole add delete thing again.
 *
 * The data for each object is the number string translated
 * to hex and replicated a random number of times.  The
 * number of times that the data is replicated is the first
 * int32 in the data.
 */

#include <stdio.h>

#include <stdlib.h>
#ifdef STDC_HEADERS
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#include <assert.h>
#include "mcom_db.h"

DB *database = 0;
int MsgPriority = 5;

#if defined(_WINDOWS) && !defined(WIN32)
#define int32 long
#define uint32 unsigned long
#else
#define int32 int
#define uint32 unsigned int
#endif

typedef enum {
    USE_LARGE_KEY,
    USE_SMALL_KEY
} key_type_enum;

#define TraceMe(priority, msg)         \
    do {                               \
        if (priority <= MsgPriority) { \
            ReportStatus msg;          \
        }                              \
    } while (0)

int
ReportStatus(char *string, ...)
{
    va_list args;

#ifdef STDC_HEADERS
    va_start(args, string);
#else
    va_start(args);
#endif
    vfprintf(stderr, string, args);
    va_end(args);

    fprintf(stderr, "\n");

    return (0);
}

int
ReportError(char *string, ...)
{
    va_list args;

#ifdef STDC_HEADERS
    va_start(args, string);
#else
    va_start(args);
#endif
    fprintf(stderr, "\n	");
    vfprintf(stderr, string, args);
    fprintf(stderr, "\n");
    va_end(args);

    return (0);
}

DBT *
MakeLargeKey(int32 num)
{
    int32 low_bits;
    static DBT rv;
    static char *string_rv = 0;
    int rep_char;
    size_t size;

    if (string_rv)
        free(string_rv);

    /* generate a really large text key derived from
     * an int32
     */
    low_bits = (num % 10000) + 1;

    /* get the repeat char from the low 26 */
    rep_char = (char)((low_bits % 26) + 'a');

    /* malloc a string low_bits wide */
    size = low_bits * sizeof(char);
    string_rv = (char *)malloc(size);

    memset(string_rv, rep_char, size);

    rv.data = string_rv;
    rv.size = size;

    return (&rv);
}

DBT *
MakeSmallKey(int32 num)
{
    static DBT rv;
    static char data_string[64];

    rv.data = data_string;

    snprintf(data_string, sizeof(data_string), "%ld", (long)num);
    rv.size = strlen(data_string);

    return (&rv);
}

DBT *
GenKey(int32 num, key_type_enum key_type)
{
    DBT *key;

    switch (key_type) {
        case USE_LARGE_KEY:
            key = MakeLargeKey(num);
            break;
        case USE_SMALL_KEY:
            key = MakeSmallKey(num);
            break;
        default:
            abort();
            break;
    }

    return (key);
}

int
SeqDatabase()
{
    int status;
    DBT key, data;

    ReportStatus("SEQuencing through database...");

    /* seq through the whole database */
    if (!(status = (*database->seq)(database, &key, &data, R_FIRST))) {
        while (!(status = (database->seq)(database, &key, &data, R_NEXT)))
            ; /* null body */
    }

    if (status < 0)
        ReportError("Error seq'ing database");

    return (status);
}

int
VerifyData(DBT *data, int32 num, key_type_enum key_type)
{
    int32 count, compare_num;
    size_t size;
    int32 *int32_array;

    /* The first int32 is count
     * The other n entries should
     * all equal num
     */
    if (data->size < sizeof(int32)) {
        ReportError("Data size corrupted");
        return -1;
    }

    memcpy(&count, data->data, sizeof(int32));

    size = sizeof(int32) * (count + 1);

    if (size != data->size) {
        ReportError("Data size corrupted");
        return -1;
    }

    int32_array = (int32 *)data->data;

    for (; count > 0; count--) {
        memcpy(&compare_num, &int32_array[count], sizeof(int32));

        if (compare_num != num) {
            ReportError("Data corrupted");
            return -1;
        }
    }

    return (0);
}

/* verify that a range of number strings exist
 * or don't exist. And that the data is valid
 */
#define SHOULD_EXIST 1
#define SHOULD_NOT_EXIST 0
int
VerifyRange(int32 low, int32 high, int32 should_exist, key_type_enum key_type)
{
    DBT *key, data;
    int32 num;
    int status;

    TraceMe(1, ("Verifying: %ld to %ld, using %s keys",
                low, high, key_type == USE_SMALL_KEY ? "SMALL" : "LARGE"));

    for (num = low; num <= high; num++) {

        key = GenKey(num, key_type);

        status = (*database->get)(database, key, &data, 0);

        if (status == 0) {
            /* got the item */
            if (!should_exist) {
                ReportError("Item exists but shouldn't: %ld", num);
            } else {
                /* else verify the data */
                VerifyData(&data, num, key_type);
            }
        } else if (status > 0) {
            /* item not found */
            if (should_exist) {
                ReportError("Item not found but should be: %ld", num);
            }
        } else {
            /* database error */
            ReportError("Database error");
            return (-1);
        }
    }

    TraceMe(1, ("Correctly verified: %ld to %ld", low, high));

    return (0);
}

DBT *
GenData(int32 num)
{
    int32 n;
    static DBT *data = 0;
    int32 *int32_array;
    size_t size;

    if (!data) {
        data = (DBT *)malloc(sizeof(DBT));
        data->size = 0;
        data->data = 0;
    } else if (data->data) {
        free(data->data);
    }

    n = rand();

    n = n % 512; /* bound to a 2K size */

    size = sizeof(int32) * (n + 1);
    int32_array = (int32 *)malloc(size);

    memcpy(&int32_array[0], &n, sizeof(int32));

    for (; n > 0; n--) {
        memcpy(&int32_array[n], &num, sizeof(int32));
    }

    data->data = (void *)int32_array;
    data->size = size;

    return (data);
}

#define ADD_RANGE 1
#define DELETE_RANGE 2

int
AddOrDelRange(int32 low, int32 high, int action, key_type_enum key_type)
{
    DBT *key, *data;
#if 0 /* only do this if your really analy checking the puts */
	DBT tmp_data;
#endif
    int32 num;
    int status;

    if (action != ADD_RANGE && action != DELETE_RANGE)
        assert(0);

    if (action == ADD_RANGE) {
        TraceMe(1, ("Adding: %ld to %ld: %s keys", low, high,
                    key_type == USE_SMALL_KEY ? "SMALL" : "LARGE"));
    } else {
        TraceMe(1, ("Deleting: %ld to %ld: %s keys", low, high,
                    key_type == USE_SMALL_KEY ? "SMALL" : "LARGE"));
    }

    for (num = low; num <= high; num++) {

        key = GenKey(num, key_type);

        if (action == ADD_RANGE) {
            data = GenData(num);
            status = (*database->put)(database, key, data, 0);
        } else {
            status = (*database->del)(database, key, 0);
        }

        if (status < 0) {
            ReportError("Database error %s item: %ld",
                        action == ADD_RANGE ? "ADDING" : "DELETING",
                        num);
        } else if (status > 0) {
            ReportError("Could not %s item: %ld",
                        action == ADD_RANGE ? "ADD" : "DELETE",
                        num);
        } else if (action == ADD_RANGE) {
#define SYNC_EVERY_TIME
#ifdef SYNC_EVERY_TIME
            status = (*database->sync)(database, 0);
            if (status != 0)
                ReportError("Database error syncing after add");
#endif

#if 0 /* only do this if your really analy checking the puts */

			/* make sure we can still get it
			 */
			status = (*database->get)(database, key, &tmp_data, 0);

			if(status != 0)
			  {
				ReportError("Database error checking item just added: %d",
							num);
			  }
			else
			  {
				/* now verify that none of the ones we already
				 * put in have disappeared
				 */
				VerifyRange(low, num, SHOULD_EXIST, key_type);
			  }
#endif
        }
    }

    if (action == ADD_RANGE) {
        TraceMe(1, ("Successfully added: %ld to %ld", low, high));
    } else {
        TraceMe(1, ("Successfully deleted: %ld to %ld", low, high));
    }

    return (0);
}

int
TestRange(int32 low, int32 range, key_type_enum key_type)
{
    int status;
    int32 low_of_range1, high_of_range1;
    int32 low_of_range2, high_of_range2;
    int32 low_of_range3, high_of_range3;

    status = AddOrDelRange(low, low + range, ADD_RANGE, key_type);
    status = VerifyRange(low, low + range, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 1"));

    SeqDatabase();

    low_of_range1 = low;
    high_of_range1 = low + (range / 2);
    low_of_range2 = high_of_range1 + 1;
    high_of_range2 = low + range;
    status = AddOrDelRange(low_of_range1, high_of_range1, DELETE_RANGE, key_type);
    status = VerifyRange(low_of_range1, high_of_range1, SHOULD_NOT_EXIST, key_type);
    status = VerifyRange(low_of_range2, low_of_range2, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 2"));

    SeqDatabase();

    status = AddOrDelRange(low_of_range1, high_of_range1, ADD_RANGE, key_type);
    /* the whole thing should exist now */
    status = VerifyRange(low, low + range, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 3"));

    SeqDatabase();

    status = AddOrDelRange(low_of_range2, high_of_range2, DELETE_RANGE, key_type);
    status = VerifyRange(low_of_range1, high_of_range1, SHOULD_EXIST, key_type);
    status = VerifyRange(low_of_range2, high_of_range2, SHOULD_NOT_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 4"));

    SeqDatabase();

    status = AddOrDelRange(low_of_range2, high_of_range2, ADD_RANGE, key_type);
    /* the whole thing should exist now */
    status = VerifyRange(low, low + range, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 5"));

    SeqDatabase();

    low_of_range1 = low;
    high_of_range1 = low + (range / 3);
    low_of_range2 = high_of_range1 + 1;
    high_of_range2 = high_of_range1 + (range / 3);
    low_of_range3 = high_of_range2 + 1;
    high_of_range3 = low + range;
    /* delete range 2 */
    status = AddOrDelRange(low_of_range2, high_of_range2, DELETE_RANGE, key_type);
    status = VerifyRange(low_of_range1, high_of_range1, SHOULD_EXIST, key_type);
    status = VerifyRange(low_of_range2, low_of_range2, SHOULD_NOT_EXIST, key_type);
    status = VerifyRange(low_of_range3, low_of_range2, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 6"));

    SeqDatabase();

    status = AddOrDelRange(low_of_range2, high_of_range2, ADD_RANGE, key_type);
    /* the whole thing should exist now */
    status = VerifyRange(low, low + range, SHOULD_EXIST, key_type);

    TraceMe(1, ("Finished with sub test 7"));

    return (0);
}

#define START_RANGE 109876
int
main(int argc, char **argv)
{
    int32 i, j = 0;
    int quick_exit = 0;
    int large_keys = 0;
    HASHINFO hash_info = {
        16 * 1024,
        0,
        0,
        0,
        0,
        0
    };

    if (argc > 1) {
        while (argc > 1) {
            if (!strcmp(argv[argc - 1], "-quick"))
                quick_exit = 1;
            else if (!strcmp(argv[argc - 1], "-large")) {
                large_keys = 1;
            }
            argc--;
        }
    }

    database = dbopen("test.db", O_RDWR | O_CREAT, 0644, DB_HASH, &hash_info);

    if (!database) {
        ReportError("Could not open database");
#ifdef unix
        perror("");
#endif
        exit(1);
    }

    if (quick_exit) {
        if (large_keys)
            TestRange(START_RANGE, 200, USE_LARGE_KEY);
        else
            TestRange(START_RANGE, 200, USE_SMALL_KEY);

        (*database->sync)(database, 0);
        (*database->close)(database);
        exit(0);
    }

    for (i = 100; i < 10000000; i += 200) {
        if (1 || j) {
            TestRange(START_RANGE, i, USE_LARGE_KEY);
            j = 0;
        } else {
            TestRange(START_RANGE, i, USE_SMALL_KEY);
            j = 1;
        }

        if (1 == rand() % 3) {
            (*database->sync)(database, 0);
        }

        if (1 == rand() % 3) {
            /* close and reopen */
            (*database->close)(database);
            database = dbopen("test.db", O_RDWR | O_CREAT, 0644, DB_HASH, 0);
            if (!database) {
                ReportError("Could not reopen database");
#ifdef unix
                perror("");
#endif
                exit(1);
            }
        } else {
            /* reopen database without closeing the other */
            database = dbopen("test.db", O_RDWR | O_CREAT, 0644, DB_HASH, 0);
            if (!database) {
                ReportError("Could not reopen database "
                            "after not closing the other");
#ifdef unix
                perror("");
#endif
                exit(1);
            }
        }
    }

    return (0);
}
