#include <icsneo/icsneoc2.h>

#include <stdio.h>
#include <inttypes.h>

/**
 * Prints an error message with the given string and error code.
 *
 * @param message The message to print.
 * @param error The error code to print.
 * @return error as int
 */
static int print_error_code(const char* message, icsneoc2_error_t error) {
	char error_str[64];
	size_t error_str_len = sizeof(error_str);
	icsneoc2_error_code_get(error, error_str, &error_str_len);
	printf("%s: \"%s\" (%u)\n", message, error_str, error);
	return (int)error;
}

/**
 * Progress callback invoked periodically during disk formatting.
 *
 * @param sectors_formatted Number of sectors formatted so far.
 * @param total_sectors Total number of sectors to format.
 * @param user_data Unused opaque pointer.
 * @return icsneoc2_disk_format_directive_continue to keep formatting.
 */
static icsneoc2_disk_format_directive_t format_progress(uint64_t sectors_formatted, uint64_t total_sectors, void* user_data) {
	(void)user_data;
	double pct = total_sectors > 0 ? (100.0 * (double)sectors_formatted / (double)total_sectors) : 0.0;
	printf("\r  Progress: %" PRIu64 " / %" PRIu64 " sectors  (%d%%)", sectors_formatted, total_sectors, (int)pct);
	fflush(stdout);
	return icsneoc2_disk_format_directive_continue;
}

