/*
* iconv.c
* Implementation of SUSv4 XCU iconv utility
* Copyright © 2011 Rich Felker
* Licensed under the terms of the GNU General Public License, v2 or later
*/

#include <stdlib.h>
#include <stdio.h>
#include <iconv.h>
#include <locale.h>
#include <langinfo.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv)
{
       const char *from=0, *to=0;
       int b;
       iconv_t cd;
       char buf[BUFSIZ];
       char outbuf[BUFSIZ*4];
       char *in, *out;
       size_t inb;
       size_t l;
       size_t unitsize=0;
       int err=0;
       FILE *f;

       while ((b = getopt(argc, argv, "f:t:csl")) != EOF) switch(b) {
       case 'l':
               puts("UTF-8, UTF-16BE, UTF-16LE, UTF-32BE, UTF32-LE, UCS-2BE, UCS-2LE, WCHAR_T,\n"
                       "US_ASCII, ISO8859-1, ISO8859-2, ISO8859-3, ISO8859-4, ISO8859-5,\n"
                       "ISO8859-6, ISO8859-7, ...");
               exit(0);
       case 'c': case 's': break;
       case 'f': from=optarg; break;
       case 't': to=optarg; break;
       default: exit(1);
       }

       if (!from || !to) {
               setlocale(LC_CTYPE, "");
               if (!to) to = nl_langinfo(CODESET);
               if (!from) from = nl_langinfo(CODESET);
       }
       cd = iconv_open(to, from);
       if (cd == (iconv_t)-1) {
               if (iconv_open(to, "WCHAR_T") == (iconv_t)-1)
                       fprintf(stderr, "iconv: destination charset %s: ", to);
               else
                       fprintf(stderr, "iconv: source charset %s: ", from);
               perror("");
               exit(1);
       }
       if (optind == argc) argv[argc++] = "-";

       for (; optind < argc; optind++) {
               if (argv[optind][0]=='-' && !argv[optind][1]) {
                       f = stdin;
                       argv[optind] = "(stdin)";
               } else if (!(f = fopen(argv[optind], "rb"))) {
                       fprintf(stderr, "iconv: %s: ", argv[optind]);
                       perror("");
                       err = 1;
                       continue;
               }
               inb = 0;
               for (;;) {
                       in = buf;
                       out = outbuf;
                       l = fread(buf+inb, 1, sizeof(buf)-inb, f);
                       inb += l;
                       if (!inb) break;
                       if (iconv(cd, &in, &inb, &out, (size_t [1]){sizeof outbuf})==-1
                        && errno == EILSEQ) {
                               if (!unitsize) {
                                       wchar_t wc='0';
                                       char dummy[4], *dummyp=dummy;
                                       iconv_t cd2 = iconv_open(from, "WCHAR_T");
                                       if (cd == (iconv_t)-1) {
                                               unitsize = 1;
                                       } else {
                                               iconv(cd2,
                                                       (char *[1]){(char *)&wc},
                                                       (size_t[1]){1},
                                                       &dummyp, (size_t[1]){4});
                                               unitsize = dummyp-dummy;
                                               if (!unitsize) unitsize=1;
                                       }
                               }
                               inb-=unitsize;
                               in+=unitsize;
                       }
                       if (inb && !l && errno==EINVAL) break;
                       if (out>outbuf && !fwrite(outbuf, out-outbuf, 1, stdout)) {
                               perror("iconv: write error");
                               exit(1);
                       }
                       if (inb) memmove(buf, in, inb);
               }
               if (ferror(f)) {
                       fprintf(stderr, "iconv: %s: ", argv[optind]);
                       perror("");
                       err = 1;
               }
       }
       return err;
}