/*-
 * Copyright (c) 2001
 * Tatsuya Kudoh(CDR/TK),ROYALPANDA.    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.
 *
 * 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.
 *
 */

#include<stdlib.h>
#include<stdio.h>
#include<sys/bios.h>

#include"ks.h"
#include"regg.h"

#pragma noregalo


/* prototypes */

static int state_normal( KSMsg *msg );
static int state_yomi( KSMsg *msg );
static int state_okuri( KSMsg *msg );
static int state_conv( KSMsg *msg );

static int begin_yomi( void );
static void begin_okuri( void );
static void begin_conversion( void );
static void begin_regist( void );
static void void_okuri( void );
static void cancel( void );


#define BUFLEN 64

typedef struct fep_t{
	struct fep_t *next;
	int (*receiver)(KSMsg *msg);
	void *call_data;
	int len;
	int x,y;
	int yomi_len;
	int (*state)(KSMsg *msg);
	unsigned buf[BUFLEN];
}fep_t;

static fep_t *CurFep = NULL;

static KSMemoryBlockManager FepMemMgr;

#define UDIC_WORD_LEN	32

static regg_t Regg;
static int Cand;
static int Order;


/*
** draw floating string
*/

#define CURSOR_FONT	0x11
#define FLOAT_LEN	32

static unsigned FloatBuf[FLOAT_LEN*2];
static int Fx,Fy,Flen;


static void float_disable( void )
{
	sprite_set_range(0,1);
}


static void float_clear( void )
{
	sprite_set_range(KS_SPR_FLOAT,0);
	Fx = CurFep->x;
	Fy = CurFep->y;
	Flen = 0;
}

static int float_add( unsigned code )
{
	if( Flen >= FLOAT_LEN - 1 )
		return 0;

	KSSetFontData(1,KS_FONT_FLOAT+Flen,code);
	FloatBuf[Flen*2] = 1<<13 | KS_FONT_FLOAT+Flen
				 | (KS_PLT_FLOAT & 0x7<<9);
	FloatBuf[Flen*2+1] = Fx << 11 | Fy << 3;
	Fx++;
	if( Fx >= 28 ){
		Fx = 0;
		Fy++;
		if( Fy >= 18 )
			Fy = 0;
	}
	Flen++;
	return 1;
}


static void float_add_str( unsigned *str )
{
	while( *str ){
		if( float_add(*str++) == 0 )
			break;
	}
}


static void float_draw( void )
{
	KSSetFontData(1,KS_FONT_FLOAT+Flen,CURSOR_FONT);
	FloatBuf[Flen*2] = 1 << 13 | KS_FONT_FLOAT+Flen
				   | (KS_PLT_FLOAT & 0x7<<9);
	FloatBuf[Flen*2+1] = Fx << 11 | Fy << 3;
	Flen++;
	sprite_set_data(KS_SPR_FLOAT,Flen,FloatBuf);
	sprite_set_range(KS_SPR_FLOAT,Flen);
}


static void draw_yomi( void )
{
	float_clear();
	float_add('');
	float_add_str(CurFep->buf);
	float_draw();
}


static void draw_yomi_okuri( void )
{
	float_clear();
	float_add('');
	float_add_str(CurFep->buf);
	float_add('');
	float_draw();
}

static void draw_candidate( void )
{
	unsigned char far *s;
	unsigned code;

	float_clear();
	float_add('');

	s = REGG_get_candidate(Order);
	if( s ){
		while( *s ){
			code = *s++;
			if( code & 0x80 ){
				code = code << 8 | *s++;
				if( (code & 0xff) == 0 )
					break;
			}
			float_add(code);
		}
		float_add_str(CurFep->buf+CurFep->yomi_len);
	}else{
		float_add_str(CurFep->buf);
	}
	float_draw();
}


static int begin_yomi( void )
{
	KSGetCursorStatus(&CurFep->x,&CurFep->y);
	CurFep->len = 0;
	CurFep->yomi_len = 0;
	CurFep->state = state_yomi;
	CurFep->buf[0] = 0;
	draw_yomi();
	KSSetInputMode(KS_INPUT_MODE_CONV1);
	return 0;
}


static void begin_okuri( void )
{
	if( CurFep->len < FLOAT_LEN - 2 ){
		CurFep->state = state_okuri;
		draw_yomi_okuri();
	}
}


static void void_okuri( void )
{
	CurFep->state = state_yomi;
	CurFep->len = CurFep->yomi_len;
	CurFep->buf[CurFep->len] = 0;
	draw_yomi();
	KSSetInputMode(KS_INPUT_MODE_CONV1);
}


static void begin_conversion( void )
{
	CurFep->state = state_conv;
	Regg.yomi = CurFep->buf;
	Regg.yomi_len = CurFep->yomi_len;
	if( CurFep->len > CurFep->yomi_len )
		Regg.okuri = CurFep->buf + CurFep->yomi_len;
	else
		Regg.okuri = NULL;


	Cand = REGG_start(&Regg);
	if( Cand > 0 ){
		Order = 0;
		draw_candidate();
		KSSetInputMode(KS_INPUT_MODE_CONV2);
	}else{
		begin_regist();
	}
}


