/* B01902062 藍ĉŒşç‘‹ */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "common.h"
#include "proc.h"

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void procconn(server* svr, request* req, int maxfd, struct timeval timeout) {

	fd_set rset = svr->readfds;
	fd_set wset = svr->writefds;
	fd_set eset = svr->exceptfds;
	struct timeval select_timeout = timeout;
	int rval;

	rval = select (maxfd, &rset, &wset, &eset, &select_timeout);

	if (rval < 0) {
		if (errno != EINTR)
			perror ("select");
		return;
	}

	for (int i = 0; i < maxfd; i++) {
		if (!FD_ISSET (i, &rset))
			continue;

		if (i == svr->listen_fd) {
			struct sockaddr_in client_addr;
			int client_len = sizeof (client_addr);
			int conn_fd = accept (i,
				(struct sockaddr*)&client_addr, (socklen_t*)&client_len);
			if (conn_fd < 0) {
				switch (errno) {
					case EINTR:
					case EAGAIN:
						continue;
					case EMFILE:
					case ENFILE:
						fprintf (stderr, "Too many open file ... "
							"(maxfd %d)\n", maxfd);
						continue;
					default:
						perror ("accept");
						continue;
				}
			} else {
				req[conn_fd].conn_fd = conn_fd;
				req[conn_fd].active = true;
				strcpy(req[conn_fd].host, inet_ntoa(client_addr.sin_addr));
				request_msg (&req[conn_fd], "accepted, waiting for filename");
				FD_SET (conn_fd, &(svr->readfds));
			}
		} else {
			rval = request_read(&req[i]);
			if (rval <= 0) {
				request_msg (&req[i], "socket read: %s",
					rval ? strerror (errno) : "connection closed");
				request_free (&req[i], svr);
			} else {
				if (req[i].header_done) {
					// ignore remaining chars in the buffer
					req[i].buf_len = 0;
					FD_CLR (req[i].conn_fd, &(svr->readfds));
					FD_SET (req[i].conn_fd, &(svr->writefds));
				}
			}
		}
	}

	for (int i = 0; i < maxfd; i++) {
		if (!FD_ISSET (i, &wset))
			continue;
		if (i == svr->listen_fd)
			continue;

		if (req[i].header_done) {
			if (!req[i].header_accept) {
				req[i].file_fd = open (req[i].filename, O_RDONLY);
				request_err (&req[i], "open", req[i].file_fd);
				if (req[i].file_fd < 0) {
					write (req[i].conn_fd, svr->reject_hdr, SVR_REJECT_HDR_LEN);
					request_msg (&req[i], "REJECT sent");
					request_free (&req[i], svr);
					continue;
				}

				struct stat file_stat;
				rval = fstat (req[i].file_fd, &file_stat);
				request_msg (&req[i], "fstat", rval);
				if (req[i].file_fd < 0) {
					write (req[i].conn_fd, svr->reject_hdr, SVR_REJECT_HDR_LEN);
					request_msg (&req[i], "REJECT sent");
					request_free (&req[i], svr);
					continue;
				}
				req[i].file_info = ftab_insert (
					svr->file_table,
					file_stat.st_dev,
					file_stat.st_ino,
					req[i].file_fd);

				struct flock lock_info = {
					.l_type = F_RDLCK,
					.l_whence = SEEK_SET,
					.l_start = 0,
					.l_len = 0
				};
				rval = fcntl (req[i].file_fd, F_SETLK, &lock_info);
				request_msg (&req[i], "lock", rval);
				if (rval < 0) {
					write (req[i].conn_fd, svr->reject_hdr, SVR_REJECT_HDR_LEN);
					request_msg (&req[i], "REJECT sent");
					request_free (&req[i], svr);
					continue;
				}
				request_msg (&req[i], "ACCEPT sent");
				write (req[i].conn_fd, svr->accept_hdr, SVR_ACCEPT_HDR_LEN);
				req[i].header_accept = true;
			} else {
				if (!req[i].buf_len) {
					rval = read (req[i].file_fd, req[i].buf, SVR_BUFFER_SIZE);
					if (rval < 0) {
						if (errno != EINTR &&
							errno != EAGAIN)
						{
							// fatal error, close the connection
							request_msg (&req[i], "read: %s", strerror (errno));
							request_free (&req[i], svr);
						}
						continue;
					} else if (rval == 0) {
						request_msg (&req[i], "transfer done, closing connection");
						request_free (&req[i], svr);
						continue;
					}

					req[i].buf_set = 0;
					req[i].buf_len = rval;
				}

				rval = write (req[i].conn_fd, req[i].buf + req[i].buf_set,
					req[i].buf_len);
				if (rval < 0) {
					request_msg (&req[i], "socket write: %s", strerror (errno));
					request_free (&req[i], svr);
					continue;
				}
				req[i].buf_set += rval;
				req[i].buf_len -= rval;
			}
		}
	}

	return;
}