#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "server.h"
#include "session.h"
#include "socktool.h"
#include "basic-list.h"

#include <errno.h>
#include <locale.h>
#include <time.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t quit_request = false;
static void server_quit_request_setter (int signo) {
	quit_request = true;
}

static void server_cleanup_child (int signo) {
	while (waitpid (-1, NULL, WUNTRACED | WNOHANG) > 0);
}

static void svrlist_fill_rset (List* svrlist, fd_set* rset, int* maxfd) {
	ListNode* iter;

	for (iter = list_node_front (svrlist); iter != NULL; iter = list_next (iter)) {
		RasServer* server = RAS_SERVER (list_data (iter));
		RasConn* conn = RAS_CONN (server);
		if (conn->fd_is_set) {
			FD_SET (conn->fd, rset);
			*maxfd = xmax (*maxfd, conn->fd) + 1;
		}
	}
}

static RasServer* svrlist_find_server (List* svrlist, int fd) {
	ListNode* iter;

	for (iter = list_node_front (svrlist); iter != NULL; iter = list_next (iter)) {
		RasServer* server = RAS_SERVER (list_data (iter));
		RasConn* conn = RAS_CONN (server);
		if (conn->fd_is_set && conn->fd == fd) {
			return server;
		}
	}

	return NULL;
}


int main (int argc, char* argv[]) {
	setlocale (LC_ALL, "");
	tzset ();

	if (argc < 2) {
		fprintf (stderr, "Usage: %s port\n", argv[0]);
		return 1;
	}

	struct sigaction action = {
		.sa_handler = SIG_IGN,
		.sa_flags = 0
	};
	sigemptyset (&action.sa_mask);
	sigaction (SIGPIPE, &action, NULL);
	sigaction (SIGHUP, &action, NULL);
	action.sa_handler = server_cleanup_child;
	sigaction (SIGCHLD, &action, NULL);
	action.sa_handler = server_quit_request_setter;
	sigaction (SIGINT, &action, NULL);
	sigaction (SIGQUIT, &action, NULL);
	sigaction (SIGTERM, &action, NULL);

	/* XXX 設定 standard I/O buffer mode,為後面的 shell 做準備 */
	setvbuf (stdin, NULL, _IOLBF, 0);
	setvbuf (stdout, NULL, _IOLBF, 0);
	setvbuf (stderr, NULL, _IONBF, 0);

	List* svrlist = list_create ();
	int sid = 0, cid = 0;
	RasServer svr;

	/* XXX Check before unlink */
	unlink ("/tmp/ras-server.sock");

	ras_server_init (&svr, RAS_CONN_PERM_ADMIN, AF_UNIX, NULL, sid), sid++;
	if (ras_server_listen (&svr, "/tmp/ras-server.sock", 0600) < 0) {
		return 1;
	} else {
		list_pushback (svrlist, &svr, sizeof (RasServer));
	}

	ras_server_init (&svr, RAS_CONN_PERM_RESTRICTED, AF_INET, NULL, sid), sid++;
	if (ras_server_listen (&svr, NULL, atoi (argv[1])) < 0) {
		return 1;
	} else {
		list_pushback (svrlist, &svr, sizeof (RasServer));
	}

	ras_server_init (&svr, RAS_CONN_PERM_RESTRICTED, AF_INET6, NULL, sid), sid++;
	if (ras_server_listen (&svr, NULL, atoi (argv[1])) < 0) {
		return 1;
	} else {
		list_pushback (svrlist, &svr, sizeof (RasServer));
	}

	int maxfd;
	fd_set rset, wset;

	while (!quit_request) {
		int rval;

		maxfd = 0;
		FD_ZERO (&rset);
		FD_ZERO (&wset);
		svrlist_fill_rset (svrlist, &rset, &maxfd);

		rval = select (maxfd, &rset, &wset, NULL, NULL);
		if (rval < 0) {
			continue;
		}

		for (int i = 0; i < maxfd; i++) {
			if (FD_ISSET (i, &rset)) {
				RasServer* server = svrlist_find_server (svrlist, i);
				if (server == NULL) {
					continue;
				}

				RasSession session;
				int session_fd;
				char* peername;
				pid_t cpid;

				session_fd = accept (i, NULL, NULL);
				if (session_fd < 0) {
					continue;
				}

				peername = ras_socktool_get_peername (session_fd);
				ras_server_log (server,
					"client %d from %s is accepted", cid, peername);
				free (peername);

				cpid = fork ();

				if (cpid < 0) {
					ras_server_log (server,
						"client %d cannot continue: fork error: %s",
						cid, strerror (errno));
					close (session_fd);
					ras_server_log (server,
						"client %d connection closed", cid);
					continue;
				} else if (cpid > 0) {
					/* parent process: just close the accepted fd */
					ras_server_log (server,
						"client %d is handled by process %u", cid, cpid);
					cid++;
					close (session_fd);
				} else {
					/* child process: we need to reset signal handlers */
					struct sigaction session_action = {
						.sa_handler = SIG_DFL,
						.sa_flags = 0
					};
					sigemptyset (&session_action.sa_mask);
					sigaction (SIGINT, &action, NULL);
					sigaction (SIGQUIT, &action, NULL);
					sigaction (SIGTERM, &action, NULL);
					/* detach from the controlling terminal */
					if (setsid () < 0) {
						exit (1);
					}
					if (ras_session_init (&session, server, session_fd, cid) < 0) {
						ras_session_destroy (&session);
						exit (1);
					}
					if (ras_session_read_header (&session) < 0) {
						ras_session_destroy (&session);
						exit (1);
					}
					if (ras_session_start_shell (&session) < 0) {
						ras_session_destroy (&session);
						exit (1);
					}
					ras_session_destroy (&session);
					exit (0);
				}
			}
		}


	}

	puts ("Shutting down all servers ...");

	for (ListNode* iter = list_node_front (svrlist);
		 iter != NULL; iter = list_next (iter))
	{
		ras_server_destroy (RAS_SERVER (list_data (iter)));
	}

	list_free (svrlist);

	puts ("Exited");

	return 0;
}