/*
 * uuidparse.c --- Interpret uuid encoded information.  This program
 *	violates the UUID abstraction barrier by reaching into the
 *	guts of a UUID.
 *
 * Based on libuuid/src/uuid_time.c
 * Copyright (C) 1998, 1999 Theodore Ts'o.
 *
 * All alterations (C) 2017 Sami Kerola
 * The 3-Clause BSD License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#include <assert.h>
#include <getopt.h>
#include <libsmartcols.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <uuid.h>

#include "c.h"
#include "closestream.h"
#include "nls.h"
#include "optutils.h"
#include "strutils.h"
#include "timeutils.h"
#include "xalloc.h"

/* column IDs */
enum {
	COL_UUID = 0,
	COL_VARIANT,
	COL_TYPE,
	COL_TIME
};

/* column names */
struct colinfo {
	const char *name;	/* header */
	double whint;		/* width hint (N < 1 is in percent of termwidth) */
	int flags;		/* SCOLS_FL_* */
	const char *help;
};

/* columns descriptions */
static const struct colinfo infos[] = {
	[COL_UUID]    = {"UUID",    UUID_STR_LEN, 0, N_("unique identifier")},
	[COL_VARIANT] = {"VARIANT", 9,  0, N_("variant name")},
	[COL_TYPE]    = {"TYPE",    10, 0, N_("type name")},
	[COL_TIME]    = {"TIME",    31, 0, N_("timestamp")}
};

static int columns[ARRAY_SIZE(infos) * 2];
static size_t ncolumns;

struct control {
	unsigned int
		json:1,
		no_headings:1,
		raw:1;
};

static void __attribute__((__noreturn__)) usage(void)
{
	size_t i;

	fputs(USAGE_HEADER, stdout);
	fprintf(stdout, _(" %s [options] <uuid ...>\n"), program_invocation_short_name);

	fputs(USAGE_OPTIONS, stdout);
	puts(_(" -J, --json             use JSON output format"));
	puts(_(" -n, --noheadings       don't print headings"));
	puts(_(" -o, --output <list>    COLUMNS to display (see below)"));
	puts(_(" -r, --raw              use the raw output format"));
	printf(USAGE_HELP_OPTIONS(24));

	fputs(USAGE_COLUMNS, stdout);
	for (i = 0; i < ARRAY_SIZE(infos); i++)
		fprintf(stdout, " %8s  %s\n", infos[i].name, _(infos[i].help));

	printf(USAGE_MAN_TAIL("uuidparse(1)"));
	exit(EXIT_SUCCESS);
}

static int column_name_to_id(const char *name, size_t namesz)
{
	size_t i;

	assert(name);

	for (i = 0; i < ARRAY_SIZE(infos); i++) {
		const char *cn = infos[i].name;
		if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
			return i;
	}
	warnx(_("unknown column: %s"), name);
	return -1;
}

static int get_column_id(size_t num)
{
	assert(num < ncolumns);
	assert(columns[num] < (int)ARRAY_SIZE(infos));
	return columns[num];
}

static const struct colinfo *get_column_info(int num)
{
	return &infos[get_column_id(num)];
}

