#include "xml.h"

#include "pci.h"
#include "container.h"

#include <string.h>
#include <stdio.h>

result_t generate_domain_xml(char** _xml, const char* container, uint64_t memory, int vcpus, const char** pci_addresses, const char** iso_paths, int vnc_port, const char* vnc_password) {
	// Initialize the output parameters
	*_xml = NULL;

	// Generate the XML configuration for the PCI devices
	char* pci_xml;
	result_t result = generate_pci_xml(&pci_xml, pci_addresses);
	if (result != success())
		return result;

	// Generate the XML configuration for the ISO images
	char* iso_xml;
	result = generate_iso_xml(&iso_xml, iso_paths);
	if (result != success()) {
		free(pci_xml);
		return result;
	}

	// Generate the XML configuration for the VNC server
	char* vnc_xml;
	result = generate_vnc_xml(&vnc_xml, vnc_port, vnc_password);
	if (result != success()) {
		free(pci_xml);
		free(iso_xml);
		return result;
	}

	// Get the container path
	char* container_path;
	result = get_container_path(&container_path, container);
	if (result != success()) {
		free(pci_xml);
		free(iso_xml);
		free(vnc_xml);
		return result;
	}

	// Generate the XML configuration for the domain
	char* xml;
	result = format(&xml, "<domain type='kvm'>\n"
						  "<name>%s</name>\n"
						  "\n<!-- Resources -->\n"
						  "<memory unit='B'>%lu</memory>\n"
						  "<vcpu>%d</vcpu>\n"
						  "<cpu mode='host-passthrough'/>\n"
						  "\n<!-- OS -->\n"
						  "<os>\n"
						  "<type arch='x86_64' machine='q35'>hvm</type>\n"
						  "<bootmenu enable='no'/>\n"
						  "</os>\n"
						  "\n<!-- Features -->\n"
						  "<features>\n"
						  "<acpi/>\n"
						  "<apic/>\n"
						  "</features>\n"
						  "\n<!-- Clock -->\n"
						  "<clock offset='utc'>\n"
						  "<timer name='rtc' tickpolicy='catchup'/>\n"
						  "<timer name='pit' tickpolicy='delay'/>\n"
						  "<timer name='hpet' present='no'/>\n"
						  "</clock>\n"
						  "\n<!-- Behavior -->\n"
						  "<on_poweroff>destroy</on_poweroff>\n"
						  "<on_reboot>destroy</on_reboot>\n"
						  "<on_crash>destroy</on_crash>\n"
						  "<pm>\n"
						  "<suspend-to-mem enabled='yes'/>\n"
						  "<suspend-to-disk enabled='yes'/>\n"
						  "</pm>\n"
						  "\n<!-- Devices -->\n"
						  "<devices>\n"
						  "<emulator>/usr/bin/qemu-system-x86_64</emulator>\n"
						  "\n<!-- Disks -->\n"
						  "<disk type='file' device='disk'>\n"
						  "<driver name='qemu' type='qcow2'/>\n"
						  "<source file='%s'/>\n"
						  "<target dev='hda' bus='sata'/>\n"
						  "<boot order='1'/>\n"
						  "</disk>\n"
						  "\n<!-- PCI devices -->\n"
						  "%s"
						  "\n<!-- ISO images -->\n"
						  "%s"
						  "\n<!-- VNC server -->\n"
						  "%s"
						  "\n<!-- Misc -->\n"
						  "<watchdog model='itco' action='poweroff'/>\n"
						  "<memballoon model='none'/>\n"
						  "</devices>\n"
						  "</domain>\n",
					container, memory, vcpus, container_path, pci_xml, iso_xml, vnc_xml);
	free(pci_xml);
	free(iso_xml);
	free(vnc_xml);
	free(container_path);

	if (result != success())
		return result;

	// Return the result
	*_xml = xml;
	return success();
}

