/* 	$NetBSD$ */

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <prop/proplib.h>

static int
xstrtoul(char *str, u_long *val)
{
	char                    *end;
	int 			base = 10;
	u_long 			v;

	if (str[0] == '0')
		switch (str[1]) {
		case 'x':
			base = 16;
			str += 2;
			break;
		case 'd':
			base = 10;
			str += 2;
			break;
		case 'o':
			base = 8;
			str += 2;
			break;
		case 'b':
			base = 2;
			str += 2;
			break;
		}

	v = strtoul(str, &end, base);
	if (*end != '\0' || str[0] == '\0')
		return (EINVAL);

	*val = v;
	return (0);
}

static prop_object_t
prop_object_internalize_from_file(const char *path)
{
	prop_object_t 		po;

	/* XXX this dance is ugly, but we can't do better ATM */
	po = prop_dictionary_internalize_from_file(path);
	if (po == NULL) {
		po = prop_array_internalize_from_file(path);
		if (po == NULL)
			errx(1, "could not internalize '%s'", path);
	}

	return (po);
}

#define OPTIONS 		"b:ehlo:sI:O:"

static void
usage(void)
{
	const char 		*me = getprogname();

	printf("Usage: %s [options] file...\n", me);
	printf("       %s -e file0 file1...\n", me);
	printf("       %s -l\n", me);
	printf("-b size   \t Buffer size for -I\n");
	printf("-e        \t Pairwise compare plists\n");
	printf("-h        \t Print (this) help\n");
	printf("-l        \t Print available codecs\n");
	printf("-o file   \t Output file [stdout]\n");
	printf("-s        \t Silent operation\n");
	printf("-O format \t Output format\n");
	printf("-I format \t Input format when reading from stdin\n");
	exit(0);
}

int
main(int argc, char *argv[])
{
	u_char 			*buf;
	prop_parser_t 		parser;
	prop_codec_t 		codeco = NULL;
	prop_codec_t 		codeci = NULL;
	prop_object_t 		object;
	char 			*s;
	ssize_t 		len;
	int 			c, i, ret;
	int 			fdo = STDOUT_FILENO;
	int 			cmp = 0;
	u_long 			buflen = 8192;
	int 			silent = 0;

	codeci = codeco = prop_codec_lookup(NULL);
	if (codeco == NULL)
		errx(1, "could not find default codec");

	while ((c = getopt(argc, argv, OPTIONS)) != -1)
		switch (c) {
		case 'b': /* Buffer size for -I. */
			if (xstrtoul(optarg, &buflen))
				errx(1, "not a number '%s'", optarg);
			if (buflen == 0)
				errx(1, "buffer size must be nonzero");
			break;

		case 'e': /* Compare plists. */
			cmp = 1;
			break;

		case 'h': /* Help. */
			usage();
			/* UNREACHED */

		case 'l': /* List available codecs */
			printf("%s\n", prop_codec_list());
			return (0);

		case 'o': /* Output file. */
			fdo = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
			if (fdo == -1)
				errx(1, "could not open output '%s'", optarg);
			break;

		case 's': /* Silent operation. */
			silent = 1;
			break;

		case 'I': /* Input format. */
			codeci = prop_codec_lookup(optarg);
			if (codeci == NULL)
				errx(1, "could not find codec '%s'", optarg);
			break;

		case 'O': /* Output format. */
			codeco = prop_codec_lookup(optarg);
			if (codeco == NULL)
				errx(1, "could not find codec '%s'", optarg);
			break;

		case '?':
			errx(1, "uknown option -%c", optopt);
			/* UNREACHED */

		case ':':
			errx(1, "missing argument to -%c", optopt);
			/* UNREACHED */
		}

	argc -= optind;
	argv += optind;

	if (cmp) {
		prop_object_t 		po, pq;

		if (argc == 0 || (argc % 2) != 0)
			errx(1, "can only compare even number of plists");

		for (i = 0; i < argc; i += 2) {
			char 		*one = argv[i];
			char 		*two = argv[i + 1];

			po = prop_object_internalize_from_file(one);
			pq = prop_object_internalize_from_file(two);

			if (prop_object_equals(po, pq)) {
				if (! silent)
					printf("%s == %s\n", one, two);
			} else {
				if (! silent)
					printf("%s != %s\n", one, two);
				return (1);
			}

			prop_object_release(po);
			prop_object_release(pq);
		}
	} else {
		prop_object_t 		po;
		char 			*s;

		for (i = 0; i < argc; i++) {
			po = prop_object_internalize_from_file(argv[i]);

			s = prop_codec_externalize(codeco, po);
			if (s == NULL)
				errx(1, "could not externalize '%s'", argv[i]);

			if (write(fdo, s, strlen(s)) == -1)
				err(1, "could not write output");

			free(s);
			prop_object_release(po);
		}
	}

	/* If we were called to recode or compare files our job is done. */
	if (argc != 0)
		return (0);

	buf = malloc(buflen);
	if (buf == NULL)
		errx(1, "could not allocate input buffer");

	/* Otherwise we're supposed to read stdin. */
	ret = prop_parser_create(codeci, &parser);
	if (ret)
		errx(1, "could not create parser: %s", strerror(ret));

	while ((len = read(STDIN_FILENO, buf, buflen)) >= 0) {
		ret = prop_parser_exec(codeci, parser, buf, len);
		if (ret)
			errx(1, "%s", strerror(ret));

		while ((object = prop_parser_yield(codeci, parser)) != NULL) {
			s = prop_codec_externalize(codeco, object);
			if (s == NULL)
				errx(1, "could not externalize with codec");

			if (write(fdo, s, strlen(s)) == -1)
				err(1, "could not write output");

			free(s);
			prop_object_release(object);
		}

		if (len == 0)
			break;
	}

	prop_parser_destroy(codeci, parser);
	return (0);
}
