/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

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

	sprintf(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 throught 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);
}