result_t generate_pci_xml(char** _xml, const char** pci_addresses) {
	// Initialize the output parameters
	*_xml = NULL;

	// Get the IOMMU group for each PCI address
	int* iommu_groups;
	int iommu_groups_count;
	result_t result = get_iommu_groups(&iommu_groups, &iommu_groups_count, pci_addresses);
	if (result != success())
		return result;

	// Get the PCI devices for each IOMMU group (the devices that should be passed through)
	char** pci_devices;
	result = get_iommu_groups_devices(&pci_devices, iommu_groups, iommu_groups_count);

	// Free the IOMMU groups as they are no longer needed
	free(iommu_groups);

	if (result != success())
		return result;

	// Generate the XML configuration for the PCI devices
	char* xml = strdup("");

	// For each PCI device, generate the XML configuration, and append it to the result
	for (int i = 0; pci_devices[i] != NULL; i++) {
		// Split the PCI address into domain, bus, device, and function
		int domain, bus, device, function;
		if (sscanf(pci_devices[i], "%04x:%02x:%02x.%x", &domain, &bus, &device, &function) != 4) {
			free(xml);
			for (int j = 0; pci_devices[j] != NULL; j++)
				free(pci_devices[j]);
			free(pci_devices);
			return failure("Failed to parse PCI address.");
		}

		// Generate the XML configuration for the PCI device
		char* device_xml;
		result = format(&device_xml, "<hostdev mode='subsystem' type='pci' managed='yes'>\n"
									 "<source>\n"
									 "<address domain='0x%04x' bus='0x%02x' slot='0x%02x' function='0x%x'/>\n"
									 "</source>\n"
									 "</hostdev>\n",
						domain, bus, device, function);
		if (result != success()) {
			free(xml);
			for (int j = 0; pci_devices[j] != NULL; j++)
				free(pci_devices[j]);
			free(pci_devices);
			return result;
		}

		// Append the new XML configuration to the result
		char* new_xml;
		result = format(&new_xml, "%s%s", xml, device_xml);
		free(device_xml);

		if (result != success()) {
			free(xml);
			for (int j = 0; pci_devices[j] != NULL; j++)
				free(pci_devices[j]);
			free(pci_devices);
			return result;
		}

		// Swap the old and new XML configurations
		free(xml);
		xml = new_xml;
	}

	// Free the PCI devices as they are no longer needed
	for (int i = 0; pci_devices[i] != NULL; i++)
		free(pci_devices[i]);
	free(pci_devices);

	// Return the result
	*_xml = xml;
	return success();
}

result_t generate_iso_xml(char** _xml, const char** iso_paths) {
	// Initialize the output parameters
	*_xml = NULL;

	// Generate the XML configuration for the ISO images
	char* xml = strdup("");

	// For each ISO image, generate the XML configuration, and append it to the result
	for (int i = 0; iso_paths[i] != NULL; i++) {
		if (i > 25) {
			free(xml);
			return failure("Too many ISO images.");
		}

		char* iso_xml;
		result_t result = format(&iso_xml, "<disk type='file' device='cdrom'>\n"
										   "<driver name='qemu' type='raw'/>\n"
										   "<source file='%s'/>\n"
										   "<target dev='hd%c' bus='sata'/>\n"
										   "<readonly/>\n"
										   "<boot order='%d'/>\n"
										   "</disk>\n",
								 iso_paths[i], 'b' + i, 2 + i); // hda is reserved for the main disk
		if (result != success()) {
			free(xml);
			return result;
		}

		// Append the new XML configuration to the result
		char* new_xml;
		result = format(&new_xml, "%s%s", xml, iso_xml);
		free(iso_xml);

		if (result != success()) {
			free(xml);
			return result;
		}

		// Swap the old and new XML configurations
		free(xml);
		xml = new_xml;
	}

	// Return the result
	*_xml = xml;
	return success();
}

result_t generate_vnc_xml(char** _xml, int vnc_port, const char* vnc_password) {
	// Initialize the output parameters
	*_xml = NULL;

	// Generate the XML configuration for the VNC server (if enabled)
	if (vnc_port != 0)
		return format(_xml, "<graphics type='vnc' port='%d' autoport='no' listen='0.0.0.0' passwd='%s'/>", vnc_port, vnc_password);

	return format(_xml, "");
}