/*
**  OSSP uuid - Universally Unique Identifier
**  Copyright (c) 2004-2008 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2004-2008 The OSSP Project <http://www.ossp.org/>
**
**  This file is part of OSSP uuid, a library for the generation
**  of UUIDs which can found at http://www.ossp.org/pkg/lib/uuid/
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS 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 ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  uuid_cli.c: command line tool
*/

/* own headers */
#include "config.h"
#include "uuid.h"

/* system headers */
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#define max(a, b) ((a) > (b) ? (a) : (b))

#define USAGE                                                                                                        \
	"usage: %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV] [-v 1] [-m1]\n"                                           \
	"       %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV]  -v 3        uuid|{nil|max|ns:{DNS|URL|OID|X500}} data\n" \
	"       %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV]  -v 4\n"                                                  \
	"       %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV]  -v 5        uuid|{nil|max|ns:{DNS|URL|OID|X500}} data\n" \
	"       %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV]  -v 6\n"                                                  \
	"       %1$s [-n count] [-o outfile] [-r|-F BIN|STR|SIV]  -v 7\n"                                                  \
	"\n"                                                                                                               \
	"       %1$s -d         [-o outfile] [   -F     STR|SIV]              uuid\n"                                      \
	"       %1$s -d         [-o outfile] [-r|-F BIN|STR|SIV]              -\n"


