#include <Message.h>
#include <Entry.h>
#include <File.h>
#include <NodeMonitor.h>
#include <kernel/fs_info.h>
#include <kernel/fs_query.h>

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

#ifndef B_BAD_DATA
#	define B_BAD_DATA B_ERROR
#endif

#define DUMPED_BLOCK_SIZE 16
#define Print printf

void
dumpBlock(const char *buffer,int size)
{
	for(int i = 0;i < size;) {
		int start = i;

		for(;i < start+DUMPED_BLOCK_SIZE;i++) {
			if (!(i % 4))
				Print(" ");

			if (i >= size)
				Print("  ");
			else
				Print("%02x",*(unsigned char *)(buffer+i));
		}
		Print("  ");

		for(i = start;i < start + DUMPED_BLOCK_SIZE;i++) {
			if (i < size) {
				char c = *(buffer+i);

				if (c < 30)
					Print(".");
				else
					Print("%c",c);
			}
			else
				break;
		}
		Print("\n");
	}
}


void
createFile(int32 num)
{
	char name[B_FILE_NAME_LENGTH];
	sprintf(name,"./_query_test_%ld",num);

	BFile file(name,B_CREATE_FILE | B_WRITE_ONLY);
}


BEntry *
getEntry(int32 num)
{
	char name[B_FILE_NAME_LENGTH];
	sprintf(name,"./_query_test_%ld",num);

	return new BEntry(name);
}


status_t
waitForMessage(port_id port,const char *string,int32 op,char *name)
{
	puts(string);

	int32 msg;
	char buffer[1024];
	ssize_t bytes = read_port_etc(port,&msg,buffer,sizeof(buffer),B_TIMEOUT,1000000);
	if (op == B_TIMED_OUT && bytes == B_TIMED_OUT) {
		puts("  passed, timed out!\n");
		return bytes;
	}
	if (bytes < B_OK) {
		printf("-> %s\n\n",strerror(bytes));
		return bytes;
	}

	BMessage message;
	if (message.Unflatten(buffer) < B_OK) {
		printf("could not unflatten message:\n");
		dumpBlock(buffer,bytes);
		return B_BAD_DATA;
	}

	if (message.what != B_QUERY_UPDATE
		|| message.FindInt32("opcode") != op) {
		printf("Expected what = %lx, opcode = %ld, got:",B_QUERY_UPDATE,op);
		message.PrintToStream();
		putchar('\n');
		return message.FindInt32("opcode");
	}
	puts("  passed!\n");
	return op;
}


int
main(int argc,char **argv)
{
	port_id port = create_port(100,"query port");
	printf("port id = %ld\n",port);
	printf("  B_ENTRY_REMOVED = %ld, B_ENTRY_CREATED = %ld\n\n",B_ENTRY_REMOVED,B_ENTRY_CREATED);

	dev_t device = dev_for_path(".");
	DIR *query = fs_open_live_query(device,"name=_query_test_*",B_LIVE_QUERY,port,12345);

	createFile(1);
	waitForMessage(port,"File 1 created:",B_ENTRY_CREATED,"_query_test_1");

	createFile(2);
	waitForMessage(port,"File 2 created:",B_ENTRY_CREATED,"_query_test_2");

	BEntry *entry = getEntry(2);
	if (entry->InitCheck() < B_OK) {
		fprintf(stderr,"Could not get entry for file 2\n");
		fs_close_query(query);
		return -1;
	}
	entry->Rename("_some_other_name_");
	waitForMessage(port,"File 2 renamed (should fall out of query):",B_ENTRY_REMOVED,NULL);

	entry->Rename("_some_other_");
	waitForMessage(port,"File 2 renamed again (should time out):",B_TIMED_OUT,NULL);

	entry->Rename("_query_test_2");
	waitForMessage(port,"File 2 renamed back (should be added to query):",B_ENTRY_CREATED,"_query_test_2");

	entry->Rename("_query_test_2_and_more");
	status_t status = waitForMessage(port,"File 2 renamed (should stay in query, time out):",B_TIMED_OUT,"_query_test_2_and_more");
	if (status == B_ENTRY_REMOVED)
		waitForMessage(port,"Received B_ENTRY_REMOVED, now expecting file 2 to be added again:",B_ENTRY_CREATED,NULL);

	entry->Remove();
	delete entry;
	waitForMessage(port,"File 2 removed:",B_ENTRY_REMOVED,NULL);
	
	entry = getEntry(1);
	if (entry->InitCheck() < B_OK) {
		fprintf(stderr,"Could not get entry for file 1\n");
		fs_close_query(query);
		return -1;
	}

	entry->Rename("_some_other_name_");
	waitForMessage(port,"File 1 renamed (should fall out of query):",B_ENTRY_REMOVED,NULL);
		
	entry->Remove();
	delete entry;
	waitForMessage(port,"File 1 removed (should time out):",B_TIMED_OUT,NULL);

	fs_close_query(query);

	return 0;
}

