/*-
 * Copyright (c)2007 Citrus Project,
 * All rights reserved.
 *
 * 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, this list of conditions and the following disclaimer.
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS 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 AUTHOR OR 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.
 *
 */

#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <iconv.h>
#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
static int silent = 0;

typedef struct {
	iconv_t cd;
	size_t inbufsize, outbufsize;
	char *inbuf, *outbuf;
} converter_t;

static __inline void
converter_init(converter_t *p, const char *from, const char *to)
{
	p->inbufsize = p->outbufsize = BUFSIZ;

	p->inbuf = malloc(p->inbufsize);
	if (p->inbuf == NULL)
		errx(1, "can't malloc");

	p->outbuf = malloc(p->outbufsize);
	if (p->outbuf == NULL)
		errx(1, "can't malloc");

	p->cd = iconv_open(to, from);
	if (p->cd == (iconv_t)-1)
		errx(1, "can't iconv_open");
}

static __inline void
converter_uninit(converter_t *p)
{
	free(p->inbuf);
	free(p->outbuf);

	iconv_close(p->cd);
}

static void
converter_exec(converter_t *p, FILE *reader, FILE *writer)
{
	int ch;
	size_t irreversible, nread, inbytes, outbytes, ret, nconv;
	char *in, *out;

	irreversible = (size_t)0;
	while ((nread = fread(p->inbuf, 1, p->inbufsize, reader)) > 0) {
		in = p->inbuf;
		inbytes	= nread;

		while (inbytes > 0) {
			out = p->outbuf;
			outbytes = p->outbufsize;

			ret = iconv(p->cd, (const char **)&in, &inbytes,
			    &out, &outbytes);
			nconv = p->outbufsize - outbytes;

			if (ret == (size_t)-1) {
				switch (errno) {
				case EINVAL:
					if (in != p->inbuf)
						memmove(p->inbuf, in, inbytes);
					p->inbufsize *= 2;
					in = realloc(p->inbuf, p->inbufsize);
					if (in == NULL)
						errx(1, "can't realloc");
					p->inbuf = in;
					nread = fread(p->inbuf + inbytes, 1,
					    p->inbufsize - inbytes, reader);
					if (nread == (size_t)0)
						errx(1, "unexpected eof");
					inbytes += nread;
					break;
				case E2BIG:
					p->outbufsize *= 2;
					out = realloc(p->outbuf, p->outbufsize);
					if (out == NULL)
						errx(1, "can't realloc");
					p->outbuf = out;
					break;
				default:
					errx(1, "can't iconv");
				}
			} else {
				irreversible += ret;
			}
			if (nconv > 0)
				fwrite((const void*)p->outbuf, 1, nconv, writer);
		}
	}
	do {
		out = p->outbuf;
		outbytes = p->outbufsize;

		ret = iconv(p->cd, NULL, NULL, &out, &outbytes);
		nconv = p->outbufsize - outbytes;

		if (ret == (size_t)-1) {
			if (errno != E2BIG)
				errx(1, "can't iconv");
			p->outbufsize *= 2;
			out = realloc(p->outbuf, p->outbufsize);
			if (out == NULL)
				errx(1, "can't realloc");
			p->outbuf = out;
		} else {
			irreversible += ret;
		}
		if (nconv > 0)
			fwrite((const void *)p->outbuf, 1, nconv, writer);
	} while (ret == (size_t)-1);

	if (!silent && irreversible > 0)
		warnx("%zu irreversible character", irreversible);
}

int
main(int argc, char *argv[])
{
	const char *from, *to;
	int ch;
	converter_t converter;
	FILE *fp;

	setlocale(LC_CTYPE, "");
	from = to = nl_langinfo(CODESET);

	while ((ch = getopt(argc, argv, "f:t:s")) != -1) {
		switch (ch) {
		case 'f':
			from = (const char *)optarg;
			break;
		case 't':
			to = (const char *)optarg;
			break;
		case 's':
			silent = 1;
			break;
		default:
			fprintf(stderr,
			    "usage: %s [-s] -f <from> -t <to> [file ...]\n",
			    getprogname());
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	converter_init(&converter, from, to);
	if (argc < 1)
		converter_exec(&converter, stdin, stdout);
	while (argc-- > 0) {
		fp = fopen(*argv++, "r");
		if (fp == NULL)
			errx(1, "can't fopen");
		converter_exec(&converter, fp, stdout);
		fclose(fp);
	}
	converter_uninit(&converter);

	return 0;
}