int main() {
	/* Open the first available device (no online needed for formatting) */
	printf("Opening first available device...\n");
	icsneoc2_device_t* device = NULL;
	icsneoc2_error_t res = icsneoc2_device_open_first(0, ICSNEOC2_OPEN_OPTIONS_NONE, &device);
	if(res != icsneoc2_error_success) {
		return print_error_code("\tFailed to open first device", res);
	}

	/* Get a description of the opened device */
	char description[255] = {0};
	size_t description_length = sizeof(description);
	res = icsneoc2_device_description_get(device, description, &description_length);
	if(res != icsneoc2_error_success) {
		icsneoc2_device_free(device);
		return print_error_code("\tFailed to get device description", res);
	}
	printf("\tOpened device: %s\n", description);

	/* Check disk formatting support */
	bool supported = false;
	res = icsneoc2_device_supports_disk_formatting(device, &supported);
	if(res != icsneoc2_error_success) {
		print_error_code("\tFailed to check disk formatting support", res);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return -1;
	}
	if(!supported) {
		printf("\terror: %s does not support disk formatting\n", description);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return -1;
	}

	size_t disk_count = 0;
	icsneoc2_device_disk_count_get(device, &disk_count);
	printf("\tDisk count: %zu\n", disk_count);

	/* Query disk details */
	printf("\tQuerying disk details... ");
	fflush(stdout);
	icsneoc2_disk_details_t* details = NULL;
	res = icsneoc2_device_disk_details_get(device, &details);
	if(res != icsneoc2_error_success) {
		printf("FAIL\n");
		print_error_code("\tFailed to get disk details", res);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return -1;
	}
	printf("OK\n");

	/* Display current state */
	icsneoc2_disk_layout_t layout = 0;
	icsneoc2_disk_details_layout_get(details, &layout);
	printf("\t  Layout        : %s\n", layout == icsneoc2_disk_layout_raid0 ? "RAID0" : "Spanned");

	size_t detail_count = 0;
	icsneoc2_disk_details_count_get(details, &detail_count);
	for(size_t i = 0; i < detail_count; i++) {
		icsneoc2_disk_format_flags_t flags = 0;
		icsneoc2_disk_details_flags_get(details, i, &flags);
		printf("\t  Disk [%zu]:\n", i);
		printf("\t    Present     : %s\n", (flags & ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT) ? "yes" : "no");
		printf("\t    Initialized : %s\n", (flags & ICSNEOC2_DISK_FORMAT_FLAGS_INITIALIZED) ? "yes" : "no");
		printf("\t    Formatted   : %s\n", (flags & ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED) ? "yes" : "no");
		if(flags & ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT) {
			uint64_t sectors = 0, bps = 0;
			icsneoc2_disk_details_size_get(details, i, &sectors, &bps);
			printf("\t    Size        : %" PRIu64 " MB (%" PRIu64 " sectors x %" PRIu64 " bytes)\n",
				(sectors * bps) / (1024 * 1024), sectors, bps);
		}
	}

	/* Choose operation */
	printf("\n\tChoose operation:\n");
	printf("\t  [f] Format the disk(s)\n");
	printf("\t  [u] Update the disk configuration without formatting\n");
	printf("\t  [q] Quit\n");
	printf("\tSelection [f/u/q]: ");
	char choice[8] = {0};
	if(scanf("%7s", choice) != 1) {
		choice[0] = 'q';
	}

	if(choice[0] == 'u' || choice[0] == 'U') {
		/* Force a disk config update (e.g. changing the disk layout) without formatting.
		 * Unlike a format, this preserves the existing data on the disk(s). */
		printf("\n\tChoose disk layout:\n");
		printf("\t  [s] Spanned\n");
		printf("\t  [r] RAID0\n");
		printf("\tSelection [s/r] (current: %s): ", layout == icsneoc2_disk_layout_raid0 ? "RAID0" : "Spanned");
		char layout_choice[8] = {0};
		if(scanf("%7s", layout_choice) != 1) {
			layout_choice[0] = '\0';
		}
		if(layout_choice[0] == 'r' || layout_choice[0] == 'R') {
			icsneoc2_disk_details_layout_set(details, icsneoc2_disk_layout_raid0);
		} else if(layout_choice[0] == 's' || layout_choice[0] == 'S') {
			icsneoc2_disk_details_layout_set(details, icsneoc2_disk_layout_spanned);
		} else {
			printf("\tKeeping current layout.\n");
		}

		/* Enable/disable individual disks in the configuration. A disk's enabled
		 * state is carried by the FORMATTED flag; only present disks can be enabled. */
		for(size_t i = 0; i < detail_count; i++) {
			icsneoc2_disk_format_flags_t flags = 0;
			icsneoc2_disk_details_flags_get(details, i, &flags);
			if(!(flags & ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT)) {
				continue; /* Can't enable a disk that isn't present */
			}
			printf("\tEnable disk [%zu]? [y/N] (currently %s): ", i,
				(flags & ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED) ? "enabled" : "disabled");
			char disk_choice[8] = {0};
			if(scanf("%7s", disk_choice) != 1) {
				disk_choice[0] = '\0';
			}
			if(disk_choice[0] == 'y' || disk_choice[0] == 'Y') {
				flags |= ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED;
			} else {
				flags &= ~(icsneoc2_disk_format_flags_t)ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED;
			}
			icsneoc2_disk_details_flags_set(details, i, flags);
		}

		icsneoc2_disk_layout_t new_layout = 0;
		icsneoc2_disk_details_layout_get(details, &new_layout);
		printf("\n\tForcing disk config update on %s to %s layout (no data will be erased)...\n",
			description, new_layout == icsneoc2_disk_layout_raid0 ? "RAID0" : "Spanned");
		res = icsneoc2_device_force_disk_config_update(device, details);
		if(res != icsneoc2_error_success) {
			print_error_code("\tForce disk config update failed", res);
			icsneoc2_disk_details_free(details);
			icsneoc2_device_close(device);
			icsneoc2_device_free(device);
			return -1;
		}
		printf("\tDisk config update complete!\n");
		icsneoc2_disk_details_free(details);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return 0;
	}

	if(choice[0] != 'f' && choice[0] != 'F') {
		printf("\tAborted.\n");
		icsneoc2_disk_details_free(details);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return 0;
	}

	/* Build format config: mark present disks for formatting */
	bool any_present = false;
	for(size_t i = 0; i < detail_count; i++) {
		icsneoc2_disk_format_flags_t flags = 0;
		icsneoc2_disk_details_flags_get(details, i, &flags);
		if(flags & ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT) {
			flags |= ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED;
			icsneoc2_disk_details_flags_set(details, i, flags);
			any_present = true;
		}
	}
	icsneoc2_disk_details_full_format_set(details, false); /* Quick format */

	if(!any_present) {
		printf("\n\terror: no disks are present in the device\n");
		icsneoc2_disk_details_free(details);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return -1;
	}

	/* Confirm */
	printf("\n\tThis will format the disk(s) in %s.\n", description);
	printf("\tAll existing data will be lost. Continue? [y/N]: ");
	char confirm[8] = {0};
	if(scanf("%7s", confirm) != 1 || (confirm[0] != 'y' && confirm[0] != 'Y')) {
		printf("\tAborted.\n");
		icsneoc2_disk_details_free(details);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return 0;
	}

	/* Format */
	printf("\n\tStarting format...\n");
	res = icsneoc2_device_format_disk(device, details, format_progress, NULL);
	printf("\n"); /* newline after progress line */

	if(res != icsneoc2_error_success) {
		print_error_code("\tFormat failed", res);
		icsneoc2_disk_details_free(details);
		icsneoc2_device_close(device);
		icsneoc2_device_free(device);
		return -1;
	}
	printf("\tFormat complete!\n");
	icsneoc2_disk_details_free(details);

	/* Verify */
	printf("\n\tVerifying disk state after format... ");
	fflush(stdout);
	icsneoc2_disk_details_t* post_details = NULL;
	res = icsneoc2_device_disk_details_get(device, &post_details);
	if(res != icsneoc2_error_success) {
		printf("FAIL (could not re-query disk details)\n");
	} else {
		printf("OK\n");
		size_t post_count = 0;
		icsneoc2_disk_details_count_get(post_details, &post_count);
		for(size_t i = 0; i < post_count; i++) {
			icsneoc2_disk_format_flags_t flags = 0;
			icsneoc2_disk_details_flags_get(post_details, i, &flags);
			printf("\t  Disk [%zu] formatted: %s\n", i, (flags & ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED) ? "yes" : "no");
		}
		icsneoc2_disk_details_free(post_details);
	}

	printf("\tClosing device: %s...\n", description);
	icsneoc2_device_close(device);
	icsneoc2_device_free(device);
	return 0;
}
