#include "lib_acl.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#if !defined(_WIN32) && !defined(_WIN64)
#include <unistd.h>
#include <poll.h>
#endif
#include "fiber/libfiber.h"

#if defined(_WIN32) || defined(_WIN64)
//# define POLL	WSAPoll
# define POLL	acl_fiber_poll
# define CLOSE	acl_fiber_close
# define LISTEN	acl_fiber_listen
# define ACCEPT	acl_fiber_accept
# define snprintf _snprintf
#else
# define SOCKET	int
# define INVALID_SOCKET -1
# define POLL	poll
# define CLOSE	close
# define LISTEN	listen
# define ACCEPT accept
#endif

static int  __nconnect = 0;
static int  __count = 0;
static int  __socket_count = 0;
static char __listen_ip[64];
static int  __listen_port = 9001;
static int  __listen_qlen = 64;
static int  __read_timeout = -1;
static int  __write_timeout = -1;
static int  __echo_data  = 1;
static int  __use_sockopt = 0;

static int check_read(SOCKET fd, int timeout)
{
	struct pollfd pfd;
	int n;

	memset(&pfd, 0, sizeof(struct pollfd));
	pfd.fd = fd;
	pfd.events = POLLIN;

	n = POLL(&pfd, 1, timeout);
	if (n < 0) {
		printf("poll error: %s\r\n", acl_last_serror());
		return -1;
	}

	if (n == 0) {
		return 0;
	}
	if (pfd.revents & POLLIN) {
		return 1;
	} else {
		printf(">>>poll return n=%d read no ready,fd=%d, pfd=%p\n", n, (int) fd, &pfd);
		return 0;
	}
}

static int check_write(SOCKET fd, int timeout)
{
	struct pollfd pfd;
	int n;

	memset(&pfd, 0, sizeof(struct pollfd));
	pfd.fd = fd;
	pfd.events = POLLOUT;

	n = POLL(&pfd, 1, timeout);
	if (n < 0) {
		printf("poll error: %s\r\n", acl_last_serror());
		return -1;
	}

	if (n == 0) {
		return 0;
	}
	if (pfd.revents & POLLOUT) {
		return 1;
	} else {
		printf(">>>poll return n=%d write no ready,fd=%d, pfd=%p\n", n, (int) fd, &pfd);
		return 0;
	}
}

