#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#include <kdb.h>

#define INFINITY 100000
#define NUM_THREAD 100

#define ROOT_KEY "user/test"

#define NUL_KEY ROOT_KEY "/nul"
#define NUL_VAL "s"
#define NUL_COM "s"

#define NEW_KEY ROOT_KEY "/new"
#define NEW_VAL "value"
#define NEW_COM "comme"

#define ANO_KEY ROOT_KEY "/ano"
#define ANO_VAL "ano=th;va\nlue"
#define ANO_COM "com=en;tt\nhhi"

#define DIR_KEY ROOT_KEY "/dir"

#define TEST_OK 0
#define TEST_FAIL 1

#define BUF_SIZ 50


void statisticsOut ();

/*Printing Functions for information/errors*/
void bailOut (const char * msg);
void headerOut (const char * msg);
void testOut (Key * orig, Key * read, const char * msg);

/*These are the only functions not using
 * the above printing functions. They are
 * not used by the testing suite.*/
void printKey (Key * k);
void printKeys (KeySet * set);

/*Get all keys and check if valid*/
void getKey();
void getKeys();

/*Sets all above keys*/
void setKey();
void setKeys();

/*Deletes all above keys*/
void delKeys();


pthread_t preader[NUM_THREAD], pwriter [NUM_THREAD];

int failures;
int tests;

int counter;
void * reader (void * pV_data);
void * writer (void * pV_data);

int in = 0;
int out = 0;

int main ()
{
	int i;
	for (i=0; i< NUM_THREAD; i++) if (pthread_create (&preader[i], NULL, reader, (void *) 0) != 0) exit (1);
	for (i=0; i< NUM_THREAD; i++) if (pthread_create (&pwriter[i], NULL, writer, (void *) 0) != 0) exit (1);

	for (i=0; i< NUM_THREAD; i++) pthread_join (preader[i],NULL);
	for (i=0; i< NUM_THREAD; i++) pthread_join (pwriter[i],NULL);
	
	statisticsOut();

	return 0;
}

/*int main(int argc, char **argv) {
	kdbOpen(&handle);

	headerOut ("start tests");
	
	headerOut ("single set, single get");
	setKey(handle);
	getKey();	
	delKeys();

	headerOut ("multiple set, single get");
	setKeys ();
	getKey ();
	delKeys();
	
	headerOut ("single set, multiple get");
	setKey();
	getKeys();	
	delKeys();

	headerOut ("multiple set, multiple get");
	setKeys ();
	getKeys ();
	delKeys ();
	
	headerOut ("finished tests");
	
	kdbClose(&handle);

	
	return 0;
}*/


#define NR 1000
void * reader (void * pV_data)
{
	int i;
	KDBHandle h;
	kdbOpen (&h);
	KeySet * set = ksNew ();
	for (i=0; i<NR; i++)
	{
		setKeys (h);
		getKeys (h);
		/*printf ("reader\n");
		fflush (stdout);*/
	}
	

	kdbClose (&h);
	return (NULL);
}

void * writer (void * pV_data)
{
	int i;
	KDBHandle h;
	kdbOpen (&h);

	for (i=0; i< NR; i++)
	{
		setKey(h);
		getKey(h);
		/*printf ("writer\n");
		fflush (stdout);*/
	}

	kdbClose (&h);
	return (NULL);
}


/**Return new various keys
 * These Keys will be set and get in various
 * tests. It will be evaluated if the backend
 * does everything correctly.*/
Key * newNulKey ()
{
	return
	keyNew (NUL_KEY,
		KEY_SWITCH_VALUE, NUL_VAL,
		KEY_SWITCH_COMMENT, NUL_COM,
		KEY_SWITCH_END);
}

Key * newNewKey ()
{
	return 
	keyNew (NEW_KEY,
		KEY_SWITCH_VALUE, NEW_VAL,
		KEY_SWITCH_COMMENT, NEW_COM,
		KEY_SWITCH_END);
}

Key * newAnoKey ()
{
	return 
	keyNew (ANO_KEY,
		KEY_SWITCH_VALUE, ANO_VAL,
		KEY_SWITCH_COMMENT, ANO_COM,
		KEY_SWITCH_END);
}

