#define ICONV_INTERNAL
#include "iconv.h"

#include <stdlib.h>
#include <string.h>

struct iconv_uc {
	struct iconv_ces *	from;
	struct iconv_ces *	to;
	int			ignore_ilseq;
	ucs_t			missing;
};

static iconv_open_t iconv_uc_open;
static iconv_close_t iconv_uc_close;
static iconv_conv_t iconv_uc_conv;

struct iconv_converter_desc iconv_uc_desc = {
	iconv_uc_open,
	iconv_uc_close,
	iconv_uc_conv
};

/*
 * It is call by apr_iconv_open: (*idesc)->icd_open()
 */
apr_status_t
iconv_uc_open(const char *to, const char *from, void **data, apr_pool_t *ctx)
{
	struct iconv_uc *ic;
	int error;

	ic = malloc(sizeof(*ic));
	if (ic == NULL)
		return APR_ENOMEM;
	memset(ic, 0, sizeof(*ic));
	error = apr_iconv_ces_open(from, &ic->from, ctx);
	if (error!=APR_SUCCESS) {
		goto bad;
	}
	error = apr_iconv_ces_open(to, &ic->to, ctx);
	if (error!=APR_SUCCESS) {
		goto bad;
	}
	ic->ignore_ilseq = 0;
	ic->missing = '_';
	*data = (void*)ic;
	return APR_SUCCESS;
bad:
	iconv_uc_close(ic,ctx);
	return error;
}

apr_status_t
iconv_uc_close(void *data, apr_pool_t *ctx)
{
	struct iconv_uc *ic = (struct iconv_uc *)data;

	if (ic == NULL)
		return APR_EBADF;
	if (ic->from)
		apr_iconv_ces_close(ic->from, ctx);
	if (ic->to)
		apr_iconv_ces_close(ic->to, ctx);
	free(ic);
	return APR_SUCCESS;
}

apr_status_t
iconv_uc_conv(void *data, const unsigned char **inbuf, apr_size_t *inbytesleft,
	unsigned char **outbuf, apr_size_t *outbytesleft, apr_size_t *res)
{
	struct iconv_uc *ic = (struct iconv_uc *)data;
	const unsigned char *ptr;
	ucs_t ch;
        apr_ssize_t size;

	*res = (apr_size_t)(0);
	if (data == NULL) {
		*res = (apr_size_t) -1;
		return APR_EBADF;
	}

	if (inbuf == NULL || *inbuf == NULL) {
		if (ICONV_CES_CONVERT_FROM_UCS(ic->to, UCS_CHAR_NONE,
		    outbuf, outbytesleft) <= 0) {
			*res = (apr_size_t) -1;
			return APR_BADARG; /* too big */
		}
		ICONV_CES_RESET(ic->from);
		ICONV_CES_RESET(ic->to);
		return APR_SUCCESS;
	}
	if (inbytesleft == NULL || *inbytesleft == 0)
		return APR_SUCCESS;
	while (*inbytesleft > 0 && *outbytesleft > 0) {
		ptr = *inbuf;
		ch = ICONV_CES_CONVERT_TO_UCS(ic->from, inbuf, inbytesleft);
		if (ch == UCS_CHAR_NONE)
			return APR_EINVAL;
		if (ch == UCS_CHAR_INVALID) { /* Invalid character in source buffer */
			if (ic->ignore_ilseq)
				continue;
			*inbytesleft += *inbuf - ptr;
			*inbuf = ptr;
			return APR_BADCH; /* eilseq invalid */
		}
		size = ICONV_CES_CONVERT_FROM_UCS(ic->to, ch,
		    outbuf, outbytesleft);
		if (size < 0) {		 /* No equivalent in destination charset */
			size = ICONV_CES_CONVERT_FROM_UCS(ic->to, ic->missing,
			    outbuf, outbytesleft);
			if (size)
				*res ++;
		}
		if (!size) {		 /* No space to write to */
			*inbytesleft += *inbuf - ptr;
			*inbuf = ptr; 
			return APR_BADARG; /* too big */
		}
	}
	return APR_SUCCESS;
}

#if 0
/* iconv_byteratio(cd) returns the byte ratio between OUTPUT and INPUT
 *                     stream lengths
 * -1: unknown
 *  0: 1/2
 *  1: 1/1
 *  2: 2/1
 */
int
iconv_byteratio(iconv_t cd)
{
	iconv_data *idata = (iconv_data *)cd;
	int from, to;

	to = (*idata->to->data->nbytes)(idata->to);
	if (to == 0)
		return -1;
	from = (*idata->from->data->nbytes)(idata->from);
	return (from) ? to / from : -1;
}
#endif
