#include "file.js.h"

#include "log.h"
#include "mem.h"
#include "task.h"
#include "trace.h"
#include "util.js.h"

#include "unzip.h"
#include "uv.h"

#include <stdbool.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#endif

enum
{
	k_file_read_max = 8 * 1024 * 1024
};

typedef struct _stat_t
{
	uv_fs_t request;
	tf_task_t* task;
	void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data);
	void* user_data;
	char path[];
} stat_t;

static void _file_stat_complete(uv_fs_t* request)
{
	stat_t* data = request->data;
	data->callback(data->task, data->path, request->result, &request->statbuf, data->user_data);
	uv_fs_req_cleanup(request);
	tf_free(data);
}

void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data)
{
	if (!path)
	{
		callback(task, path, -EINVAL, NULL, user_data);
		return;
	}

	const char* zip = tf_task_get_zip_path(task);
	size_t path_length = strlen(path) + 1;
	stat_t* data = tf_malloc(sizeof(stat_t) + path_length);
	*data = (stat_t) { .request = { .data = data }, .task = task, .callback = callback, .user_data = user_data };
	memcpy(data->path, path, path_length);

	int result = uv_fs_stat(tf_task_get_loop(task), &data->request, zip ? zip : path, _file_stat_complete);
	if (result)
	{
		callback(task, path, result, NULL, user_data);
		uv_fs_req_cleanup(&data->request);
		tf_free(data);
	}
}

typedef struct _read_t
{
	uv_work_t work;
	tf_task_t* task;
	void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data);
	void* user_data;
	int64_t result;
	char buffer[k_file_read_max];
	char path[];
} read_t;

static void _file_read_work(uv_work_t* work)
{
	read_t* data = work->data;
	const char* zip_path = tf_task_get_zip_path(data->task);
	if (zip_path)
	{
		tf_trace_t* trace = tf_task_get_trace(data->task);
		tf_trace_begin(trace, "file_read_zip_work");
		unzFile zip = unzOpen(zip_path);
		if (zip)
		{
			data->result = unzLocateFile(zip, data->path, 1);
			if (data->result == UNZ_OK)
			{
				unz_file_info64 info = { 0 };
				data->result = unzGetCurrentFileInfo64(zip, &info, NULL, 0, NULL, 0, NULL, 0);
				if (data->result == UNZ_OK && info.uncompressed_size > sizeof(data->buffer))
				{
					data->result = -EFBIG;
				}
				else if (data->result == UNZ_OK)
				{
					data->result = unzOpenCurrentFile(zip);
					if (data->result == UNZ_OK)
					{
						data->result = unzReadCurrentFile(zip, data->buffer, info.uncompressed_size);
						if (data->result == (int64_t)info.uncompressed_size)
						{
							int r = unzCloseCurrentFile(zip);
							if (r != UNZ_OK)
							{
								data->result = r;
							}
						}
						else
						{
							data->result = EAGAIN;
						}
					}
				}
			}
			unzClose(zip);
		}
		else
		{
			data->result = errno;
		}
		tf_trace_end(trace);
	}
	else
	{
		tf_trace_t* trace = tf_task_get_trace(data->task);
		tf_trace_begin(trace, "file_read_zip_work");
		uv_loop_t* loop = tf_task_get_loop(data->task);
		uv_fs_t open_req = { 0 };
		int open_result = uv_fs_open(loop, &open_req, data->path, UV_FS_O_RDONLY, 0, NULL);
		if (open_result >= 0)
		{
			uv_buf_t buf = { .base = data->buffer, .len = sizeof(data->buffer) };
			uv_fs_t read_req = { 0 };
			int result = uv_fs_read(loop, &read_req, open_result, &buf, 1, 0, NULL);
			if ((size_t)result >= sizeof(data->buffer))
			{
				data->result = -EFBIG;
			}
			else
			{
				data->result = result;
			}
			uv_fs_req_cleanup(&read_req);

			uv_fs_t close_req = { 0 };
			result = uv_fs_close(loop, &close_req, open_result, NULL);
			if (result && data->result >= 0)
			{
				data->result = result;
			}
			uv_fs_req_cleanup(&close_req);
		}
		else
		{
			data->result = errno;
		}
		uv_fs_req_cleanup(&open_req);
		tf_trace_end(trace);
	}
}

static void _file_read_after_work(uv_work_t* work, int result)
{
	read_t* data = work->data;
	data->callback(data->task, data->path, data->result, data->buffer, data->user_data);
	tf_free(data);
}

void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data)
{
	if (!path)
	{
		callback(task, path, -EINVAL, NULL, user_data);
		return;
	}

	size_t path_length = strlen(path) + 1;
	read_t* data = tf_malloc(sizeof(read_t) + path_length);
	memset(data, 0, sizeof(read_t));
	data->callback = callback;
	data->user_data = user_data;
	data->work.data = data;
	data->task = task;
	memcpy(data->path, path, path_length);
	int r = uv_queue_work(tf_task_get_loop(task), &data->work, _file_read_work, _file_read_after_work);
	if (r)
	{
		_file_read_after_work(&data->work, r);
	}
}
