#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "tdb.h"

/* this tests tdb by doing lots of ops from several simultaneous
   writers - that stresses the locking code. Build with TDB_DEBUG=1
   for best effect */



#define REOPEN_PROB 30
#define DELETE_PROB 8
#define STORE_PROB 4
#define APPEND_PROB 6
#define LOCKSTORE_PROB 0
#define TRAVERSE_PROB 20
#define CULL_PROB 100
#define KEYLEN 3
#define DATALEN 100
#define LOCKLEN 20

static TDB_CONTEXT *db;

static void tdb_log(TDB_CONTEXT *tdb, int level, const char *format, ...)
{
	va_list ap;
    
	va_start(ap, format);
	vfprintf(stdout, format, ap);
	va_end(ap);
	fflush(stdout);
#if 0
	{
		char *ptr;
		asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
		system(ptr);
		free(ptr);
	}
#endif	
}

static void fatal(char *why)
{
	perror(why);
	exit(1);
}

static char *randbuf(int len)
{
	char *buf;
	int i;
	buf = (char *)malloc(len+1);

	for (i=0;i<len;i++) {
		buf[i] = 'a' + (rand() % 26);
	}
	buf[i] = 0;
	return buf;
}

static int cull_traverse(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf,
			 void *state)
{
	if (random() % CULL_PROB == 0) {
		tdb_delete(tdb, key);
	}
	return 0;
}

static void addrec_db(void)
{
	int klen, dlen, slen;
	char *k, *d, *s;
	TDB_DATA key, data, lockkey;

	klen = 1 + (rand() % KEYLEN);
	dlen = 1 + (rand() % DATALEN);
	slen = 1 + (rand() % LOCKLEN);

	k = randbuf(klen);
	d = randbuf(dlen);
	s = randbuf(slen);

	key.dptr = k;
	key.dsize = klen+1;

	data.dptr = d;
	data.dsize = dlen+1;

	lockkey.dptr = s;
	lockkey.dsize = slen+1;

#if REOPEN_PROB
	if (random() % REOPEN_PROB == 0) {
		tdb_reopen_all();
		goto next;
	} 
#endif

#if DELETE_PROB
	if (random() % DELETE_PROB == 0) {
		tdb_delete(db, key);
		goto next;
	}
#endif

#if STORE_PROB
	if (random() % STORE_PROB == 0) {
		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
			fatal("tdb_store failed");
		}
		goto next;
	}
#endif

#if APPEND_PROB
	if (random() % APPEND_PROB == 0) {
		if (tdb_append(db, key, data) != 0) {
			fatal("tdb_append failed");
		}
		goto next;
	}
#endif

#if LOCKSTORE_PROB
	if (random() % LOCKSTORE_PROB == 0) {
		tdb_chainlock(db, lockkey);
		data = tdb_fetch(db, key);
		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
			fatal("tdb_store failed");
		}
		if (data.dptr) free(data.dptr);
		tdb_chainunlock(db, lockkey);
		goto next;
	} 
#endif

#if TRAVERSE_PROB
	if (random() % TRAVERSE_PROB == 0) {
		tdb_traverse(db, cull_traverse, NULL);
		goto next;
	}
#endif

	data = tdb_fetch(db, key);
	if (data.dptr) free(data.dptr);

next:
	free(k);
	free(d);
	free(s);
}

static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf,
                       void *state)
{
	tdb_delete(tdb, key);
	return 0;
}

#ifndef NPROC
#define NPROC 6
#endif

#ifndef NLOOPS
#define NLOOPS 200000
#endif

int main(int argc, char *argv[])
{
	int i, seed=0;
	int loops = NLOOPS;
	pid_t pids[NPROC];

	pids[0] = getpid();

	for (i=0;i<NPROC-1;i++) {
		if ((pids[i+1]=fork()) == 0) break;
	}

	db = tdb_open("torture.tdb", 2, TDB_CLEAR_IF_FIRST, 
		      O_RDWR | O_CREAT, 0600);
	if (!db) {
		fatal("db open failed");
	}
	tdb_logging_function(db, tdb_log);

	srand(seed + getpid());
	srandom(seed + getpid() + time(NULL));
	for (i=0;i<loops;i++) addrec_db();

	tdb_traverse(db, NULL, NULL);
	tdb_traverse(db, traverse_fn, NULL);
	tdb_traverse(db, traverse_fn, NULL);

	tdb_close(db);

	if (getpid() == pids[0]) {
		for (i=0;i<NPROC-1;i++) {
			int status;
			if (waitpid(pids[i+1], &status, 0) != pids[i+1]) {
				printf("failed to wait for %d\n",
				       (int)pids[i+1]);
				exit(1);
			}
			if (WEXITSTATUS(status) != 0) {
				printf("child %d exited with status %d\n",
				       (int)pids[i+1], WEXITSTATUS(status));
				exit(1);
			}
		}
		printf("OK\n");
	}

	return 0;
}