Key * newDirKey ()
{
	return 
	keyNew (DIR_KEY,
		KEY_SWITCH_TYPE, KEY_TYPE_DIR);
}


/**Prints header for failures*/
void bailOut (const char * msg)
{
	fprintf (stderr, "******** %29s ********\n", msg);
	//TODO:
	kdbPrintError (msg);
	// perror (msg);
	// exit (0);
}

/**Prints header for what is done now*/
void headerOut (const char * msg)
{
	fprintf (stderr, "-------- %29s --------\n", msg);
}

void testOut (Key * orig, Key * read, const char * keyname)
{
	uint32_t failed=0;
	
	fprintf (stderr, "Test ");
	tests ++;
	failed=keyCompare (orig, read);
	if (failed == KEY_SWITCH_NEEDSYNC)
	{
		fprintf (stderr, "succeded for : %s\n", keyname);
		// return;
	} else {
		fprintf (stderr, "failed for : %s\n", keyname);
		failures ++;
	}
	if (failed & KEY_SWITCH_TYPE) 
		fprintf (stderr, "type differs: is %d, was %d\n",
			keyGetType(read),keyGetType(orig));
	if (failed & KEY_SWITCH_NAME)
		fprintf (stderr, "name differs: is \"%s\", was \"%s\"\n",
			keyStealName(read),keyStealName(orig));
	if (failed & KEY_SWITCH_VALUE)
		fprintf (stderr, "value differs: is \"%s\", was \"%s\"\n",
			(char *) keyStealValue(read), (char *) keyStealValue(orig));
	if (failed & KEY_SWITCH_OWNER)
		fprintf (stderr, "owner differs: is \"%s\", was \"%s\"\n",
			keyStealOwner(read),keyStealOwner(orig));
	if (failed & KEY_SWITCH_COMMENT)
		fprintf (stderr, "comment differs: is \"%s\", was \"%s\"\n",
			keyStealComment(read),keyStealComment(orig));
	if (failed & KEY_SWITCH_UID)
		fprintf (stderr, "uid differs: is \"%d\", was \"%d\"\n",
			keyGetUID(read),keyGetUID(orig));
	if (failed & KEY_SWITCH_GID)
		fprintf (stderr, "gid differs: is \"%d\", was \"%d\"\n",
			keyGetGID(read),keyGetGID(orig));
	if (failed & KEY_SWITCH_MODE)
		fprintf (stderr, "mode differs: is \"0%o\", was \"0%o\"\n",
			keyGetAccess(read),keyGetAccess(orig));
	if (failed & KEY_SWITCH_FLAG)
		fprintf (stderr, "flag differs\n");

	keyDel (orig);
}

void statisticsOut ()
{
	printf ("\n******************************\n");
	printf (" %d    tests total\n", tests);
	printf ("-%d    tests passed\n", tests-failures);
	printf ("-----\n");
	printf (" %d    tests failed", failures);
	if (failures != 0) printf ("  (%3.2f %%)", ((double)failures/(double)tests)*100.0);
	printf ("\n******************************\n");
}

void printKey (Key * k)
{
	size_t s;
	char * str;

	size_t c;
	char * com;

	size_t n;
	char * nam;
	
	n = keyGetNameSize (k);
	nam = (char*) malloc (n);
	if (nam == NULL) {bailOut ("malloc error");}
	keyGetName (k, nam, n);
	
	s = keyGetDataSize (k);
	str = (char*) malloc (s+100);
	if (str == NULL) {bailOut ("malloc error");}
	keyGetString (k, str, s);

	c = keyGetCommentSize (k);
	com = (char*) malloc (c+100);
	if (com == NULL) {bailOut ("malloc error");}
	keyGetComment (k, com, c);
	
	printf ("Name[%d,%d]: %s\t", n, strblen(nam), nam);
	printf ("String[%d,%d]: %s\t", s, strblen(str), str);
	printf ("Kommentar[%d,%d]: %s\n", c, strblen(com), com);

	free (nam);
	free (str);
	free (com);
}

void printKeys (KeySet * set)
{
	Key * k;
	ksRewind (set);
	k = ksNext (set);
	while (k) {
		printKey (k);
		k = ksNext (set);
	}
}

