/*
 * vdev_block_info.c
 * Copyright (C) 2023  Aitor C.Z. <aitor_czr@gnuinos.org>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * See the COPYING file.
 */

/* gcc vdev_block_info.c -o vdev_block_info -lblkid */

#include <assert.h>
#include <blkid/blkid.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "common.h"
#include "libvdev/misc.h"

const char *progname = "vdev_block_info";

#define BUFSZ 64
#define DIM(x) (sizeof(x) / sizeof(*(x)))

// static const char     *sizes[]   = { "EiB", "PiB", "TiB", "GiB", "MiB",
// "KiB", "B" };
static const char *sizes[] = { "E", "P", "T", "G", "M", "K", "B" };
static const uint64_t exbibytes =
	1024ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL;

// function prototypres
bool vdev_blkid_utils_get_label(const char *device, char **buf);
char *vdev_blkid_utils_get_size(const char *device);

/**
 * \brief Convert a number of bytes into a file size
 */
static char *convert_size_into_string(uint64_t size)
{
	uint64_t multiplier = exbibytes;
	char *result = (char *)malloc(sizeof(char) * 20);

	if (!result) {
		fprintf(stderr, "Memory allocation failure\n");
		exit(EXIT_FAILURE);
	}

	for (size_t i = 0; i < DIM(sizes); i++, multiplier /= 1024) {
		if (size < multiplier)
			continue;
		if (size % multiplier == 0)
			sprintf(result, "%" PRIu64 "%s", size / multiplier, sizes[i]);
		else
			sprintf(result, "%.1f%s", (float)size / multiplier, sizes[i]);
		return result;
	}
	strncpy_t(result, sizeof(result), "0", strlen("0"));
	return result;
}

// print usage statement
static void config_usage(const char *progname)
{
	fprintf(stderr,
			"%s -- vdev_block_info\n\n"
			"Usage: %s [options]\n\n"
			"Options:\n"
			"   -h --help      this help\n"
			"   -D --device    select the device\n"
			"   -L --label     print label\n"
			"   -S --size      print size\n\n",
			progname, progname);
}

int main(int argc, char **argv)
{
	char *buf = NULL;
	bool get_size_ok = false;
	bool get_label_ok = false;

	static const struct option long_option[] = {
		{ "label",	no_argument,	   0, 'L' },
		{ "device", required_argument, 0, 'D' },
		{ "size",	no_argument,	   0, 'S' },
		{ "help",	no_argument,	   0, 'h' },
		{ 0,		0,				   0, 0	  },
	};

	buf = (char *)malloc(sizeof(char) * (BUFSZ + 1));
	if (!buf) {
		fprintf(stderr, "Memory allocation failure\n");
		exit(EXIT_FAILURE);
	}

	while (1) {
		int c;
		int option_index = 0;

		if ((c = getopt_long(argc, argv, "LD:Sh", long_option, &option_index)) < 0)
			break;

		switch (c) {
		case 'S':
			get_size_ok = true;
			break;
		case 'L':
			get_label_ok = true;
			break;
		case 'D': {
			char tmp[BUFSZ + 1];
			strncpy(tmp, optarg, sizeof(tmp) - 1);
			tmp[sizeof(tmp) - 1] = '\0';
			snprintf(buf, BUFSZ + 1, "%s", tmp);
			break;
		}
		case 'h':
			config_usage(progname);
			break;
		case '?':
			if (isprint(optopt))
				fprintf(stderr, "Unknown option `-%c'.\n", optopt);
			else
				fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
			return 1;
		default:
			abort();
		}   // switch
	}       // while

	if (*buf != 0) {
		if (get_size_ok) {
			char *size = NULL;
			size = vdev_blkid_utils_get_size(buf);
			if (size)
				printf("%s", size);
			else
				get_size_ok = false;
			free(size);
		}

		if (get_label_ok) {
			char *label = NULL;
			bool has_label = vdev_blkid_utils_get_label(buf, &label);
			if (has_label) {
				if (get_size_ok)
					printf(",");
				printf("%s", label);
			} else {
				get_label_ok = false;
			}
			free(label);
		}
	}

	free(buf);
	return 0;
}

// return value must be freed
bool vdev_blkid_utils_get_label(const char *device, char **buf)
{
	const char *label = NULL;
	blkid_probe pr = NULL;
	bool has_label_ok = false;

	*buf = (char *)malloc(sizeof(char) * (BUFSZ + 1));
	if (!*buf) {
		fprintf(stderr, "Memory allocation failure\n");
		exit(EXIT_FAILURE);
	}

	pr = blkid_new_probe_from_filename(device);
	if (!pr) {
		fprintf(stderr, "%s: Failed to open %s: %s\n", progname, device,
				strerror(errno));
		exit(EXIT_FAILURE);
	}

	blkid_do_probe(pr);
	blkid_probe_lookup_value(pr, "LABEL", &label, NULL);
	if (label)
		has_label_ok = true;
	snprintf(*buf, BUFSZ, "%s", label);
	blkid_free_probe(pr);
	return has_label_ok;
}

// return value must be freed
char *vdev_blkid_utils_get_size(const char *device)
{
	char *size = NULL;
	blkid_probe pr = NULL;
	blkid_loff_t sz;

	pr = blkid_new_probe_from_filename(device);
	if (!pr) {
		fprintf(stderr, "%s: Failed to open %s: %s\n", progname, device,
				strerror(errno));
		exit(EXIT_FAILURE);
	}

	blkid_do_probe(pr);
	sz = blkid_probe_get_size(pr);
	size = convert_size_into_string(sz);
	blkid_free_probe(pr);
	return size;
}
