/*
 * server.c - TCP server.
 *
 * Accepts incoming connections and forks a dedicated process for each client.
 */

#include "server.h"
#include "http.h"
#include "log.h"
#include "ws.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>

/* Manages the connection for a single client in its own process. */
static void server_handle_client(int fd)
{
	struct ws_client *client;
	ssize_t n;
	bool handshake_done = false;

	client = ws_client_create(fd);
	if (!client) {
		LOG_ERR("ws_client_create: out of memory");
		close(fd);
		return;
	}

	while (1) {
		memset(client->buf, 0, WSD_READ_BUF_SIZE);

		n = read(client->fd, client->buf, WSD_READ_BUF_SIZE);
		if (n < 0) {
			if (errno == EINTR)
				continue;
			LOG_ERR("read(fd=%d): %s", client->fd, strerror(errno));
			break;
		}

		if (n == 0) {
			LOG_INFO("Client disconnected (fd=%d)", client->fd);
			break;
		}

		if (!handshake_done) {
			// Check for HTTP request
			if (memmem(client->buf, (size_t)n, "\r\n\r\n", 4) != NULL) {
				if (n < WSD_READ_BUF_SIZE)
					client->buf[n] = '\0';
				else
					client->buf[WSD_READ_BUF_SIZE - 1] = '\0';

				struct http_request req;

				if (http_request_init(&req) < 0)
					break;

				if (http_parse_request((char *)client->buf,
						       (size_t)n, &req) < 0) {
					http_request_free(&req);
					continue;
				}

				if (ws_handshake(client, &req) < 0) {
					http_request_free(&req);
					// Send HTTP 400 Response
					if (write(client->fd, "HTTP/1.1 400 Bad Request\r\n\r\n", 28) < 0) {
						break;
					}

					continue;
				}

				http_request_free(&req);
				handshake_done = true;
			}
		} else {
			if (ws_on_data(client, (size_t)n) < 0)
				break;
		}

		if (client->session && client->session->state == WS_STATE_CLOSED)
			break;
	}

	ws_client_destroy(client);
}

int server_init(struct server *srv, int port)
{
	struct sockaddr_in addr;
	int opt = 1;

	memset(srv, 0, sizeof(*srv));
	srv->port      = port;
	srv->listen_fd = -1;
	srv->running   = false;

	signal(SIGCHLD, SIG_IGN);

	// Create listening socket
	srv->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (srv->listen_fd < 0) {
		LOG_ERR("socket: %s", strerror(errno));
		return -1;
	}

	if (setsockopt(srv->listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
		LOG_ERR("setsockopt: %s", strerror(errno));
		goto fail;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family      = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port        = htons((uint16_t)port);

	if (bind(srv->listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		LOG_ERR("bind: %s", strerror(errno));
		goto fail;
	}

	if (listen(srv->listen_fd, SOMAXCONN) < 0) {
		LOG_ERR("listen: %s", strerror(errno));
		goto fail;
	}

	LOG_INFO("Server listening on port %d", port);
	return 0;

fail:
	server_cleanup(srv);
	return -1;
}

void server_run(struct server *srv)
{
	struct sockaddr_in addr;
	socklen_t addr_len = sizeof(addr);
	int fd;
	pid_t pid;

	srv->running = true;

	while (srv->running) {
		fd = accept(srv->listen_fd, (struct sockaddr *)&addr, &addr_len);
		if (fd < 0) {
			if (errno == EINTR)
				continue;
			LOG_ERR("accept: %s", strerror(errno));
			break;
		}

		LOG_INFO("New connection from %s:%d (fd=%d)",
			 inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fd);

		pid = fork();
		if (pid < 0) {
			LOG_ERR("fork: %s", strerror(errno));
			close(fd);
		} else if (pid == 0) {
			// Child process
			close(srv->listen_fd);
			server_handle_client(fd);
			exit(0);
		} else {
			// Parent process
			close(fd);
		}
	}
}

void server_stop(struct server *srv)
{
	srv->running = false;
}

void server_cleanup(struct server *srv)
{
	if (srv->listen_fd >= 0) {
		close(srv->listen_fd);
		srv->listen_fd = -1;
	}
}
