#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/sendfile.h>
#include <sys/syscall.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 <signal.h>

#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/ukevent.h>

#include "evserver_common.c"

#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name (type1 arg1, type2 arg2) \
{\
	return syscall(__NR_##name, arg1, arg2);\
}

#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{\
	return syscall(__NR_##name, arg1, arg2, arg3, arg4);\
}

#define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
	  type5,arg5) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{\
	return syscall(__NR_##name, arg1, arg2, arg3, arg4, arg5);\
}
#define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
	  type5,arg5,type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5, type6 arg6) \
{\
	return syscall(__NR_##name, arg1, arg2, arg3, arg4, arg5, arg6);\
}

_syscall2(int, kevent_init, struct kevent_ring *, arg1, unsigned int, arg2);
_syscall4(int, kevent_ctl, int, arg1, unsigned int, argv2, unsigned int, argv3, void *, argv4);
_syscall6(int, kevent_get_events, int, arg1, unsigned int, argv2, unsigned int, argv3, struct timespec, argv4, void *, argv5, unsigned int, argv6);

#if 0
#define ulog(f, a...) fprintf(stderr, f, ##a)
#else
#define ulog(f, a...) do {} while (0)
#endif
#define ulog_err(f, a...) ulog(f ": %s [%d].\n", ##a, strerror(errno), errno)

typedef int (* kweb_callback_t)(struct ukevent *, int);

static int kevent_ctl_fd;
static int removed, total, imm;

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

static int kweb_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;
	}
	
	fcntl(s, F_SETFL, O_NONBLOCK);

	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 void usr1_handler(int signo)
{
	printf("removed: %d, total: %d, imm: %d.\n", removed, total, imm);
}

static int kweb_kevent_remove(struct ukevent *event)
{
	int err;

	removed++;

	err = kevent_ctl(kevent_ctl_fd, KEVENT_CTL_REMOVE, 1, event);
	if (err < 0) {
		ulog_err("Failed to perform control REMOVE operation");
		return err;
	}

	return err;
}
static int kweb_callback_main(struct ukevent *e, int im);
static int kweb_callback_client(struct ukevent *e, int immediately);

static int kweb_kevent_init(int fd, unsigned int events, kweb_callback_t callback)
{
	struct ukevent uk;

	uk.type = KEVENT_SOCKET;
	uk.event = events;
	uk.req_flags = KEVENT_REQ_ALWAYS_QUEUE | KEVENT_REQ_ET;
	uk.ptr = callback;
	uk.id.raw[0] = fd;

	return kevent_ctl(kevent_ctl_fd, KEVENT_CTL_ADD, 1, &uk);
}

static int kweb_callback_client(struct ukevent *e, int im)
{
	int err;
	int s = e->id.raw[0];

	err = server_handle_requst(s);

	if (!im)
		kweb_kevent_remove(e);
	return err;
}

static int kweb_callback_main(struct ukevent *e, int im)
{
	int cs, err;
	struct sockaddr_in csa;
	socklen_t addrlen = sizeof(struct sockaddr_in);

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

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

	fcntl(cs, F_SETFL, O_NONBLOCK);

	ulog("Accepted connect from %s:%d, qlen=%u.\n",
		inet_ntoa(csa.sin_addr), ntohs(csa.sin_port), e->ret_data[0]);

	total++;

	err = kweb_kevent_init(cs, KEVENT_SOCKET_RECV, &kweb_callback_client);
	if (err != 0)
		close(cs);

	return err;
}

static int kweb_kevent_wait(unsigned int wait_num)
{
	int num;
	struct ukevent *uk;
	kweb_callback_t func;
	int i;
	char buf[4096];
	struct timespec tm;

	tm.tv_sec = 10;
	tm.tv_nsec = 0;

	num = kevent_get_events(kevent_ctl_fd, 1, wait_num, tm, buf, 0);
	if (num < 0) {
		ulog_err("Failed to perform control operation");
		return num;
	}

	uk = (struct ukevent *)(buf);
	for (i=0; i<num; ++i) {
		func = uk[i].ptr;

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

	return 0;
}

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

	addr = "0.0.0.0";
	port = 80;
	wait_num = 1;
	ctl_file = "/dev/kevent";

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

	kevent_ctl_fd = kevent_init(NULL, 0);
	if (kevent_ctl_fd == -1) {
		ulog_err("Failed to create kevent queue");
		return -1;
	}

	signal(SIGPIPE, SIG_IGN);
	signal(SIGUSR1, usr1_handler);
	
	//fcntl(kevent_ctl_fd, F_SETFL, O_NONBLOCK);

	s = kweb_server_init(addr, port);
	if (s < 0)
		return s;

	err = kweb_kevent_init(s, KEVENT_SOCKET_RECV|KEVENT_SOCKET_ACCEPT, &kweb_callback_main);
	if (err < 0)
		goto err_out_exit;

	while (1) {
		err = kweb_kevent_wait(wait_num);
	}

err_out_exit:
	close(kevent_ctl_fd);
	return 0;
}
