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

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

#pragma ident	"@(#)wcs_utf16.c 1.5	99/05/14 SMI"


#include <stdlib.h>
#include <stddef.h>
#include <iconv.h>
#include <locale.h>
#include <errno.h>
#include <sys/types.h>

#include "csconv.h"


typedef struct _csc_state {
	char *		locale;
	iconv_t		cd0;
	iconv_t		cd1;
} csc_state_t;

#define INTERM_ENCODING	"UTF-8"
#define KO_INTERM_ENCODING	"ko_KR-UTF-8"


csc_state_t *
wcs_utf16_open(
	const char *	locale,
	const char *	tocode,
	const char *	fromcode)
{
	csc_state_t *	csc_state;
	iconv_t		cd0;
	iconv_t		cd1;
	char *		locale_current;
	char *		locale_dup;

	csc_state = NULL;
	cd0 = (iconv_t)(-1);
	locale_dup = NULL;

	do {
		/*
		 * check whether the locale is available
		 */
		if (NULL == (locale_current = setlocale(LC_CTYPE, NULL))) {
			continue;
		}

		if (NULL == (locale_current = strdup(locale_current))) {
			continue;
		}

		if (NULL == locale) {
			locale = locale_current;
			locale_dup = locale_current;
			locale_current = NULL;

		} else if (0 == strcmp(locale, locale_current)) {
			locale_dup = locale_current;
			locale_current = NULL;

		} else {
			if (NULL == setlocale(LC_CTYPE, locale)) {
				continue;
			}

			if (NULL == setlocale(LC_CTYPE, locale_current)) {
				continue;
			}

			free(locale_current);
			locale_current = NULL;

			if (NULL == (locale_dup = strdup(locale))) {
				continue;
			}
		}


		/*
		 * initialize iconv(3) descriptors
		 */
		cd0 = iconv_open(tocode, fromcode);
		if ((iconv_t)(-1) != cd0) {
			cd1 = (iconv_t)(-1);
		} else {
			cd0 = iconv_open(INTERM_ENCODING, fromcode);
			if ((iconv_t)(-1) == cd0) {
			  cd0 = iconv_open(KO_INTERM_ENCODING, fromcode);
			  if ((iconv_t)(-1) == cd0) {
			    continue;
			  }
			}

			cd1 = iconv_open(tocode, INTERM_ENCODING);
			if ((iconv_t)(-1) == cd1) {
				continue;
			}
		}


		/*
		 * allocate csc_state structure
		 */
		csc_state = malloc(sizeof (*csc_state));
		if (NULL == csc_state) {
			continue;
		}

		csc_state->locale = locale_dup;
		csc_state->cd0 = cd0;
		csc_state->cd1 = cd1;

		return csc_state;
	} while (0);


	/*
	 * error
	 */
	if ((iconv_t)(-1) != cd0) {
		iconv_close(cd0);
		if ((iconv_t)(-1) != cd1) {
			iconv_close(cd1);
		}
	}

	free(locale_current);
	free(locale_dup);
	free(csc_state);

	return NULL;
}


void
wcs_utf16_close(csc_state_t * csc_state)
{
	if (NULL == csc_state) {
		return;
	}

	iconv_close(csc_state->cd0);
	if ((iconv_t)(-1) != csc_state->cd1) {
		iconv_close(csc_state->cd1);
	}

	free(csc_state->locale);
	free(csc_state);

	return;
}