static void echo_client(ACL_FIBER *fiber acl_unused, void *ctx)
{
	SOCKET  *pfd = (SOCKET *) ctx;
	SOCKET   fd  = *pfd;
	char  buf[8192];
	int   ret;

	__socket_count++;
	//printf("client fiber-%d: fd: %d\r\n", acl_fiber_self(), fd);

	if (__read_timeout > 0 && __use_sockopt) {
		struct timeval tm;
		tm.tv_sec = __read_timeout;
		tm.tv_usec = 0;

#if defined(__APPLE__) || defined(_WIN32) || defined(_WIN64)
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
			(char*) &__read_timeout, sizeof(__read_timeout)) < 0) {
#else
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)) < 0) {
#endif
			printf("%s: setsockopt error: %s\r\n", __FUNCTION__, acl_last_serror());
		}
	}

	if (__write_timeout > 0 && __use_sockopt) {
		struct timeval tm;
		tm.tv_sec = __write_timeout;
		tm.tv_usec = 0;

#if defined(__APPLE__) || defined(_WIN32) || defined(_WIN64)
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
			(char*) &__write_timeout, sizeof(__write_timeout)) < 0) {
#else
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)) < 0) {
#endif
			printf("%s: setsockopt error: %s\r\n", __FUNCTION__, acl_last_serror());
		}
	}

	while (1) {
		if (__read_timeout > 0 && !__use_sockopt) {
			ret = check_read(fd, __read_timeout * 1000);
			if (ret < 0) {
				break;
			}
			if (ret == 0) {
				printf("read timeout fd=%u\r\n", (int) fd);
				break;
			}
		}

#if defined(_WIN32) || defined(_WIN64)
		ret = acl_fiber_recv(fd, buf, sizeof(buf) - 1, 0);
#else
		ret = read(fd, buf, sizeof(buf) - 1);
#endif
		if (ret == 0) {
			printf("read close by peer fd: %d, %s\r\n",
				(int) fd, acl_last_serror());
			break;
		} else if (ret < 0) {
			if (acl_last_error() == EINTR) {
				printf("catch a EINTR signal\r\n");
				continue;
			}

			printf("read error %s, fd: %u\n", acl_last_serror(), (int) fd);
			break;
		}

		//buf[ret] = 0; printf("buf=%s\r\n", buf);
		__count++;

		if (!__echo_data) {
			continue;
		}

		if (__write_timeout > 0 && !__use_sockopt) {
			int n = check_write(fd, __write_timeout * 1000);
			if (n < 0) {
				break;
			}
			if (n == 0) {
				printf("write wait timeout, fd=%d\r\n", (int) fd);
				break;
			}
		}

#if defined(_WIN32) || defined(_WIN64)
		if (acl_fiber_send(fd, buf, ret, 0) < 0) {
#else
		if (write(fd, buf, ret) < 0) {
#endif
			if (errno == EINTR) {
				continue;
			}
			printf("write error, fd: %d\r\n", (int) fd);
			break;
		}
	}

	__socket_count--;
	printf("%s: close %d, socket_count=%d\r\n", __FUNCTION__, (int) fd, __socket_count);
	CLOSE(fd);
	free(pfd);

	if (--__nconnect == 0) {
		printf("\r\n----total read/write: %d----\r\n", __count);
		__count = 0;
	}
}

static void fiber_accept(ACL_FIBER *fiber acl_unused, void *ctx)
{
	ACL_FIBER_ATTR *attr = (ACL_FIBER_ATTR*) ctx;
	SOCKET lfd;
	int on = 1;
	struct sockaddr_in sa;

	assert(attr);

	memset(&sa, 0, sizeof(sa));
	sa.sin_family      = AF_INET;
	sa.sin_port        = htons(__listen_port);
	sa.sin_addr.s_addr = inet_addr(__listen_ip);

	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if (lfd == INVALID_SOCKET)
		abort();

#if defined(_WIN32) || defined(_WIN64)
	if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on))) {
#else
	if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
#endif
		printf("setsockopt error %s\r\n", acl_last_serror());
		exit (1);
	}

	if (bind(lfd, (struct sockaddr *) &sa, sizeof(struct sockaddr)) < 0) {
		printf("bind error %s\r\n", acl_last_serror());
		exit (1);
	}

	if (LISTEN(lfd, 128) < 0) {
		printf("listen error %s\r\n", acl_last_serror());
		exit (1);
	}

	printf("fiber-%d listen %s:%d ok\r\n",
		acl_fiber_self(), __listen_ip, __listen_port);

	for (;;) {
		int len = sizeof(sa);
		SOCKET *pfd;
		SOCKET cfd = ACCEPT(lfd, (struct sockaddr *)& sa, (socklen_t *) &len);
		if (cfd == INVALID_SOCKET) {
			printf("accept error %s\r\n", acl_last_serror());
			break;
		}

		pfd = (SOCKET *) malloc(sizeof(SOCKET));
		assert(pfd != NULL);
		*pfd = cfd;

		__nconnect++;
		printf("fiber-%d: accept one, fd: %d, %p\r\n",
			acl_fiber_self(), (int) cfd, pfd);
		acl_fiber_create2(attr, echo_client, pfd);
	}

	CLOSE(lfd);
	exit(0);
}

//#define SCHEDULE_AUTO

