#include "response.h"

#ifdef __NetBSD__
#ifndef PATH_MAX
#include <sys/syslimits.h>
#endif
#endif

#if defined(__sun) && defined(__illumos__)
#ifndef PATH_MAX
#include <limits.h>
#endif
#endif

#ifdef __linux__
#ifndef PATH_MAX
#include <linux/limits.h> /* for PATH_MAX */
#endif
#endif

#include <sys/stat.h>
#include <sys/types.h>

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#include "header.h"
#include "utils.h"

#define MAX_BUFFER_SIZE 1024

/*
 * Sends a given status line and headers to the client, given the parameters
 * of the original request, the main directory where the requested file is
 * supposed to be stored, and the integer status to send over.
 */
void
send_status_and_headers(REQUEST* request, int status)
{
	send_status(request, status);
	if (status >= 400)
	{
		if (write_error_header(request) != 0)
		{
			fprintf(stderr,
			        "[Debug] An error occured trying to write headers: %s\n",
			        strerror(errno));
		}
	}
	else if (status == 304)
	{
		if (write_304_header(request) != 0)
		{
			fprintf(stderr,
			        "[Debug] An error occured trying to write headers: %s\n",
			        strerror(errno));
		}
	}
	else
	{
		if (write_header(request) != 0)
		{
			fprintf(stderr,
			        "[Debug] An error occured trying to write headers: %s\n",
			        strerror(errno));
		}
	}
	if (write_crlf_to_request(request) != 0)
	{
		fprintf(stderr, "[Debug] Could not write CRLF to client.\n");
	}
	return;
}

void
send_status_and_headers_cgi(CGI* cgi, int status)
{
	send_status(cgi->http_request, status);
	if (write_cgi_header(cgi) != 0)
	{
		fprintf(stderr,
		        "[Debug] An error occured trying to write headers: %s\n",
		        strerror(errno));
	}
	if (write_crlf_to_request(cgi->http_request) != 0)
	{
		fprintf(stderr, "[Debug] Could not write CRLF to client.\n");
	}
	return;
}

/*
 * Sends directory entries. Makes faithful assumption that directory exists.
 */
int
send_dir_entries(REQUEST* request, bool headers_only)
{
	struct dirent** entries;
	int number_of_files;
	int status = 200;

	// we sort the dirs here
	number_of_files = scandir(request->parsed_path, &entries, NULL, alphasort);

	char* buffer = (char*)calloc(MAX_BUFFER_SIZE, sizeof(char));
	if (buffer == NULL)
	{
		status = 500;
		send_status_and_headers(request, status);
		fprintf(stderr, "[Debug] calloc failed: %s\n", strerror(errno));
		return status;
	}
	size_t buffer_size = MAX_BUFFER_SIZE;
	size_t buffer_len = 0;

	for (int i = 0; i < number_of_files; i++)
	{
		if (entries[i]->d_name[0] == '.')
		{
			free(entries[i]);
			continue;
		}

		printf("[Debug] dir: %s\n", entries[i]->d_name);
		int dir_name_length = strlen(entries[i]->d_name) + 1; // add 1 for \n

		if (buffer_len + dir_name_length > buffer_size)
		{
			/* see cgi.c, around line 300 for the explanation on this */
			char* new_buffer = realloc(buffer, buffer_size + MAX_BUFFER_SIZE);
			if (new_buffer == NULL)
			{
				status = 500;
				send_status_and_headers(request, status);
				fprintf(stderr, "[Debug] realloc failed: %s\n",
				        strerror(errno));
				free(buffer);
				for (int j = i; j < number_of_files; j++)
				{
					free(entries[i]);
				}
				return status;
			}

			buffer = new_buffer;
			memset(buffer + buffer_size, 0, MAX_BUFFER_SIZE);
			buffer_size += MAX_BUFFER_SIZE;
		}

		int len = snprintf(buffer + buffer_len, dir_name_length + 1, "%s\n",
		                   entries[i]->d_name);

		if (len < 0)
		{
			status = 500;
			send_status_and_headers(request, status);
			fprintf(stderr, "[Debug] realloc failed: %s\n", strerror(errno));
			free(buffer);
			for (int j = i; j < number_of_files; j++)
			{
				free(entries[i]);
			}
			return status;
		}
		buffer_len += len;

		free(entries[i]);
	}

	send_status(request, status);
	write_dir_header(request, buffer_len);
	write_crlf_to_request(request);

	if (!headers_only)
	{
		int bytes_written = safe_write(request->client_fd, buffer, buffer_len);

		if (bytes_written == -1)
		{
			fprintf(stderr, "[Debug] safe_write failed: %s\n", strerror(errno));
		}
	}

	free(buffer);
	return status;
}

/*
 * Attempts to serve a file located at the request's path in dir_arg. Sends
 * the appropriate status code and headers if so.
 *
 * Returns the status code of the response sent to the client.
 */
int
attempt_serve_main(REQUEST* request, bool headers_only)
{
	/*
	 * Much of the code here  was originally written by  Tristan, but re-adapted
	 * to ensure that status lines and header key-value pairs are written in the
	 * exact format expected by RFC 1945 (see Section 4).
	 */

	char buffer[4096]; /* Generally a good blocksize for most file systems. */
	ssize_t bytes_read = 0;

	errno = 0;
	int source_fd = -1;

	if (strlen(request->parsed_path) > PATH_MAX)
	{
		send_status_and_headers(request, 400);
		return 400;
	}

	if ((source_fd = open(request->parsed_path, O_RDONLY)) == -1)
	{
		if (errno == ENOENT)
		{
			send_status_and_headers(request, 404);
			return 404;
		}
		else
		{
			send_status_and_headers(request, 500);
			return 500;
		}
	}

	struct stat st;
	if (fstat(source_fd, &st) == -1)
	{
		close(source_fd);
		if (errno == ENOENT)
		{
			send_status_and_headers(request, 404);
			return 404;
		}
		else
		{
			send_status_and_headers(request, 500);
			return 500;
		}
	}

	if (request->if_modified_since_time != 0 &&
	    request->if_modified_since_time > st.st_mtime)
	{
		send_status_and_headers(request, 304);
		return 304;
	}

	if (S_ISDIR(st.st_mode))
	{
		(void)close(source_fd);
		return send_dir_entries(request, headers_only);
	}

	send_status_and_headers(request, 200);

	if (headers_only)
	{
		(void)close(source_fd);
		return 200;
	}

	while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0)
	{
		ssize_t bytes_written_per_read = 0;

		while (bytes_written_per_read < bytes_read)
		{
			/* this write is intentionally not our own safe_write */
			ssize_t bytesWritten =
				write(request->client_fd, (buffer + bytes_written_per_read),
			          (bytes_read - bytes_written_per_read));

			if (errno == EINTR)
			{
				continue; /* try again */
			}

			if (bytesWritten < 0)
			{
				/* Short writes are fine, not much we can do */
				return 200;
			}

			bytes_written_per_read += bytesWritten;
			request->response_size += bytesWritten;
		}
	}

	(void)close(source_fd);

	if (bytes_read < 0)
	{
		/* also don't think there's much we can do here */
		return 200;
	}

	return 200;
}