#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/sendfile.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

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

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

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

typedef int (* evtest_callback_t)(struct ukevent *);

static int kevent_ctl_fd;
static unsigned int kevent_events = KEVENT_POLL_POLLIN|KEVENT_POLL_POLLRDNORM|KEVENT_POLL_POLLHUP|KEVENT_POLL_POLLERR|KEVENT_POLL_POLLNVAL;

static void usage(char *p)
{
	ulog("Usage: %s -a addr -p port -f kevent_path -t timeout -w wait_num\n", p);
}

static int evtest_server_init(char *addr, unsigned short port)
{
	struct hostent *h;
	int s, on;
	struct sockaddr_in sa;

	if (!addr) {
		ulog("%s: Bind address cannot be NULL.\n", __func__);
		return -1;
	}
	
	h = gethostbyname(addr);
	if (!h) {
		ulog_err("%s: Failed to get address of %s.\n", __func__, addr);
		return -1;
	}
	
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == -1) {
		ulog_err("%s: Failed to create server socket", __func__);
		return -1;
	}

	memcpy(&(sa.sin_addr.s_addr), h->h_addr_list[0], 4);
	sa.sin_port = htons(port);
	sa.sin_family = AF_INET;
		
	on = 1;
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, 4);
	
	if (bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) == -1) {
		ulog_err("%s: Failed to bind to %s", __func__, addr);
		close(s);
		return -1;
	}

	if (listen(s, 30000) == -1) {
		ulog_err("%s: Failed to listen on %s", __func__, addr);
		close(s);
		return -1;
	}

	return s;
}

static int evtest_kevent_remove(struct ukevent *event)
{
	struct kevent_user_control *ctl;
	struct ukevent *uk;
	int err;
	char buf[4096];

	ctl = (struct kevent_user_control *)buf;
	uk = (struct ukevent *)(ctl + 1);

	ctl->cmd = KEVENT_CTL_REMOVE;
	ctl->num = 1;
	ctl->timeout = 1000;

	memcpy(uk, event, sizeof(struct ukevent));

	err = ioctl(kevent_ctl_fd, KEVENT_USER_CTL, buf);
	if (err < 0) {
		ulog_err("Failed to perform control REMOVE operation");
		return err;
	}

	return err;
}

static int evtest_kevent_modify(int fd, unsigned int events, evtest_callback_t callback)
{
	struct kevent_user_control *ctl;
	struct ukevent *uk;
	int err;
	char buf[4096];
	struct timeval tm;

	ctl = (struct kevent_user_control *)buf;
	uk = (struct ukevent *)(ctl + 1);

	ctl->cmd = KEVENT_CTL_MODIFY;
	ctl->num = 1;
	ctl->timeout = 1000;

	uk->type = KEVENT_POLL;
	uk->event = events;
	uk->ret_flags = 0;
	memcpy(uk->user, &callback, sizeof(uk->user));
	uk->id.raw[0] = fd;

	err = ioctl(kevent_ctl_fd, KEVENT_USER_CTL, buf);
	gettimeofday(&tm, NULL);
	ulog("%08lu:%06lu: modify fd=%3d, callback=%p, err=%1d, ctl->num=%d.\n", tm.tv_sec, tm.tv_usec, fd, callback, err, ctl->num);
	if (err < 0) {
		ulog_err("Failed to perform control ADD operation: fd=%d, events=%08x", fd, events);
		return err;
	} /*else if (ctl->num)
		err = callback(uk);*/

	return err;
}

static int evtest_kevent_init(int fd, unsigned int events, evtest_callback_t callback)
{
	struct kevent_user_control *ctl;
	struct ukevent *uk;
	int err;
	char buf[4096];
	struct timeval tm;

	ctl = (struct kevent_user_control *)buf;
	uk = (struct ukevent *)(ctl + 1);

	ctl->cmd = KEVENT_CTL_ADD;
	ctl->num = 1;
	ctl->timeout = 1000;

	uk->type = KEVENT_POLL;
	uk->event = events;
	memcpy(uk->user, &callback, sizeof(uk->user));
	uk->id.raw[0] = fd;

	err = ioctl(kevent_ctl_fd, KEVENT_USER_CTL, buf);
	gettimeofday(&tm, NULL);
	ulog("%08lu:%06lu: fd=%3d, callback=%p, err=%1d, ctl->num=%d.\n", tm.tv_sec, tm.tv_usec, fd, callback, err, ctl->num);
	if (err < 0) {
		ulog_err("Failed to perform control ADD operation: fd=%d, events=%08x", fd, events);
		return err;
	} else if (ctl->num) {
		err = callback(uk);
	}

	return err;
}