void getKeys (KDBHandle handle)
{
	Key * t;
	KeySet * set;

	/* int ret;
	Key * root;
	Key *parentKey;
        ssize_t rc; */

	set = ksNew();

	/* Get all value keys for this application */
	headerOut ("getChildKeys");
	set = ksNew();
	if (kdbGetChildKeys (handle, ROOT_KEY, set, KDB_O_RECURSIVE | KDB_O_DIR) == -1)
		bailOut("kdbGetChildKeys");

	t = ksLookupByName (set, NUL_KEY, 0);
	if (t) testOut (newNulKey(), t, NUL_KEY);
	else bailOut ("Did not find nulkey");
	
	ksRewind(set);
	t = ksLookupByName (set, NEW_KEY, 0);
	if (t) testOut (newNewKey(), t, NEW_KEY);
	else bailOut ("Did not find newkey");
	
	ksRewind(set);
	t = ksLookupByName (set, ANO_KEY, 0);
	if (t) testOut (newAnoKey(), t, ANO_KEY);
	else bailOut ("Did not find anokey");
	
	ksRewind(set);
	t = ksLookupByName (set, DIR_KEY, 0);
	if (t) testOut (newDirKey(), t, DIR_KEY);
	else bailOut ("Did not find dirkey");
		
	ksDel (set);
}


void getKey(KDBHandle handle)
{
	Key * k;

	headerOut ("getKey");

	k = keyNew(NUL_KEY, KEY_SWITCH_END);
	if (kdbGetKey (handle, k) == -1) bailOut ("newkey kdbGetKey");
	else testOut (newNulKey(), k, NUL_KEY); 
	keyDel (k);
	
	k = keyNew(NEW_KEY, KEY_SWITCH_END);
	if (kdbGetKey (handle, k) == -1) bailOut ("newkey kdbGetKey");
	else testOut (newNewKey(), k, NEW_KEY); 
	keyDel (k);
	
	k = keyNew(ANO_KEY, KEY_SWITCH_END);
	if (kdbGetKey (handle, k) == -1) bailOut ("kdbGetKey");
	else testOut (newAnoKey(), k, ANO_KEY); 
	keyDel (k);
	
	k = keyNew(DIR_KEY, KEY_SWITCH_END);
	if (kdbGetKey (handle, k) == -1) bailOut ("kdbGetKey");
	else testOut (newDirKey(), k, DIR_KEY); 
	keyDel (k);
}

void setKey (KDBHandle handle)
{
	Key * k;
	k = newNulKey();
	if (kdbSetKey (handle, k) == -1) bailOut ("Error in kdbSetKeys");
	keyDel (k);
	
	k = newNewKey();
	if (kdbSetKey (handle, k) == -1) bailOut ("Error in kdbSetKeys");
	keyDel (k);
	
	k = newAnoKey();
	if (kdbSetKey (handle, k) == -1) bailOut ("Error in kdbSetKeys");
	keyDel (k);
	
	k = newDirKey();
	if (kdbSetKey (handle, k) == -1) bailOut ("Error in kdbSetKeys");
	keyDel (k);
}

void setKeys (KDBHandle handle)
{
	KeySet * ks = ksNew();
	ksAppend (ks, newNulKey());
	ksAppend (ks, newNewKey());
	ksAppend (ks, newAnoKey());
	ksAppend (ks, newDirKey());
	
	if (kdbSetKeys (handle, ks) == -1) bailOut ("Error in kdbSetKeys");
	ksDel (ks);
}


/**This removes all the keys*/
void delKeys (KDBHandle handle)
{
	headerOut ("Will remove all keys now");

	Key * k;
	k = keyNew(NUL_KEY, KEY_SWITCH_END);
	if (kdbRemoveKey (handle, k) == -1) bailOut ("Error Remove Nul Key");
	keyDel (k);
	k = keyNew(NEW_KEY, KEY_SWITCH_END);
	if (kdbRemoveKey (handle, k) == -1) bailOut ("Error Remove New Key");
	keyDel (k);
	k = keyNew(ANO_KEY, KEY_SWITCH_END);
	if (kdbRemoveKey (handle, k) == -1) bailOut ("Error Remove Ano Key");
	keyDel (k);
	k = keyNew(DIR_KEY, KEY_SWITCH_END);
	if (kdbRemoveKey (handle, k) == -1) bailOut ("Error Remove Dir Key");
	keyDel (k);
	
}