/* main procedure */
int main(int argc, char * argv[]) {
	const char * altfile = NULL;
	size_t count         = 1;
	bool iterate         = false;         // not one at a time
	uuid_fmt_t fmt       = UUID_FMT_STR;  // default is ASCII output
	bool decode          = false;         // default is to encode
	unsigned int version = UUID_MAKE_V1;

	/* command line parsing */
	for(int ch; (ch = getopt(argc, argv, "1n:rF:dmo:v:")) != -1;) {
		switch(ch) {
			case '1':
				iterate = true;
				break;
			case 'n': {
				char * p;
				long long c = strtoll(optarg, &p, 10);
				if(*p != '\0' || (c < 1 && (errno = ERANGE)))
					return fprintf(stderr, "%s: -n %s: %s\n", argv[0], optarg, strerror(errno ?: EINVAL)), 1;
				count = c;
			} break;
			case 'r':
				fmt = UUID_FMT_BIN;
				break;
			case 'F':
				if(!strcasecmp(optarg, "bin"))
					fmt = UUID_FMT_BIN;
				else if(!strcasecmp(optarg, "str"))
					fmt = UUID_FMT_STR;
				else if(!strcasecmp(optarg, "siv"))
					fmt = UUID_FMT_SIV;
				else
					return fprintf(stderr, "%s: -F %s: unknown; may be any of {\"BIN\", \"STR\", \"SIV\"}\n", argv[0], optarg), 1;
				break;
			case 'd':
				decode = true;
				break;
			case 'o':
				altfile = optarg;
				break;
			case 'm':
				version |= UUID_MAKE_MC;
				break;
			case 'v': {
				char * p;
				long v = strtol(optarg, &p, 10);
				if(*p != '\0')
					return fprintf(stderr, "%s: -v %s: %s\n", argv[0], optarg, strerror(errno ?: EINVAL)), 1;

				version = version & UUID_MAKE_MC;
				switch(v) {
					case 1:
						version |= UUID_MAKE_V1;
						break;
					case 3:
						version |= UUID_MAKE_V3;
						break;
					case 4:
						version |= UUID_MAKE_V4;
						break;
					case 5:
						version |= UUID_MAKE_V5;
						break;
					case 6:
						version |= UUID_MAKE_V6;
						break;
					case 7:
						version |= UUID_MAKE_V7;
						break;
					default:
						return fprintf(stderr, "%s: -v %s: %s\n", argv[0], optarg, strerror(ERANGE)), 1;
				}
			} break;
			default:
				return fprintf(stderr, USAGE, argv[0]), 1;
		}
	}
	const char * self = argv[0];
	argv += optind;
	argc -= optind;

	if(altfile)
		if(!freopen(altfile, "w", stdout))
			return fprintf(stderr, "%s: -o %s: %s\n", self, optarg, strerror(ERANGE)), 1;

	uuid_t * uuid;
	uuid_rc_t rc;
	if((rc = uuid_create(&uuid)) != UUID_RC_OK)
		return fprintf(stderr, "%s: allocate: %s\n", self, uuid_error(rc)), 1;

	char buf[max(UUID_LEN_BIN, max(UUID_LEN_STR, UUID_LEN_SIV)) + 1];
	if(decode) {
		if(argc != 1)
			return fprintf(stderr, USAGE, self), 1;

		/* decoding */
		const char * source = argv[0];
		if(!strcmp(argv[0], "-")) {
			const char * lenname;
			size_t len = (fmt == UUID_FMT_BIN)   ? ((lenname = "BIN"), UUID_LEN_BIN)
			             : (fmt == UUID_FMT_STR) ? ((lenname = "STR"), UUID_LEN_STR)
			             : (fmt == UUID_FMT_SIV) ? ((lenname = "SIV"), UUID_LEN_SIV)
			                                     : (__builtin_unreachable(), 0);
			source     = buf;

			if(fread(buf, len, 1, stdin) != 1)
				return fprintf(stderr, "%s: -: read %zu (UUID_LEN_%s): %s\n", self, len, lenname, strerror(errno)), 1;
			buf[len] = '\0';
			if((rc = uuid_import(uuid, fmt, buf, len)) != UUID_RC_OK)
				return fprintf(stderr, "%s: -: import %s: %s\n", self, buf, uuid_error(rc)), 1;
		} else {
			if(fmt == UUID_FMT_BIN)
				return fprintf(stderr, "%s: -dF BIN: only acceptable with - (when reading from the standard input stream)\n", self), 1;
			else if(fmt == UUID_FMT_STR || fmt == UUID_FMT_SIV) {
				if((rc = uuid_import(uuid, fmt, argv[0], strlen(argv[0]))) != UUID_RC_OK)
					return fprintf(stderr, "%s: import %s: %s\n", self, argv[0], uuid_error(rc)), 1;
			}
		}
		void * vp = NULL;
		if((rc = uuid_export(uuid, UUID_FMT_TXT, &vp, NULL)) != UUID_RC_OK)
			return fprintf(stderr, "%s: export %s: %s\n", self, source, uuid_error(rc)), 1;
		fputs((char *)vp, stdout);
	} else {
		if((version & UUID_MAKE_V1 && argc != 0) ||  //
		   (version & UUID_MAKE_V3 && argc != 2) ||  //
		   (version & UUID_MAKE_V4 && argc != 0) ||  //
		   (version & UUID_MAKE_V5 && argc != 2) ||  //
		   (version & UUID_MAKE_V6 && argc != 0) ||  //
		   (version & UUID_MAKE_V7 && argc != 0))
			return fprintf(stderr, USAGE, self), 1;

		/* encoding */
		for(size_t i = 0; i < count; ++i) {
			if(iterate)
				assert(uuid_load(uuid, "nil") == UUID_RC_OK);

			if(version & UUID_MAKE_V3 || version & UUID_MAKE_V5) {
				uuid_t * uuid_ns;
				if((rc = uuid_create(&uuid_ns)) != UUID_RC_OK)
					return fprintf(stderr, "%s: allocate: %s\n", self, uuid_error(rc)), 1;
				if((rc = uuid_load(uuid_ns, argv[0])) != UUID_RC_OK)
					if((rc = uuid_import(uuid_ns, UUID_FMT_STR, argv[0], strlen(argv[0]))) != UUID_RC_OK)
						return fprintf(stderr, "%s: import %s: %s\n", self, argv[0], uuid_error(rc)), 1;

				if((rc = uuid_make(uuid, version, uuid_ns, argv[1])) != UUID_RC_OK)
					return fprintf(stderr, "%s: make UUID %zu/%zu (%s %s): %s\n", self, i + 1, count, argv[0], argv[1], uuid_error(rc)), 1;
				assert(uuid_destroy(uuid_ns) == UUID_RC_OK);
			} else {
				if((rc = uuid_make(uuid, version)) != UUID_RC_OK)
					return fprintf(stderr, "%s: make UUID %zu/%zu: %s\n", self, i + 1, count, uuid_error(rc)), 1;
			}
			size_t buflen = sizeof(buf);
			if((rc = uuid_export(uuid, fmt, &(void *){buf}, &buflen)) != UUID_RC_OK)
				return fprintf(stderr, "%s: export UUID %zu/%zu: %s\n", self, i + 1, count, uuid_error(rc)), 1;
			if(fmt == UUID_FMT_BIN)
				fwrite(buf, buflen, 1, stdout);
			else if(fmt == UUID_FMT_STR || fmt == UUID_FMT_SIV)
				puts(buf);
		}
	}
}
