/*
 * Copyright (c) 1999 Sun Microsystems, Inc.
 * Copyright (c) 1999 Nihon Sun Microsystems K.K.
 * All rights reserved.
 */

/*
 * "$Id: utf16_ct.c,v 1.1.1.1 2000/10/29 16:45:20 himi Exp $"
 */

#pragma ident	"@(#)utf16_ct.c 1.5	99/05/24 SMI"


#if defined(CSC_UTF8W_CT)
#  define CSC_UTF8_CT	1
#endif /* CSC_UTF8W_CT */

#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#if defined(CSC_UTF8W_CT)
#include <locale.h>
#include <wchar.h>
#endif /* CSC_UTF8W_CT */


#include "csconv.h"
#include "utf16_ct_map.h"
#include "utf16_ct_map_set.h"
#if defined(CSC_UTF8_CT)
#include "csc_utf.h"
#endif /* CSC_UTF8_CT */


#define	MAXVAL_14BIT		((16 * 1024) - 1)
#define	ERROR_BREAK(err)	ret_errno = (err);	\
				ret_val = (size_t)(-1);	\
				break
#undef	CSC_KEEP_MAP_CURRENT


struct _csconv_info {
	const csc_utf16_ct_map_set_t *	utf16_ct_map_set;
#if defined(CSC_KEEP_MAP_CURRENT)
	csc_utf16_ct_map_t *		utf16_ct_map;
#endif /* CSC_KEEP_MAP_CURRENT */
#if defined(CSC_UTF8W_CT)
	char *		locale;
#endif /* CSC_UTF8W_CT */
};


extern unsigned int *	csc_utf16_codeset_map[];


#if defined(CSC_UTF8W_CT)
#  define MB_BUF_LEN		(32)
#endif /* CSC_UTF8W_CT */

#if defined(CSC_UTF8W_CT)
#  define utf16_ct_open		utf8w_ct_open
#  define utf16_ct_close	utf8w_ct_close
#  define utf16_ct_conv		utf8w_ct_conv
#else /* !CSC_UTF8W_CT */
#if defined(CSC_UTF8_CT)
#  define utf16_ct_open		utf8_ct_open
#  define utf16_ct_close	utf8_ct_close
#  define utf16_ct_conv		utf8_ct_conv
#endif /* CSC_UTF8_CT */
#endif /* !CSC_UTF8W_CT */


csconv_t
utf16_ct_open(
	const char *	locale,
	const char *	tocode,
	const char *	fromcode)
{
	csconv_t			cd;
	const csc_utf16_ct_map_set_t *	ucms;
	int				ret_errno;
#if defined(CSC_UTF8W_CT)
	char *				locale_dup;
#endif /* CSC_UTF8W_CT */

	cd = NULL;
#if defined(CSC_UTF8W_CT)
	locale_dup = NULL;
#endif /* CSC_UTF8W_CT */

	do {
		ucms = utf16_ct_map_set_get(tocode);
		if (NULL == ucms) {
			ret_errno = EINVAL;
			continue;
		}

		if (NULL == (cd = malloc(sizeof (*cd)))) {
			ret_errno = ENOMEM;
			continue;
		}

#if defined(CSC_UTF8W_CT)
		locale_dup = strdup(locale);
		if (NULL == locale_dup) {
			ret_errno = ENOMEM;
			continue;
		}
#endif /* CSC_UTF8W_CT */

		cd->utf16_ct_map_set = ucms;
#if defined(CSC_KEEP_MAP_CURRENT)
		cd->utf16_ct_map = ucms->utf16_ct_map_default;
#endif /* CSC_KEEP_MAP_CURRENT */
#if defined(CSC_UTF8W_CT)
		cd->locale = locale_dup;
#endif /* CSC_UTF8W_CT */

		return cd;
	} while (0);

#if defined(CSC_UTF8W_CT)
	free(locale_dup);
#endif /* CSC_UTF8W_CT */
	free(cd);

	errno = ret_errno;
	return NULL;
}


void
utf16_ct_close(csconv_t cd)
{
	if (NULL == cd) {
		return;
	}

	utf16_ct_map_set_free((csc_utf16_ct_map_set_t *)(cd->utf16_ct_map_set));
#if defined(CSC_UTF8W_CT)
	free(cd->locale);
#endif /* CSC_UTF8W_CT */
	free(cd);

	return;
}