static void fill_table_row(struct libscols_table *tb, char const *const uuid)
{
	static struct libscols_line *ln;
	size_t i;
	uuid_t buf;
	int invalid = 0;
	int variant, type;

	assert(tb);
	assert(uuid);

	ln = scols_table_new_line(tb, NULL);
	if (!ln)
		errx(EXIT_FAILURE, _("failed to allocate output line"));

	if (uuid_parse(uuid, buf))
		invalid = 1;
	else {
		variant = uuid_variant(buf);
		type = uuid_type(buf);
	}

	for (i = 0; i < ncolumns; i++) {
		char *str = NULL;

		switch (get_column_id(i)) {
		case COL_UUID:
			str = xstrdup(uuid);
			break;
		case COL_VARIANT:
			if (invalid) {
				str = xstrdup(_("invalid"));
				break;
			}
			switch (variant) {
			case UUID_VARIANT_NCS:
				str = xstrdup("NCS");
				break;
			case UUID_VARIANT_DCE:
				str = xstrdup("DCE");
				break;
			case UUID_VARIANT_MICROSOFT:
				str = xstrdup("Microsoft");
				break;
			default:
				str = xstrdup(_("other"));
			}
			break;
		case COL_TYPE:
			if (invalid) {
				str = xstrdup(_("invalid"));
				break;
			}
			switch (type) {
			case 0:
				if (strspn(uuid, "0-") == 36)
					str = xstrdup(_("nil"));
				else
					str = xstrdup(_("unknown"));
				break;
			case 1:
				str = xstrdup(_("time-based"));
				break;
			case 2:
				str = xstrdup("DCE");
				break;
			case 3:
				str = xstrdup(_("name-based"));
				break;
			case 4:
				str = xstrdup(_("random"));
				break;
			case 5:
				str = xstrdup(_("sha1-based"));
				break;
			default:
				str = xstrdup(_("unknown"));
			}
			break;
		case COL_TIME:
			if (invalid) {
				str = xstrdup(_("invalid"));
				break;
			}
			if (variant == UUID_VARIANT_DCE && type == 1) {
				struct timeval tv;
				char date_buf[ISO_BUFSIZ];

				uuid_time(buf, &tv);
				strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA,
					       date_buf, sizeof(date_buf));
				str = xstrdup(date_buf);
			}
			break;
		default:
			abort();
		}
		if (str && scols_line_refer_data(ln, i, str))
			errx(EXIT_FAILURE, _("failed to add output data"));
	}
}

static void print_output(struct control const *const ctrl, int argc,
			 char **argv)
{
	struct libscols_table *tb;
	size_t i;

	scols_init_debug(0);
	tb = scols_new_table();
	if (!tb)
		err(EXIT_FAILURE, _("failed to allocate output table"));

	if (ctrl->json) {
		scols_table_enable_json(tb, 1);
		scols_table_set_name(tb, "uuids");
	}
	scols_table_enable_noheadings(tb, ctrl->no_headings);
	scols_table_enable_raw(tb, ctrl->raw);

	for (i = 0; i < ncolumns; i++) {
		const struct colinfo *col = get_column_info(i);

		if (!scols_table_new_column(tb, col->name, col->whint,
					    col->flags))
			err(EXIT_FAILURE,
			    _("failed to initialize output column"));
	}

	for (i = 0; i < (size_t) argc; i++)
		fill_table_row(tb, argv[i]);

	if (i == 0) {
		char uuid[UUID_STR_LEN];

		while (scanf(" %36[^ \t\n]%*c", uuid) && !feof(stdin))
			fill_table_row(tb, uuid);
	}
	scols_print_table(tb);
	scols_unref_table(tb);
}

int main(int argc, char **argv)
{
	struct control ctrl = { 0 };
	char *outarg = NULL;
	int c;

	static const struct option longopts[] = {
		{"json",       no_argument,       NULL, 'J'},
		{"noheadings", no_argument,       NULL, 'n'},
		{"output",     required_argument, NULL, 'o'},
		{"raw",        no_argument,       NULL, 'r'},
		{"version",    no_argument,       NULL, 'V'},
		{"help",       no_argument,       NULL, 'h'},
		{NULL, 0, NULL, 0}
	};
	static const ul_excl_t excl[] = {
		{'J', 'r'},
		{0}
	};
	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	atexit(close_stdout);

	while ((c = getopt_long(argc, argv, "Jno:rVh", longopts, NULL)) != -1) {
		err_exclusive_options(c, longopts, excl, excl_st);
		switch (c) {
		case 'J':
			ctrl.json = 1;
			break;
		case 'n':
			ctrl.no_headings = 1;
			break;
		case 'o':
			outarg = optarg;
			break;
		case 'r':
			ctrl.raw = 1;
			break;
		case 'V':
			printf(UTIL_LINUX_VERSION);
			return EXIT_SUCCESS;
		case 'h':
			usage();
		default:
			errtryhelp(EXIT_FAILURE);
		}
	}
	argc -= optind;
	argv += optind;

	columns[ncolumns++] = COL_UUID;
	columns[ncolumns++] = COL_VARIANT;
	columns[ncolumns++] = COL_TYPE;
	columns[ncolumns++] = COL_TIME;

	if (outarg
	    && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
				     &ncolumns, column_name_to_id) < 0)
		return EXIT_FAILURE;

	print_output(&ctrl, argc, argv);

	return EXIT_SUCCESS;
}