#ifndef	SCHEDULE_AUTO
static void fiber_memcheck(ACL_FIBER *fiber acl_unused, void *ctx acl_unused)
{
	while (1) {
#if defined(_WIN32) || defined(_WIN64)
		acl_fiber_delay(1000);
#else
		sleep(1);
#endif
		acl_default_meminfo();
		acl_fiber_memstat();
	}
}
#endif

static void usage(const char *procname)
{
	printf("usage: %s -h [help]\r\n"
		" -e event_mode [kernel|select|poll|io_uring]\r\n"
		" -s listen_ip\r\n"
		" -p listen_port\r\n"
		" -r read_timeout\r\n"
		" -w write_timeout\r\n"
		" -q listen_queue\r\n"
		" -z stack_size\r\n"
		" -D [if using directly event mode, default: 0]\r\n"
		" -K [if using event keep IO mode, default: 0]\r\n"
		" -O [if using epoll event oneshot mode, default: 0]\r\n"
		" -Z [if use shared stack]\r\n"
		" -T [if use setsockopt to set IO timeout]\r\n"
		" -S [if using single IO, default: no]\r\n", procname);
}

int main(int argc, char *argv[])
{
	int   ch, event_mode = FIBER_EVENT_KERNEL;
	ACL_FIBER_ATTR fiber_attr;
	int   stack_size = 320000;
	int   directly = 0, keepio = 0, oneshot = 0;

	acl_fiber_attr_init(&fiber_attr);
	snprintf(__listen_ip, sizeof(__listen_ip), "%s", "127.0.0.1");

	while ((ch = getopt(argc, argv, "hs:p:r:w:q:Sz:Ze:TDKO")) > 0) {
		switch (ch) {
		case 'h':
			usage(argv[0]);
			return 0;
		case 's':
			snprintf(__listen_ip, sizeof(__listen_ip), "%s", optarg);
			break;
		case 'p':
			__listen_port = atoi(optarg);
			break;
		case 'r':
			__read_timeout = atoi(optarg);
			break;
		case 'w':
			__write_timeout = atoi(optarg);
			break;
		case 'q':
			__listen_qlen = atoi(optarg);
			break;
		case 'S':
			__echo_data = 0;
			break;
		case 'z':
			stack_size = atoi(optarg);
			acl_fiber_attr_setstacksize(&fiber_attr, stack_size);
			break;
		case 'Z':
			acl_fiber_attr_setsharestack(&fiber_attr, 1);
			break;
		case 'T':
			__use_sockopt = 1;
			break;
		case 'D':
			directly = 1;
			break;
		case 'K':
			keepio = 1;
			break;
		case 'O':
			oneshot = 1;
			break;
		case 'e':
			if (strcasecmp(optarg, "select") == 0) {
				event_mode = FIBER_EVENT_SELECT;
			} else if (strcasecmp(optarg, "poll") == 0) {
				event_mode = FIBER_EVENT_POLL;
			} else if (strcasecmp(optarg, "io_uring") == 0) {
				event_mode = FIBER_EVENT_IO_URING;
			}
			break;
		default:
			break;
		}
	}

#if !defined(_WIN32) && !defined(_WIN64)
	signal(SIGPIPE, SIG_IGN);
#endif
	acl_lib_init();
	acl_msg_stdout_enable(1);
	acl_fiber_msg_stdout_enable(1);
	acl_fiber_event_directly(directly);
	acl_fiber_event_keepio(keepio);
	acl_fiber_event_oneshot(oneshot);

#ifdef	SCHEDULE_AUTO
	acl_fiber_schedule_init(1);
	acl_fiber_schedule_set_event(event_mode);
#endif

	printf("%s: call fiber_creater\r\n", __FUNCTION__);
	acl_fiber_create(fiber_accept, &fiber_attr, 327680);

#ifndef	SCHEDULE_AUTO
	acl_fiber_create(fiber_memcheck, NULL, 640000);

	printf("call fiber_schedule\r\n");
	acl_fiber_schedule_with(event_mode);
#endif

	return 0;
}