static void cancel( void )
{
	float_disable();
	CurFep->state = state_normal;
	KSResetKeyboard();
	KSSetInputMode(KS_INPUT_MODE_KBD);
}


static int settle_kana( KSMsg *msg, int toggle )
{
	unsigned *result_buf,*src,*dst;

	cancel();
	result_buf = KSAllocate((CurFep->len+1)*2);
	if( result_buf == NULL )
		return 0;

	if( toggle ){
		Regg.yomi = CurFep->buf;
		Regg.yomi_len = CurFep->yomi_len;
		Regg.okuri = NULL;
		REGG_toggle_type(&Regg);
	}

	src = CurFep->buf;
	dst = result_buf;

	while( *src )
		*dst++ = *src++;
	*dst = 0;

	msg->type = KS_MSG_TEXT;
	msg->text.receiver = CurFep->receiver;
	msg->text.call_data = CurFep->call_data;
	msg->text.len = CurFep->len;
	msg->text.text = result_buf;

	KSSendMsg(msg);
	return 1;
}


static int settle( KSMsg *msg )
{
	int len;
	unsigned char far *s;
	unsigned char far *p;
	unsigned *dst,*src;
	unsigned *result_buf;
	unsigned code;

	cancel();
	p = REGG_get_candidate(Order);
	if( p == NULL )
		return settle_kana(msg,0);
	REGG_settle(Order);

	len = 0;
	s = p;
	while( *s ){
		code = *s++;
		if( code & 0x80 ){
			code = code << 8 | *s++;
			if( (code & 0xff) == 0 )
				break;
		}
		len++;
	}
	len += CurFep->len - CurFep->yomi_len + 1;

	result_buf = KSAllocate(len*2);
	if( result_buf == NULL )
		return 0;

	s = p;
	dst = result_buf;
	while( *s ){
		code = *s++;
		if( code & 0x80 ){
			code = code << 8 | *s++;
			if( (code & 0xff) == 0 )
				break;
		}
		*dst++ = code;
	}
	src = CurFep->buf + CurFep->yomi_len;
	while( *src )
		*dst++ = *src++;
	*dst = 0;

	msg->type = KS_MSG_TEXT;
	msg->text.receiver = CurFep->receiver;
	msg->text.call_data = CurFep->call_data;
	msg->text.len = len;
	msg->text.text = result_buf;

	KSSendMsg(msg);
	return 1;
}


static void delete_udic( void )
{
	if( REGG_delete_udic(Order) == 1 )
		cancel();
}


static int regist_handler( KSMsg *msg )
{
	int len;
	unsigned *result_buf,*src,*dst;

	if( CurFep == NULL )
		return 0;

	if( msg->type != KS_MSG_TEXT )
		return 0;

	Regg.yomi = CurFep->buf;
	Regg.yomi_len = CurFep->yomi_len;
	if( CurFep->len > CurFep->yomi_len )
		Regg.okuri = CurFep->buf + CurFep->yomi_len;
	else
		Regg.okuri = NULL;

	if( msg->text.len == 0 ){
		Cand = REGG_start(&Regg);
		if( Cand > 0 ){
			Order = Cand - 1;
			CurFep->state = state_conv;
			draw_candidate();
			KSSetInputMode(KS_INPUT_MODE_CONV2);
		}else{
			void_okuri();
		}
		return 0;
	}

	Regg.regist_word = msg->text.text;
	REGG_regist_udic(&Regg);

	cancel();
	len = msg->text.len + CurFep->len - CurFep->yomi_len;
	result_buf = KSAllocate((len+1)*2);
	if( result_buf == NULL )
		return 0;

	src = msg->text.text;
	dst = result_buf;
	while( *src )
		*dst++ = *src++;

	src = CurFep->buf + CurFep->yomi_len;
	while( *src )
		*dst++ = *src++;
	*dst = 0;

	KSFree(msg->text.text);

	msg->type = KS_MSG_TEXT;
	msg->text.receiver = CurFep->receiver;
	msg->text.call_data = CurFep->call_data;;
	msg->text.text = result_buf;
	msg->text.len = len;
	KSSendMsg(msg);
	return 1;
}


static void begin_regist( void )
{
	unsigned char buf[64+12];
	int len;
	int i;
	unsigned *src;
	unsigned char *s;

	len = 0;
	src = CurFep->buf;
	for( i = 0 ; i < CurFep->yomi_len ; i++ ){
		if( len > 61 )
			break;
		if( *src & 0x8000 ){
			buf[len++] = *src >> 8;
			buf[len++] = *src & 0xff;
		}else{
			buf[len++] = *src;
		}
		src++;
	}
	if( CurFep->len > CurFep->yomi_len ){
		if( len <= 61 ){
			buf[len++] = '' >> 8;
			buf[len++] = '' & 0xff;
		}
		if( len <= 61 ){
			if( *src & 0x8000 ){
				buf[len++] = *src >> 8;
				buf[len++] = *src & 0xff;
			}else{
				buf[len++] = *src;
			}
		}
	}
	s = "mo^n";
	while( *s )
		buf[len++] = *s++;
	buf[len] = 0;

	if( KSLineInput(buf,NULL,regist_handler,NULL) != -1 )
		cancel();
}