static int evtest_callback_client(struct ukevent *e)
{
	int err, fd;
	int s = e->id.raw[0];
	off_t offset = 0;
	int count = 40960;
	char path[] = "/tmp/index.html";
	char buf[4096];
	struct timeval tm;
		
	err = recv(s, buf, sizeof(buf), 0);
	if (err < 0) {
		ulog_err("Failed to read data from s=%d", s);
		goto err_out_remove;
	}
	if (err == 0) {
		gettimeofday(&tm, NULL);
		ulog("%08lu:%06lu: Client exited: fd=%d.\n", tm.tv_sec, tm.tv_usec, s);
		goto err_out_remove;
	}

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		ulog_err("Failed to open '%s'", path);
		err = -1;
		goto err_out_remove;
	}
#if 0
	do {
		err = read(fd, buf, sizeof(buf));
		if (err <= 0)
			break;
		err = send(s, buf, err, 0);
		if (err <= 0)
			break;
	} while (1);
#endif
	err = sendfile(s, fd, &offset, count);
	{
		int on = 0;
		setsockopt(s, SOL_TCP, TCP_CORK, &on, sizeof(on));
	}

	close(fd);
	if (err < 0) {
		ulog_err("Failed send %d bytes: fd=%d.\n", count, s);
		goto err_out_remove;
	}

	gettimeofday(&tm, NULL);
	ulog("%08lu:%06lu: %d bytes has been sent to client fd=%d.\n", tm.tv_sec, tm.tv_usec, err, s);
	
	err = evtest_kevent_modify(s, kevent_events, &evtest_callback_client);

	return 0;

err_out_remove:
	evtest_kevent_remove(e);
	close(s);
	return err;
}

static int evtest_callback_main(struct ukevent *e)
{
	int cs, err;
	struct sockaddr_in csa;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	struct timeval tm;

	memset(&csa, 0, sizeof(csa));

	if ((cs = accept(e->id.raw[0], (struct sockaddr *)&csa, &addrlen)) == -1) {
		ulog_err("Failed to accept client");
		return -1;
	}

	gettimeofday(&tm, NULL);
	
	ulog("%08lu:%06lu: Accepted connect from %s:%d.\n",
		tm.tv_sec, tm.tv_usec,
		inet_ntoa(csa.sin_addr), ntohs(csa.sin_port));

	err = evtest_kevent_modify(e->id.raw[0], kevent_events, &evtest_callback_main);

	err = evtest_kevent_init(cs, kevent_events, &evtest_callback_client);
	if (err < 0) {
		close(cs);
		return -1;
	}

	return 0;
}

static int evtest_kevent_wait(unsigned int timeout, unsigned int wait_num)
{
	int num, err;
	struct kevent_user_control *ctl;
	struct ukevent *uk;
	struct timeval tm;
	evtest_callback_t func;
	int i;
	char buf[4096];

	ctl = (struct kevent_user_control *)buf;
	uk = (struct ukevent *)(ctl + 1);

	ctl->num = wait_num;
	ctl->timeout = timeout;

	num = ioctl(kevent_ctl_fd, KEVENT_USER_WAIT, buf);
	if (num < 0) {
		ulog_err("Failed to perform control operation");
		return num;
	}

	gettimeofday(&tm, NULL);

	ulog("%08lu.%06lu: Wait: num=%d, ctl->num=%u.\n", tm.tv_sec, tm.tv_usec, num, ctl->num);
	uk = (struct ukevent *)(ctl+1);
	for (i=0; i<num; ++i) {
		memcpy(&func, uk[i].user, sizeof(void *));

		ulog("%3u - %3u: %08x.%08x - %08x.%08x: %p.\n", 
			i, ctl->num,
			uk[i].id.raw[0], uk[i].id.raw[1],
			uk[i].ret_data[0], uk[i].ret_data[1],
			func);
		err = func(&uk[i]);
	}

	return 0;
}

int main(int argc, char *argv[])
{
	int ch, s, err;
	char *addr, *kevent_path;
	unsigned short port;
	unsigned int timeout, wait_num;

	kevent_path = NULL;
	addr = "0.0.0.0";
	port = 80;
	timeout = 1000;
	wait_num = 1;

	while ((ch = getopt(argc, argv, "n:t:f:a:p:h")) > 0) {
		switch (ch) {
			case 't':
				timeout = atoi(optarg);
				break;
			case 'n':
				wait_num = atoi(optarg);
				break;
			case 'f':
				kevent_path = optarg;
				break;
			case 'a':
				addr = optarg;
				break;
			case 'p':
				port = atoi(optarg);
				break;
			default:
				usage(argv[0]);
				return -1;
		}
	}

	if (!kevent_path) {
		ulog("You must specify at least -f parameter.\n");
		usage(argv[0]);
		return -1;
	}
	
	kevent_ctl_fd = open(kevent_path, O_RDWR);
	if (kevent_ctl_fd == -1) {
		ulog_err("Failed to open kevent control file '%s'", kevent_path);
		return -1;
	}
	
	s = evtest_server_init(addr, port);
	if (s < 0)
		return s;

	err = evtest_kevent_init(s, kevent_events, &evtest_callback_main);
	if (err < 0)
		goto err_out_exit;

	while (1) {
		err = evtest_kevent_wait(timeout, wait_num);
	}

err_out_exit:
	close(kevent_ctl_fd);
	return 0;
}
