/*      $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);
}