/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 2003-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * character codeset coder
 */

#include <codex.h>
#include <iconv.h>

typedef struct State_s
{
	iconv_t		cvt;

	char*		bp;

	char		buf[SF_BUFSIZE];
} State_t;

static int
cc_options(Codexmeth_t* meth, Sfio_t* sp)
{
	register iconv_list_t*	ic;
	register const char*	p;
	register int		c;

	for (ic = iconv_list(NiL); ic; ic = iconv_list(ic))
	{
		sfputc(sp, '[');
		sfputc(sp, '+');
		sfputc(sp, '\b');
		p = ic->match;
		if (*p == '(')
			p++;
		while (c = *p++)
		{
			if (c == ')' && !*p)
				break;
			if (c == '?' || c == ']')
				sfputc(sp, c);
			sfputc(sp, c);
		}
		sfputc(sp, '?');
		p = ic->desc;
		while (c = *p++)
		{
			if (c == ']')
				sfputc(sp, c);
			sfputc(sp, c);
		}
		sfputc(sp, ']');
	}
	return 0;
}

static int
cc_open(Codex_t* p, char* const args[], Codexnum_t flags)
{
	State_t*	state;
	const char*	src;
	const char*	dst;
	iconv_t		cvt;

	dst = (src = args[2]) ? args[3] : 0;
	if (flags & CODEX_DECODE)
	{
		if (!src)
		{
			if (p->disc->errorf)
				(*p->disc->errorf)(NiL, p->disc, 2, "%s: source codeset option must be specified", p->meth->name);
			return -1;
		}
	}
	else
	{
		if (!src)
		{
			if (p->disc->errorf)
				(*p->disc->errorf)(NiL, p->disc, 2, "%s: destination codeset option must be specified", p->meth->name);
			return -1;
		}
		if (!dst)
		{
			dst = src;
			src = 0;
		}
	}
	if ((cvt = iconv_open(dst, src)) == (iconv_t)(-1))
	{
		if (p->disc->errorf)
		{
			if ((cvt = iconv_open("utf-8", src)) == (iconv_t)(-1))
			{
				(*p->disc->errorf)(NiL, p->disc, 2, "%s: %s: unknown source codeset", p->meth->name, src);
				return -1;
			}
			iconv_close(cvt);
			if ((cvt = iconv_open(dst, "utf-8")) == (iconv_t)(-1))
			{
				(*p->disc->errorf)(NiL, p->disc, 2, "%s: %s: unknown destination codeset", p->meth->name, dst);
				return -1;
			}
			iconv_close(cvt);
			(*p->disc->errorf)(NiL, p->disc, 2, "%s: cannot convert from %s to %s", p->meth->name, src, dst);
		}
		return -1;
	}
	if (!(state = newof(0, State_t, 1, 0)))
	{
		if (p->disc->errorf)
			(*p->disc->errorf)(NiL, p->disc, 2, "out of space");
		iconv_close(cvt);
		return 0;
	}
	state->cvt = cvt;
	state->bp = state->buf;
	p->data = state;
	return 0;
}

static int
cc_close(Codex_t* p)
{
	State_t*	state = (State_t*)p->data;
	int		r;

	if (!state)
		r = -1;
	else
	{
		r = iconv_close(state->cvt);
		free(state);
	}
	return r;
}

static ssize_t
cc_read(Sfio_t* sp, void* buf, size_t n, Sfdisc_t* disc)
{
	register State_t*	state = (State_t*)((Codex_t*)disc)->data;
	char*			fb;
	char*			tb;
	size_t			fn;
	size_t			tn;
	ssize_t			r;

	fn = sizeof(state->buf) - (state->bp - state->buf);
	if (n < fn)
		fn = n;
	if ((r = sfrd(sp, state->bp, fn, disc)) <= 0)
		return (state->bp > state->buf) ? -1 : r;
	fb = state->buf;
	fn = r + (state->bp - state->buf);
	tb = buf;
	tn = n;
	n = 0;
	while (fn > 0 && tn > 0)
	{
		if ((r = iconv(state->cvt, &fb, &fn, &tb, &tn)) == -1)
		{
			if (!n)
				n = -1;
			break;
		}
		n += r;
	}
	if (fn && fb > state->buf)
	{
		tb = state->buf;
		while (fn--)
			*tb++ = *fb++;
		state->bp = tb;
	}
	return n;
}

static ssize_t
cc_write(Sfio_t* sp, const void* buf, size_t n, Sfdisc_t* disc)
{
	register State_t*	state = (State_t*)((Codex_t*)disc)->data;
	char*			fb;
	char*			tb;
	size_t			fn;
	size_t			tn;
	size_t			r;

	fb = (char*)buf;
	fn = n;
	n = 0;
	while (fn > 0)
	{
		tb = (char*)state->buf;
		tn = sizeof(buf);
		if ((r = iconv(state->cvt, &fb, &fn, &tb, &tn)) == (size_t)(-1))
			return n ? n : -1;
		n += r;
		if (sfwr(sp, state->buf, r, disc) != r)
			return n ? n : -1;
	}
	return n;
}

static int
cc_sync(Codex_t* p)
{
	mbinit();
	return 0;
}

Codexmeth_t	codex_iconv =
{
	"iconv",
	"iconv character codeset conversion. One or two character codeset"
	" options must be specified. Two options specify the source and"
	" destination codesets. One option specifies the decode source or"
	" encode destination codeset; the implied second codeset defaults"
	" to \bnative\b.",
	"[+(version)?codex-iconv (AT&T Labs Research) 2000-05-09]"
	"[+(author)?Glenn Fowler <gsf@research.att.com>]",
	CODEX_DECODE|CODEX_ENCODE|CODEX_ICONV,
	cc_options,
	0,
	cc_open,
	cc_close,
	cc_sync,
	cc_sync,
	cc_read,
	cc_write,
	cc_sync,
	0,
	0,
	0,
	0,
	CODEXNEXT(codex_iconv_next)
};

CODEXLIB(&codex_iconv)
