/*-
 * Copyright (c) 1999,2000
 *	Konstantin Chuguev.  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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Konstantin Chuguev
 *	and its contributors.
 *
 * 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.
 *
 *	iconv (Charset Conversion Library) v1.0
 */

#include <stdlib.h>	/* free, malloc */
#include <string.h>

#define ICONV_INTERNAL
#include "iconv.h"

#define	CESTOSTATE(ces)		((iconv_ces_euc_state_t *)(ces)->data)
#define	MODTOCCS(mod)		((struct iconv_ccs_desc *)(mod)->im_desc->imd_data)


typedef struct {
	int nccs;
	const struct iconv_module *ccs[1];
} iconv_ces_euc_state_t;

API_DECLARE_NONSTD(apr_status_t)
apr_iconv_euc_open(struct iconv_ces *ces, apr_pool_t *ctx)
{
	struct iconv_module *depmod = ces->mod->im_deplist;
	iconv_ces_euc_state_t *state;
	apr_size_t stsz;
	int i;

	stsz = sizeof(iconv_ces_euc_state_t) +
	    sizeof(struct iconv_module *) * (ces->mod->im_depcnt - 1);
	state = (iconv_ces_euc_state_t *)malloc(stsz);
	if (state == NULL)
		return APR_ENOMEM;
	memset(state, 0, stsz);
	state->nccs = ces->mod->im_depcnt;
	for (i = ces->mod->im_depcnt; i; i--, depmod = depmod->im_next)
		state->ccs[i - 1] = depmod;
	CESTOSTATE(ces) = state;
	return APR_SUCCESS;
}

API_DECLARE_NONSTD(apr_status_t)
apr_iconv_euc_close(struct iconv_ces *ces)
{
	free(CESTOSTATE(ces));
	return APR_SUCCESS;
}

#define is_7_14bit(data) ((data)->nbits & 7)
#define is_7bit(data) ((data)->nbits & 1)

API_DECLARE_NONSTD(apr_ssize_t)
apr_iconv_euc_convert_from_ucs(struct iconv_ces *ces, ucs_t in,
	unsigned char **outbuf, apr_size_t *outbytesleft)
{
	iconv_ces_euc_state_t *euc_state = CESTOSTATE(ces);
	const iconv_ces_euc_ccs_t *ccsattr;
	const struct iconv_ccs_desc *ccs;
	ucs_t res;
	apr_size_t bytes;
	int i;

	if (in == UCS_CHAR_NONE)
		return 1;	/* No state reinitialization for table charsets */
	if (iconv_char32bit(in))
		return -1;

	for (i = 0; i < euc_state->nccs; i++) {
		ccs = MODTOCCS(euc_state->ccs[i]);
		res = ICONV_CCS_CONVERT_FROM_UCS(ccs, in);
		if (res == UCS_CHAR_INVALID)
			continue;
		ccsattr = euc_state->ccs[i]->im_depdata;
		if (i) {
			if (is_7_14bit(ccs))
				res |= is_7bit(ccs) ? 0x80 : 0x8080;
			else if (!(res & 0x8080))
				continue;
		} else if (res & 0x8080)
			continue;
		bytes = (res & 0xFF00 ? 2 : 1) + ccsattr->prefixlen;
		if (*outbytesleft < bytes)
			return 0;	/* No space in the output buffer */
		if (ccsattr->prefixlen) {
			memcpy(*outbuf, ccsattr->prefix, ccsattr->prefixlen);
			(*outbuf) += ccsattr->prefixlen;
		}
		if (res & 0xFF00)
			*(*outbuf)++ = (unsigned char)(res >> 8);
		*(*outbuf)++ = (unsigned char)res;
		*outbytesleft -= bytes;
		return 1;
	}
	return -1;	/* No character in output charset */
}

static ucs_t
cvt2ucs(const struct iconv_ccs_desc *ccs, const unsigned char *inbuf,
	apr_size_t inbytesleft, int hi_plane, const unsigned char **bufptr)
{
	apr_size_t bytes = ccs->nbits > 8 ? 2 : 1;
	ucs_t ch = *(const unsigned char *)inbuf++;

	if (inbytesleft < bytes)
		return UCS_CHAR_NONE;	/* Not enough bytes in the input buffer */
	if (bytes == 2)
		ch = (ch << 8) | *(const unsigned char *)inbuf++;
	*bufptr = inbuf;
	if (hi_plane) {
		if (!(ch & 0x8080))
			return UCS_CHAR_INVALID;
		if (is_7_14bit(ccs))
			ch &= 0x7F7F;
	} else if (ch & 0x8080)
		return UCS_CHAR_INVALID;
	return ICONV_CCS_CONVERT_TO_UCS(ccs, ch);
}

API_DECLARE_NONSTD(ucs_t)
apr_iconv_euc_convert_to_ucs(struct iconv_ces *ces,
	const unsigned char **inbuf, apr_size_t *inbytesleft)
{
	iconv_ces_euc_state_t *euc_state = CESTOSTATE(ces);
	const iconv_ces_euc_ccs_t *ccsattr;
	const struct iconv_module *ccsmod;
	ucs_t res = UCS_CHAR_INVALID;
	const unsigned char *ptr;
        int i;

	if (**inbuf & 0x80) {
		for (i = 1; i < euc_state->nccs; i++) {
			ccsmod = euc_state->ccs[i];
			ccsattr = ccsmod->im_depdata;
			if (ccsattr->prefixlen + 1 > *inbytesleft)
				return UCS_CHAR_NONE;
			if (ccsattr->prefixlen &&
			    memcmp(*inbuf, ccsattr->prefix, ccsattr->prefixlen) != 0)
				continue;
			res = cvt2ucs(MODTOCCS(ccsmod), *inbuf + ccsattr->prefixlen,
			    *inbytesleft - ccsattr->prefixlen, 1, &ptr);
			if (res != UCS_CHAR_INVALID)
				break;
		}
		if (res == UCS_CHAR_INVALID)
			ptr = *inbuf + 1;
	} else
		res = cvt2ucs(MODTOCCS(euc_state->ccs[0]), *inbuf, *inbytesleft, 0, &ptr);
	if (res == UCS_CHAR_NONE)
		return res;	/* Not enough bytes in the input buffer */
	*inbytesleft -= ptr - *inbuf;
	*inbuf = ptr;
	return res;
}