#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <linux/types.h>
#include <linux/kevent.h>

#define ulog(f, a...) fprintf(stderr, f, ##a)
#define ulog_err(f, a...) ulog(f ": %s [%d].\n", ##a, strerror(errno), errno)

static void usage(char *p)
{
	ulog("Usage: %s -f file -t type -e event -o oneshot -p path -n wait_num -h\n", p);
}

static int get_id(int type, char *path)
{
	int ret = -1;

	switch (type) {
		case KEVENT_TIMER:
			ret = 1000;
			break;
		case KEVENT_INODE:
			ret = open(path, O_RDONLY);
			break;
	}

	return ret;
}

int main(int argc, char *argv[])
{
	int ch, fd, err, type, event, oneshot, i, num, wait_num;
	char *file, *path;
	char buf[4096];
	struct kevent_user_control *ctl;
	struct ukevent *uk;
	struct timeval tm1, tm2;

	file = path = NULL;
	type = event = -1;
	oneshot = 0;
	wait_num = 10;

	while ((ch = getopt(argc, argv, "p:f:t:e:o:n:h")) > 0) {
		switch (ch) {
			case 'n':
				wait_num = atoi(optarg);
				break;
			case 'p':
				path = optarg;
				break;
			case 'f':
				file = optarg;
				break;
			case 't':
				type = atoi(optarg);
				break;
			case 'e':
				event = atoi(optarg);
				break;
			case 'o':
				oneshot = atoi(optarg);
				break;
			default:
				usage(argv[0]);
				return -1;
		}
	}

	if (!file || event == -1 || type == -1 || (type == KEVENT_INODE && !path)) {
		ulog("You need at least -f -t -e parameters and -p for inode notifications.\n");
		usage(argv[0]);
		return -1;
	}
	
	fd = open(file, O_RDWR);
	if (fd == -1) {
		ulog_err("Failed to open %s", file);
		return -1;
	}

	memset(buf, 0, sizeof(buf));
	
	ctl = (struct kevent_user_control *)buf;
	uk = (struct ukevent *)(ctl+1);
	
	gettimeofday(&tm1, NULL);

	num = 1;
	for (i=0; i<num; ++i) {
		ctl->num = 1;
		ctl->timeout = 1000;
		
		uk->type = type;
		uk->event = event;
		if (oneshot)
			uk->req_flags |= KEVENT_REQ_ONESHOT;
		uk->user[0] = i;
		uk->id.raw[0] = get_id(uk->type, path);

		err = ioctl(fd, KEVENT_USER_CTL, buf);
		if (err) {
			ulog_err("Failed to perform control operation: type=%d, event=%d, oneshot=%d", type, event, oneshot);
			close(fd);
			return err;
		}
	}
	
	gettimeofday(&tm2, NULL);

	ulog("%08ld.%08ld: Load: diff=%ld usecs.\n", 
			tm2.tv_sec, tm2.tv_usec, ((tm2.tv_sec - tm1.tv_sec)*1000000 + (tm2.tv_usec - tm1.tv_usec))/num);

	while (1) {
		ctl->num = wait_num;
		ctl->timeout = 3000;

		gettimeofday(&tm1, NULL);
		
		err = ioctl(fd, KEVENT_USER_WAIT, buf);
		if (err < 0) {
			ulog_err("Failed to perform control operation: type=%d, event=%d, oneshot=%d", type, event, oneshot);
			close(fd);
			return err;
		}
		
		gettimeofday(&tm2, NULL);

		ulog("%08ld.%08ld: Wait: num=%d, ctl->num=%d: diff=%ld usec.\n", 
				tm2.tv_sec, tm2.tv_usec,
				err, ctl->num, 
				((tm2.tv_sec - tm1.tv_sec)*1000000 + (tm2.tv_usec - tm1.tv_usec))/(ctl->num?ctl->num:1));
		uk = (struct ukevent *)(ctl+1);
		for (i=0; i<(signed)ctl->num; ++i) {
			ulog("%08x: %08x.%08x - %08x.%08x\n", 
					uk[i].user[0],
					uk[i].id.raw[0], uk[i].id.raw[1],
					uk[i].ret_data[0], uk[i].ret_data[1]);
		}
	}
	ulog("Closing %s, buf=%p.\n", file, buf);

	close(fd);
	return 0;
}
