/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program is distributed in the hope that it will be 
	useful, but WITHOUT ANY WARRANTY; without even the 
	implied warranty of MERCHANTABILITY or FITNESS FOR A 
	PARTICULAR PURPOSE.

**********************************************************************/


#include	<stdlib.h>
#include	"memory_debug.h"
#include	"long_char.h"

typedef struct utf16_work {
	unsigned char	buf[2];
	char		co[2];
	L_CHAR		d;
	char		d_sts;
	char		len;
	char		out_start;
} UTF16_WORK;

void * open_utf16();
int close_utf16(unsigned char *,void *);
int utf162int(L_CHAR *,void*,unsigned char);
int int2utf16(unsigned char *,void*,L_CHAR);

CODE_METHOD utf16_cm = {
	"UTF-16",
	LCC_UTF16,
	0,
	6,
	0,
	{0,0},
	open_utf16,
	close_utf16,
	utf162int,
	int2utf16,
	0
};


void *
open_utf16()
{
UTF16_WORK * ret;
int i;
	ret = d_alloc(sizeof(UTF16_WORK),99);
	ret->len = 0;
	for ( i = 0 ; i < 2 ; i ++ ) {
		ret->buf[i] = 0;
		ret->buf[i+2] = 0;
		ret->co[i] = -1;
	}
	ret->d = 0;
	ret->d_sts = 0;
	ret->out_start = 0;
	return ret;
}

int
close_utf16(unsigned char * ret,void * work)
{
	d_f_ree(work);
	return 0;
}

int
utf162int(L_CHAR * ret,void * w,unsigned char ch)
{
UTF16_WORK * _w;
int i;
unsigned int b,c;
	_w = w;
	_w->buf[_w->len++] = ch;
	if ( _w->len == 2 ) {
		if ( _w->co[0] == -1 ) {
			b = 0;
			for ( i = 0 ; i < 2 ; i ++ ) {
				b |= (((int)_w->buf[i])&0x00ff)<<(24-8*i);
			}
		endian:
			if ( b == 0xfeff ) {
				_w->co[0] = 8;
				_w->co[1] = 0;
			}
			else if ( b == 0xfffe ) {
				_w->co[0] = 0;
				_w->co[1] = 8;
			}
			else {
				_w->co[0] = 8;
				_w->co[1] = 0;
				goto code;
			}
			_w->len = 0;
			_w->d_sts = 0;
			return 0;
		}
		else {
		code:
			b = 0;
			c = 0;
			for ( i = 0 ; i < 2 ; i ++ ) {
				b |= (((int)_w->buf[i])&0x00ff)<<_w->co[i];
				c |= (((int)_w->buf[i])&0x00ff)<<(24-8*i);
			}
			_w->len = 0;
			if ( c == 0xfeff ) {
				b = c;
				goto endian;
			}
			else if ( c == 0xfffe ) {
				b = c;
				goto endian;
			}
			if ( (b&0xfc00) == 0xd800 ) {
				b = b << 10;
				if ( _w->d_sts )
					_w->d |= b;
				else {
					_w->d = b;
					_w->d_sts = 1;
					return 0;
				}
			}
			else if ( (b&0xfc00) == 0xdc00 ) {
				if ( _w->d_sts )
					_w->d |= b;
				else {
					_w->d = b;
					_w->d_sts = 1;
					return 0;
				}
			}
			else {
				_w->d_sts = 0;
				*ret = b;
				return 1;
			}
			*ret = _w->d + 0x10000;
			_w->d_sts = 0;
			return 1;
		}
	}
	return 0;
}

int
int2utf16(unsigned char * ret,void * _work,L_CHAR ch)
{
UTF16_WORK * w;
unsigned short * r;
unsigned int chh;
int ret_bit;
	w = _work;
	if ( (ch&LCZM_4BYTE) != LCZ_4BYTE )
		return 0;
	r = (unsigned short*)ret;
	ret_bit = 0;
	if ( w->out_start == 0 ) {
		w->out_start = 1;
		*r++ = 0xfeff;
		ret_bit = 2;
	}
	chh = ch;
	if ( (chh&0xffff0000) == 0 ) {
		*r = chh;
		return ret_bit+2;
	}
	else if ( chh < 0x110000 ) {
		chh = chh - 0x10000;
		*r++ = ((chh>>10)&0x3ff)|0xd800;
		*r++ = (chh&0x3ff)|0xdc00;
		return ret_bit+4;
	}
	else	return 0;
}