size_t
utf16_ct_conv(
	csconv_t	cd,
	const char **	inbuf,
	size_t *	inbytesleft,
	char **		outbuf,
	size_t *	outbytesleft)
{
	size_t				ret_val;
	int				ret_errno;
	const unsigned char * 		ip;
	size_t				ileft;
	unsigned char *			op;
	size_t				oleft;
	unsigned short			uc;
	const csc_utf16_ct_map_set_t *	map_set;
	const csc_utf16_ct_map_t **	map;
	const csc_utf16_ct_map_t *	map_current;
	unsigned int			id_current;
	unsigned int			id;
	unsigned int			id_mask;
	unsigned int			high;
	unsigned int			low;
	unsigned int *			map_cs;
	const unsigned char *		map_low;
	const unsigned char *		p;
	unsigned int			i;
	unsigned char *			extended_segment_ml;
	int				extended_segment_len;
	int				control_char;
#if defined(CSC_UTF8_CT)
	int				len;
#endif /* !CSC_UTF8_CT */
#if defined(CSC_UTF8W_CT)
	char *				locale;
	unsigned char			mb_buf[MB_BUF_LEN];
	unsigned char *			mb;
	size_t				mb_buf_len;
	unsigned char *			ip1;
	size_t				ileft1;
#endif /* CSC_UTF8W_CT */

	ret_val = 0;
	control_char = 0;
	ret_errno = 0;

	if ((NULL == inbuf) || (NULL == *inbuf)) {
		map_current = cd->utf16_ct_map_set->utf16_ct_map_default;
#if defined(CSC_KEEP_MAP_CURRENT)
		cd->utf16_ct_map = map_current;
#endif /* CSC_KEEP_MAP_CURRENT */

		if ((NULL == outbuf) && (NULL == *outbuf)) {
			return 0;
		}

		p = map_current->desig;
		i = map_current->desig_length;
		if (*outbytesleft < i) {
			errno = E2BIG;
			return (size_t)(-1);
		}

		op = (unsigned char *)(*outbuf);
		*outbuf += i;
		*outbytesleft -= i;

		if (1 == map_current->extended_segment) {
			unsigned int	high7;
			unsigned int	low7;

			if (i < 6) {
				errno = EBADF;
				return (size_t)(-1);
			}

			high7 = (0x00000080 | ((0x00004fff & (i - 6)) >> 7));
			low7 = (0x00000080 | (0x0000007f & (i - 6)));

			*(op++) = *(p++);
			*(op++) = *(p++);
			*(op++) = *(p++);
			*(op++) = *(p++);
			*(op++) = high;
			*(op++) = low;
			i -= 6;
		}

		for (; 0 < i; --i) {
			*(op++) = *(p++);
		}

		return 0;
	}

	extended_segment_ml = NULL;
	extended_segment_len = 0;

#if defined(CSC_KEEP_MAP_CURRENT)
	map_current = cd->utf16_ct_map;
	id_current = cd->utf16_ct_map->id;
#else /* !CSC_KEEP_MAP_CURRENT */
	map_current = NULL;
	id_current = 0;
#endif /* !CSC_KEEP_MAP_CURRENT */
	id = 0;

	ip = (const unsigned char *)(*inbuf);
	ileft = *inbytesleft;
	op = (unsigned char *)(*outbuf);
	oleft = *outbytesleft;

#if defined(CSC_UTF8W_CT)
	mb = mb_buf;
	mb_buf_len = (sizeof (mb_buf));

	locale = setlocale(LC_CTYPE, NULL);
	if (0 == strcmp(locale, cd->locale)) {
		locale = NULL;
	} else {
		locale = strdup(locale);
		if (NULL == locale) {
			errno = ENOMEM;
			return (size_t)(-1);
		}
		if (NULL == setlocale(LC_CTYPE, cd->locale)) {
			free(locale);
			errno = EBADF;
			return (size_t)(-1);
		}
	}
#endif /* CSC_UTF8W_CT */

#if defined(CSC_UTF8_CT)
#  if defined(CSC_UTF8W_CT)
#  else /* !CSC_UTF8W_CT */
#  endif /* !CSC_UTF8W_CT */
#else /* !CSC_UTF8_CT */
#endif /* !CSC_UTF8_CT */
	for (; 0 < ileft;) {
#if defined(CSC_UTF8_CT)
#  if defined(CSC_UTF8W_CT)
		if (ileft < (sizeof (wchar_t))) {
			ERROR_BREAK(EINVAL);
		}
		len = wctomb((char *)mb, *((wchar_t *)ip));
		if (len <= 0) {
			ERROR_BREAK(EILSEQ);
		}
		ip1 = mb;
		ileft1 = len;
		UTF8_UTF16(ip1, ileft1, len, uc, ret_errno);
		if (0 == len) {
			ERROR_BREAK(ret_errno);
		}
		ip += (sizeof (wchar_t));
		ileft -= (sizeof (wchar_t));
#  else /* !CSC_UTF8W_CT */
		UTF8_UTF16(ip, ileft, len, uc, ret_errno);
		if (0 == len) {
			ERROR_BREAK(ret_errno);
		}
#  endif /* !CSC_UTF8W_CT */
		high = (uc >> 8);
		low = (uc & 0xff);
#else /* !CSC_UTF8_CT */
		if (ileft < 2) {
			ERROR_BREAK(EINVAL);
		}
		high = *((unsigned char *)ip);
		low = *(((unsigned char *)ip) + 1);
		uc = ((high << 8) + low);
		ip += 2;
		ileft -= 2;
#endif /* !CSC_UTF8_CT */

		if ((0x0a == uc) || (0x09 == uc)) {
			control_char = 1;
			map_current =
				cd->utf16_ct_map_set->utf16_ct_map_default;
			id = map_current->id;
			map = &map_current;

		} else {
			control_char = 0;

			if (NULL == (map_cs = csc_utf16_codeset_map[high])) {
				ERROR_BREAK(EILSEQ);
			}

			if (0 == (id_mask = *(map_cs + low))) {
				ERROR_BREAK(EILSEQ);
			}

			map_set = cd->utf16_ct_map_set;
			map = map_set->utf16_ct_map;
			for (; NULL != (*map); map += 1) {
				if ((*map)->id == (id_mask & (*map)->id)) {
					id = (*map)->id;
					break;
				}
			}
			if (NULL == (*map)) {
				ERROR_BREAK(EILSEQ);
			}
		}

		if ((id != id_current) ||
		    ((NULL != extended_segment_ml) &&
		     (MAXVAL_14BIT <= extended_segment_len))) {
			id_current = id;
			map_current = *map;
			if (oleft <
			    ((*map)->out_code_length + (*map)->desig_length)) {
				ERROR_BREAK(E2BIG);
			}

			p = (*map)->desig;
			i = (*map)->desig_length;
			oleft -= i;

			if (NULL != extended_segment_ml) {
				unsigned int	h7;
				unsigned int	l7;

				h7 = (0x0080 |
				      ((0x3f80 & extended_segment_len) >> 7));
				l7 = (0x0080 | (0x007f & extended_segment_len));
				

				*(extended_segment_ml + 0) = h7;
				*(extended_segment_ml + 1) = l7;

				extended_segment_ml = NULL;
				extended_segment_len = 0;
			}

			if (1 == (*map)->extended_segment) {
				extended_segment_ml = (op + 4);
				extended_segment_len = (i - 6);
			}

			for (; 0 < i; --i) {
				*(op++) = *(p++);
			}

			if (1 == control_char) {
				if (oleft < 1) {
					ERROR_BREAK(E2BIG);
				}
				*(op++) = uc;
				oleft -= 1;

				continue;
			}
		} else {
			if (1 == control_char) {
				if (oleft < 1) {
					ERROR_BREAK(E2BIG);
				}
				*(op++) = uc;
				oleft -= 1;

				continue;

			} else if (oleft < (*map)->out_code_length) {
				ERROR_BREAK(E2BIG);
			}
		}

		if (NULL == (map_low = *((*map)->map + high))) {
			ERROR_BREAK(EILSEQ);
		}

		i = (*map)->out_code_length;
		p = (map_low + (i * low));
		oleft -= i;

		extended_segment_len += i;

		for (; 0 < i; --i) {
			*(op++) = *(p++);
		}
	}

#if defined(CSC_UTF8W_CT)
	if (NULL != locale) {
		setlocale(LC_CTYPE, locale);
		free(locale);
	}
#endif /* CSC_UTF8W_CT */

	if (NULL != extended_segment_ml) {
		unsigned int	h7;
		unsigned int	l7;
		
		h7 = (0x0080 | ((0x3f00 & extended_segment_len) >> 7));
		l7 = (0x0080 | (0x007f & extended_segment_len));

		*(extended_segment_ml + 0) = h7;
		*(extended_segment_ml + 1) = l7;

		extended_segment_ml = NULL;
		extended_segment_len = 0;
	}

	if ((1 == ileft) && (0 == ret_val)) {
		errno = EINVAL;
		ret_val = (size_t)(-1);
	}

	*inbuf = (const char *)ip;
	*inbytesleft = ileft;
	*outbuf = (char *)op;
	*outbytesleft = oleft;

#if defined(CSC_KEEP_MAP_CURRENT)
	if ((NULL != map) && (NULL != *map)) {
		cd->utf16_ct_map = *map;
	}
#endif /* CSC_KEEP_MAP_CURRENT */

	errno = ret_errno;

	return ret_val;
}