size_t
wcs_utf16_conv(
	csc_state_t *	csc_state,
	const char **	inbuf,
	size_t *	inbytesleft,
	char **		outbuf,
	size_t *	outbytesleft)
{
	wchar_t		buf_wcs[256];
	wchar_t *	p_interm_wcs;
	size_t		l_interm_wcs;
	char		buf_mbs[1024];
	char *		p_interm_mbs;
	size_t		l_interm_mbs;
	char *		p_interm_mbs_save;
	size_t		l_interm_mbs_save;
	char *		locale_current;
	int		fatal_error;
	size_t		ret0;
	size_t		ret1;
	char		buf[1024];
	char *		p_interm;
	size_t		l_interm;
	char *		p_interm_save;
	size_t		l_interm_save;
	int		errno_save;


	p_interm_mbs = NULL;
	p_interm_wcs = NULL;
	fatal_error = 0;


	/*
	 * WideChar to MultiByte
	 */
	do {
		/*
		 * initialize only
		 */
		if ((NULL == inbuf) || (NULL == *inbuf)) {
			p_interm_mbs = NULL;
			l_interm_mbs = 0;
			continue;
		}


		/*
		 * convert input data to null terminated strings
		 */
		l_interm_wcs = *inbytesleft;
		if (((sizeof (buf_wcs)) - (sizeof (wchar_t))) <= l_interm_wcs) {
			p_interm_wcs =
				malloc(l_interm_wcs + (sizeof (wchar_t)));
			if (NULL == p_interm_wcs) {
				fatal_error = 1;
				continue;
			}
		} else {
			p_interm_wcs = buf_wcs;
		}

		memcpy(p_interm_wcs, *inbuf, l_interm_wcs);
		*(p_interm_wcs + (l_interm_wcs / (sizeof (wchar_t)))) = L'\0';


		/*
		 * allocate intermediate storage for MultiByte strings
		 */
		l_interm_mbs = (((*inbytesleft / 4) * 3) + 1);
		if ((sizeof (buf_mbs)) < l_interm_mbs) {
			p_interm_mbs = malloc(l_interm_mbs);
			if (NULL == p_interm_mbs) {
				fatal_error = 1;
				continue;
			}
		} else {
			p_interm_mbs = buf_mbs;
			l_interm_mbs = (sizeof (buf_mbs));
		}


		/*
		 * enter the locale
		 */
		locale_current = setlocale(LC_CTYPE, NULL);
		if (NULL == locale_current) {
			fatal_error = 1;
			continue;
		}

		if (0 != strcmp(csc_state->locale, locale_current)) {
			locale_current = strdup(locale_current);
			if (NULL == locale_current) {
				fatal_error = 1;
				continue;
			}

			if (NULL == setlocale(LC_CTYPE, csc_state->locale)) {
				fatal_error = 1;
				continue;
			}
		} else {
			locale_current = NULL;
		}


		/*
		 * WideChar to MultiByte
		 */
		l_interm_mbs = wcstombs(p_interm_mbs, p_interm_wcs, l_interm_mbs);
		*(p_interm_mbs + l_interm_mbs) = '\0';


		/*
		 *  exit the locale, revert to the previous locale
		 */
		if (NULL != locale_current) {
			setlocale(LC_CTYPE, locale_current);
		}


		/*
		 * free unnecessary storage
		 */
		if (buf_wcs != p_interm_wcs) {
			free(p_interm_wcs);
		}

		/*
		 * variable
		 */
		p_interm_mbs_save = p_interm_mbs;
		l_interm_mbs_save = l_interm_mbs;
	} while (0);


	/*
	 * fatal error
	 */
	if (0 != fatal_error) {
		if (buf_wcs != p_interm_wcs) {
			free(p_interm_wcs);
		}

		if (buf_mbs != p_interm_mbs) {
			free(p_interm_mbs);
		}

		free(locale_current);

		return (size_t)(-1);
	}


	/*
	 * MultiByte to UTF-16
	 */


	/*
	 * MultiByte to UTF-16 (direct)
	 */
	if ((iconv_t)(-1) == csc_state->cd1) {
		ret0 = iconv(csc_state->cd0,
			     (const char **)(&p_interm_mbs), &l_interm_mbs,
			     outbuf, outbytesleft);

		if ((NULL != inbuf) && (NULL != *inbuf)) {
			*inbuf = (*inbuf + l_interm_mbs);

			if (NULL != inbytesleft) {
				*inbytesleft = l_interm_mbs;
			}
		}

		if (buf_mbs != p_interm_mbs_save) {
			free(p_interm_mbs_save);
		}

		return ret0;
	}


	/*
	 * MultiByte to Intermediate Encoding, then
	 * Intermediate Encoding to UTF-8
	 */

	/*
	 * Initialize Only
	 */
	if ((NULL == inbuf) || (NULL == *inbuf)) {
		size_t	ol;
		size_t	il;

		ol = 0;
		il = 0;

		ret0 = iconv(csc_state->cd0,
			     NULL, &il, NULL, &ol);
		errno_save = errno;

		ret1 = iconv(csc_state->cd0,
			     inbuf, inbytesleft, outbuf, outbytesleft);

		if ((size_t)(-1) == ret0) {
			errno = errno_save;
			return ret0;

		} else {
			return ret1;
		}
	}


	/*
	 * allocate intermediate encoding storage,
	 * and convert all input data into the storage
	 */
	p_interm = buf;
	l_interm = (sizeof (buf));
	p_interm_save = p_interm;
	l_interm_save = l_interm;

	ret0 = iconv(csc_state->cd0,
		     (const char **)(&p_interm_mbs), &l_interm_mbs,
		     &p_interm, &l_interm);

	if (((size_t)(-1) == ret0) && (E2BIG == errno)) {
		size_t	l;
		char *	p;

		l = (l_interm_save * 2);

		if (NULL == (p = malloc(l))) {
			return (size_t)(-1);
		}

		memcpy(p, p_interm_save, l_interm_save - l_interm);

		do {
			l_interm = (l - (l_interm_save - l_interm));
			l_interm_save = l;
			p_interm = (p + (l_interm_save - l_interm));
			p_interm_save = p;

			ret0 = iconv(csc_state->cd0,
				     (const char **)(&p_interm_mbs),
				     &l_interm_mbs,
				     &p_interm, &l_interm);
			if (((size_t)(-1) != ret0) ||
			    (E2BIG != errno)) {
				break;
			}

			l = (l_interm_save * 2);

			if (NULL == (p = realloc(p_interm_save, l))) {
				free(p_interm_save);
				if (buf_mbs != p_interm_mbs_save) {
					free(p_interm_mbs_save);
				}
				return (size_t)(-1);
			}
		} while (1);
	}

	errno_save = errno;

	if (buf_mbs != p_interm_mbs_save) {
		free(p_interm_mbs_save);
	}

	p_interm = p_interm_save;
	l_interm = (l_interm_save - l_interm);


	/*
	 * convert intermediate encoding to target encoding
	 */
	ret1 = iconv(csc_state->cd1,
		     (const char **)(&p_interm), &l_interm,
		     outbuf, outbytesleft);

	if ((size_t)(-1) == ret1) {
		if ((size_t)(-1) != ret0) {
			errno_save = errno;
		}
	}

	if (buf != p_interm_save) {
		free(p_interm_save);
	}

	if (((size_t)(-1) == ret0) || ((size_t)(-1) == ret1)) {
		errno = errno_save;
		return (size_t)(-1);
	} else {
		return ret1;
	}
}