/*
** statemachine
**     msg->type must be KS_MSG_STRING
*/

static int state_normal( KSMsg *msg )
{
	if( msg->string.str[0] != KEYCODE_CONVERSION ){
		msg->string.receiver = CurFep->receiver;
		msg->string.call_data = CurFep->call_data;
		KSSendMsg(msg);
		return 1;
	}

	begin_yomi();
	return 0;
}


static int state_yomi( KSMsg *msg )
{
	unsigned *str;
	int result;

	str = msg->string.str;
	result = 0;

	switch( *str ){
	case KEYCODE_TOGGLECONV:
		if( CurFep->len == 0 )
			cancel();
		else
			result = settle_kana(msg,1);
		break;

	case KEYCODE_NOCONV:
	case KEYCODE_RETURN:
		if( CurFep->len == 0 )
			cancel();
		else
			result = settle_kana(msg,0);
		break;

	case KEYCODE_CONVERSION:
	case KEYCODE_SPACE:
		if( CurFep->len == 0 )
			cancel();
		else
			begin_okuri();
		
		break;

	case KEYCODE_BACKSPACE:
		if( CurFep->len == 0 ){
			cancel();
		}else{
			CurFep->len--;
			CurFep->yomi_len--;
			CurFep->buf[CurFep->len] = 0;
			draw_yomi();
		}
		break;

	case KEYCODE_HOME:
		cancel();
		break;

	default:
		while( *str ){
			if( *str < 32 ){
				str++;
				continue;
			}
			if( CurFep->len < FLOAT_LEN - 2 ){
				CurFep->buf[CurFep->len] = *str++;
				CurFep->len++;
				CurFep->buf[CurFep->len] = 0;
			}else{
				break;
			}
		}
		CurFep->yomi_len = CurFep->len;
		draw_yomi();
		break;
	}
	return result;
}


static int state_okuri( KSMsg *msg )
{
	unsigned *str;
	int result;

	str = msg->string.str;
	result = 0;

	switch( *str ){
	case KEYCODE_TOGGLECONV:
		result = settle_kana(msg,1);
		break;

	case KEYCODE_NOCONV:
	case KEYCODE_RETURN:
		result = settle_kana(msg,0);
		break;

	case KEYCODE_CONVERSION:
	case KEYCODE_SPACE:
		begin_conversion();
		break;

	case KEYCODE_BACKSPACE:
		void_okuri();
		break;

	case KEYCODE_HOME:
		cancel();
		break;

	default:
		while( *str ){
			if( *str < 32 ){
				str++;
				continue;
			}
			if( CurFep->len < FLOAT_LEN - 3 ){
				CurFep->buf[CurFep->len] = *str++;
				CurFep->len++;
				CurFep->buf[CurFep->len] = 0;
			}else{
				break;
			}
		}
		if( CurFep->len > CurFep->yomi_len )
			begin_conversion();
		break;
	}
	return result;
}


static int state_conv( KSMsg *msg )
{
	unsigned *str;
	int result;

	str = msg->string.str;
	result = 0;

	switch( *str ){
	case KEYCODE_TOGGLECONV:
		result = settle_kana(msg,1);
		break;

	case KEYCODE_NOCONV:
		result = settle_kana(msg,0);
		break;

	case KEYCODE_SELECT:
		result = settle(msg);
		break;

	case KEYCODE_CONVERSION:
		if( Order >= Cand - 1 ){
			begin_regist();
		}else{
			Order++;
			draw_candidate();
		}
		break;

	case KEYCODE_PRIOR:
		if( Order > 0 ){
			Order--;
			draw_candidate();
		}
		break;

	case KEYCODE_DELETEDIC:
		delete_udic();
		break;

	case KEYCODE_BACKSPACE:
		void_okuri();
		break;
	}
	return result;
}




static int key_handler( KSMsg *msg )
{
	if( CurFep == NULL )
		return 0;

	if( msg->type != KS_MSG_STRING )
		return 0;

	return CurFep->state(msg);
}


void KSInitializeReggFEP( void )
{
	KSSetMemoryBlockManager(&FepMemMgr,sizeof(fep_t));
}


int KSStartReggFEP( int (*receiver)(KSMsg *msg), void *call_data )
{
	fep_t *p;

	p = KSAllocateMemoryBlock(&FepMemMgr);
	if( p == NULL )
		return -1;

	if( KSSetKeyHandler(key_handler,NULL)  < 0 ){
		KSFreeMemoryBlock(&FepMemMgr,p);
		return -1;
	}

	p->receiver = receiver;
	p->call_data = call_data;
	p->state = state_normal;

	p->next = CurFep;
	CurFep = p;

	return 0;
}

void KSFinishReggFEP( void )
{
	fep_t *p;

	if( CurFep == NULL )
		return;

	KSRemoveKeyHandler();

	p = CurFep;
	CurFep = p->next;
	KSFreeMemoryBlock(&FepMemMgr,p);
}
