/*
 * 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.4	99/04/07 SMI"


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

#include <X11/Xlib.h>
#include "XlcPublic.h"

#include "csconv.h"
#include "csc_utf.h"


typedef struct _csc_state {
#if defined(USE_XOPENLC)
	int		lcd_opened;
#endif /* USE_XOPENLC */
	XLCd		lcd;
	XlcConv		lc_conv;
	char *		locale;
} csc_state_t;


csc_state_t *
utf16_ct_open(
	const char *	locale,
	const char *	tocode,
	const char *	fromcode)
{
#if defined(USE_XOPENLC)
	int		lcd_opened;
#endif /* USE_XOPENLC */
	XLCd		lcd;
	XlcConv		lc_conv;
	csc_state_t *	csc_state;
	char *		locale_current;
	char *		locale_dup;

	lc_conv = NULL;
	csc_state = NULL;

	do {
		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 == (locale_dup = strdup(locale))) {
				continue;
			}
		}

		lcd = _XlcCurrentLC();
		if (NULL == lcd) {
#if !defined(USE_XOPENLC)
			continue;
#else /* !USE_XOPENLC */
			lcd = _XOpenLC(locale);
			if (NULL == lcd) {
				continue;
			}
			lcd_opened = 1;
		} else {
			lcd_opened = 0;
#endif /* !USE_XOPENLC */
		}

		lc_conv = _XlcOpenConverter(lcd, XlcNMultiByte,
					    lcd, XlcNCompoundText);
		if (NULL == lc_conv) {
			continue;
		}

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

#if defined(USE_XOPENLC)
		csc_state->lcd_opened = lcd_opened;
#endif /* USE_XOPENLC */
		csc_state->lcd = lcd;
		csc_state->lc_conv = lc_conv;
		csc_state->locale = locale_dup;

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

		return csc_state;
	} while (0);

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

	if (NULL != lc_conv) {
		_XlcCloseConverter(lc_conv);
	}

#if defined(USE_XOPENLC)
	if (1 == lcd_opened) {
		_XCloseLC(lcd);
	}
#endif /* USE_XOPENLC */

	if (NULL != locale_dup) {
		free(locale_dup);
	}

	free(csc_state);

	return NULL;
}

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

	if (NULL != csc_state->lc_conv) {
		_XlcCloseConverter(csc_state->lc_conv);
	}

#if defined(USE_XOPENLC)
	if (1 == csc_state->lcd_opened) {
		_XCloseLC(csc_state->lcd);
	}
#endif /* USE_XOPENLC */

	if (NULL != csc_state->locale) {
		free(csc_state->locale);
	}

	free(csc_state);
	return;
}

size_t
utf16_ct_conv(
	csc_state_t *	csc_state,
	const char **	inbuf,
	size_t *	inbytesleft,
	char **		outbuf,
	size_t *	outbytesleft)
{
	char		buf[1024];
	char *		p_interm;
	size_t		l_interm;
	int		ret;
	const char * 	ip;
	char *		op;
	size_t		ileft;
	size_t		oleft;
	char *		locale_current;
	char *		locale_back;
	int		errno_save;

	if ((NULL == inbuf) || (NULL == *inbuf)) {
		p_interm = NULL;

	} else {
		l_interm = (((*inbytesleft) + 1) * 3);

		if ((sizeof (buf)) < l_interm) {
			p_interm = malloc(l_interm);
			if (NULL == p_interm) {
				errno = ENOMEM;
				return (size_t)(-1);
			}
		} else {
			p_interm = buf;
		}

		ip = *inbuf;
		op = p_interm;
		ileft = *inbytesleft;
		oleft = l_interm;

		while (2 <= ileft) {
			UTF16_TO_UTF8(ip, ileft, op, oleft);
		}

		ip = p_interm;
		ileft = l_interm - oleft;
		op = *outbuf;
		oleft = *outbytesleft;
	}

	if (NULL == (locale_current = setlocale(LC_CTYPE, NULL))) {
		if (buf != p_interm) {
			free(p_interm);
		}
		return (size_t)(-1);
	}

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

		if (NULL == setlocale(LC_CTYPE, csc_state->locale)) {
			free(locale_current);
			return (size_t)(-1);
		}
	} else {
		locale_current = NULL;
	}

	if (NULL == p_interm) {
		ret = _XlcConvert(csc_state->lc_conv,
				  (XPointer *)(inbuf), (int *)(inbytesleft),
				  (XPointer *)(outbuf), (int *)(outbytesleft),
				  NULL, 0);
	} else {
		ret = _XlcConvert(csc_state->lc_conv,
				  (XPointer *)(&ip), (int *)(&ileft),
				  (XPointer *)(&op), (int *)(&oleft), NULL, 0);
	}

	locale_back = NULL;

	if (NULL != locale_current) {
		if ((-1) == ret) {
			errno_save = errno;
		}

		locale_back = setlocale(LC_CTYPE, locale_current);
		free(locale_current);

		if ((-1) == ret) {
			errno = errno_save;
		}
	}

	if (NULL != p_interm) {
		if ((-1) == ret) {
			int	length;
			UTF8LEN(length, p_interm, ip);
			*inbytesleft -= (length * 2);
			*inbuf += *inbytesleft;

		} else {
			int	i;
			i = ((*inbytesleft) % 2);
			*inbuf += ((*inbytesleft) - i);
			*inbytesleft = i;

			if (1 == i) {
				errno = EINVAL;
				ret = (-1);
			}

			if (NULL == locale_back) {
				ret = (-1);
			}
		}
		*outbuf = op;
		*outbytesleft = oleft;

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

	return (size_t)ret;
}
