#include "disk.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/stat.h>
#include <json-c/json.h>

Status CreateRootDisk(const char* path, uint64_t disk_size) {
	// Convert the size to a string
	char* size_str;
	Status status = Format(&size_str, "%lu", disk_size);
	if (status != SUCCESS)
		return status;

	// Create the disk
	int exit_code;
	// char* stdout;
	char* stderrb;

	status = RunExecutable(&exit_code, NULL, &stderrb, "/usr/bin/qemu-img", "create", "-f", "qcow2", path, size_str, NULL);

	free(size_str);

	// Check if the disk was created successfully
	if (status != SUCCESS)
		return status;

	if (exit_code != 0) {
		if (stderrb != NULL) {
			// Remove newlines from the stderr buffer
			for (int i = 0; stderrb[i] != '\0'; i++)
				if (stderrb[i] == '\n')
					stderrb[i] = ' ';

			Log(LOG_LEVEL_ERROR, "Failed to create disk at '%s' (%s).", path, stderrb);
			free(stderrb);
		} else
			Log(LOG_LEVEL_ERROR, "Failed to create disk");

		return FAILURE;
	}

	return SUCCESS;
}

Status CreateBackedDisk(const char* path, const char* backing_file) {
	// Create the disk
	int exit_code;
	// char* stdout;
	char* stderrb;

	Status status = RunExecutable(&exit_code, NULL, &stderrb, "/usr/bin/qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_file, path, NULL);

	// Check if the disk was created successfully
	if (status != SUCCESS)
		return status;

	if (exit_code != 0) {
		if (stderrb != NULL) {
			// Remove newlines from the stderr buffer
			for (int i = 0; stderrb[i] != '\0'; i++)
				if (stderrb[i] == '\n')
					stderrb[i] = ' ';

			Log(LOG_LEVEL_ERROR, "Failed to create disk at '%s' (%s).", path, stderrb);
			free(stderrb);
		} else
			Log(LOG_LEVEL_ERROR, "Failed to create disk");

		return FAILURE;
	}

	return SUCCESS;
}

Status TrimDisk(const char* path) {
	char* tmp_path = NULL;
	Status status = Format(&tmp_path, "%s.tmp", path);
	if (status != SUCCESS)
		return status;

	DiskInfo info;
	status = GetDiskInfo(path, &info);
	if (status != SUCCESS) {
		free(tmp_path);
		return status;
	}

	// Create the temporary disk
	int exit_code;
	// char* stdout;
	char* stderrb;

	if (info.backing_file != NULL) {
		char* backing_file_opt = NULL;
		status = Format(&backing_file_opt, "backing_file=%s", info.backing_file);
		if (status != SUCCESS) {
			free(tmp_path);
			FreeDiskInfo(&info);
			return status;
		}

		status = RunExecutable(&exit_code, NULL, &stderrb, "/usr/bin/qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_file_opt, path, tmp_path, NULL);

		free(backing_file_opt);
	} else
		status = RunExecutable(&exit_code, NULL, &stderrb, "/usr/bin/qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", path, tmp_path, NULL);

	FreeDiskInfo(&info);

	// Check if the disk was created successfully
	if (status != SUCCESS) {
		free(tmp_path);
		return status;
	}

	if (exit_code != 0) {
		if (stderrb != NULL) {
			// Remove newlines from the stderr buffer
			for (int i = 0; stderrb[i] != '\0'; i++)
				if (stderrb[i] == '\n')
					stderrb[i] = ' ';

			Log(LOG_LEVEL_ERROR, "Failed to create temporary disk at '%s' (%s).", tmp_path, stderrb);
			free(stderrb);
		} else
			Log(LOG_LEVEL_ERROR, "Failed to create temporary disk");

		free(tmp_path);
		return FAILURE;
	}

	// Try to move the temporary disk to the original path
	if (rename(tmp_path, path) != 0) {
		Log(LOG_LEVEL_ERROR, "Failed to move the temporary disk to the original path '%s' (%s).", path, strerror(errno));

		unlink(tmp_path);

		free(tmp_path);
		return FAILURE;
	}

	free(tmp_path);

	return SUCCESS;
}

Status RebaseDisk(const char* path, const char* backing_file) {
	// Create the disk
	int exit_code;
	// char* stdout;
	char* stderrb;

	Status status = RunExecutable(&exit_code, NULL, &stderrb, "/usr/bin/qemu-img", "rebase", "-f", "qcow2", "-F", "qcow2", "-u", "-b", backing_file, path, NULL);

	// Check if the disk was created successfully
	if (status != SUCCESS)
		return status;

	if (exit_code != 0) {
		if (stderrb != NULL) {
			// Remove newlines from the stderr buffer
			for (int i = 0; stderrb[i] != '\0'; i++)
				if (stderrb[i] == '\n')
					stderrb[i] = ' ';

			Log(LOG_LEVEL_ERROR, "Failed to rebase disk at '%s' (%s).", path, stderrb);
			free(stderrb);
		} else
			Log(LOG_LEVEL_ERROR, "Failed to rebase disk");

		return FAILURE;
	}

	return SUCCESS;
}

Status GetDiskInfo(const char* path, DiskInfo* _info) {
	*_info = (DiskInfo){0};

	// Get the disk info
	int exit_code;
	char* stdoutb;
	char* stderrb;

	Status status = RunExecutable(&exit_code, &stdoutb, &stderrb, "/usr/bin/qemu-img", "info", "--output", "json", path, NULL);
	if (status != SUCCESS)
		return status;

	if (exit_code != 0) {
		if (stderrb != NULL) {
			// Remove newlines from the stderr buffer
			for (int i = 0; stderrb[i] != '\0'; i++)
				if (stderrb[i] == '\n')
					stderrb[i] = ' ';

			Log(LOG_LEVEL_ERROR, "Failed to get disk info at '%s' (%s).", path, stderrb);
			free(stderrb);
		} else
			Log(LOG_LEVEL_ERROR, "Failed to get disk info");

		free(stdoutb);
		return FAILURE;
	}
	free(stderrb);

	// Parse the JSON output
	json_object* root = json_tokener_parse(stdoutb);
	free(stdoutb);

	if (root == NULL) {
		Log(LOG_LEVEL_ERROR, "Failed to parse the JSON output");
		return FAILURE;
	}

	json_object* virtual_size = NULL;
	json_object* actual_size = NULL;
	json_object* backing_file = NULL;

	json_object_object_get_ex(root, "virtual-size", &virtual_size);
	json_object_object_get_ex(root, "actual-size", &actual_size);
	json_object_object_get_ex(root, "backing-filename", &backing_file);

	if (virtual_size != NULL)
		_info->size = json_object_get_int64(virtual_size);
	if (actual_size != NULL)
		_info->allocated = json_object_get_int64(actual_size);
	if (backing_file != NULL)
		_info->backing_file = strdup(json_object_get_string(backing_file));

	json_object_put(root);

	if (_info->backing_file != NULL) {
		_info->backing_identifier = strdup(basename(_info->backing_file));
		if (_info->backing_identifier == NULL) {
			Log(LOG_LEVEL_ERROR, "Failed to duplicate the backing identifier (%s).", strerror(errno));
			FreeDiskInfo(_info);
			return FAILURE;
		}
	}

	return SUCCESS;
}

void FreeDiskInfo(DiskInfo* info) {
	free(info->backing_file);
	free(info->backing_identifier);
}