/*-
 * 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<sys/bios.h>

#include"ks.h"
#include"pietable.h"
#include"malloc.h"

#pragma noregalo

#define relative_time(t,base)	((int)((unsigned)(t) - (unsigned)(base)))

/*
** raw key handling
*/

#define KEY_REPEAT_DELAY 	30
#define KEY_REPEAT_INTERVAL 	3


#define KEY_MIN	1
#define KEY_MAX	11
#define KEY_NUM	(KEY_MAX - KEY_MIN + 1)


static unsigned KeyTime[KEY_NUM];	/* past from previous change */
static int KeyRepeatDelay;		/* period to start repeating */
static int KeyRepeatInterval;		/* interval bettween repeating */

static unsigned KeyState;
static unsigned KeyPressed;
static unsigned KeyReleased; 
static unsigned KeyRepeated;


typedef struct key_handler_t{
	struct key_handler_t *next;
	KSKeyHandler handler;
	void *call_data;
}key_handler_t;

static key_handler_t *KeyHandler = NULL;

static KSMemoryBlockManager KeyHandlerMgr;


#define PIE_SCR		0
#define PIE_X		7
#define PIE_Y		13
#define PIE_X2		0
#define PIE_Y2		14

#define iskanji(c)	((c) & 0x80)

/* input mode (Normal/Conv1/Conv2) */

static int InputMode;

/* virtual screen for drawing */
static unsigned MapBuf[10*5];

/* piemenu status */
static pieent_t **Pie;
static int Mode;
static int Dir1,Dir2;
static int Shift;
static int PieState;


#define INTENSITY	7	/* intensity for stick */

static int StickCurDir;

static int PressRight;
static int PressLeft;
static int PressUp;
static int PressDown;

#define STICK_NONE	4
#define STICK_SETTLE	-1

#define KEY_STICK	(KEY_UP1|KEY_DOWN1|KEY_LEFT1|KEY_RIGHT1)
#define KEY_CENTER	KEY_A
#define KEY_CANCEL	KEY_B

#define PIE_STATE_TOP		0
#define PIE_STATE_FLOAT		1
#define PIE_STATE_SUB		2
#define PIE_STATE_SELECT	3



/* LookUp table to get stick direction from key bitmap */

static int StickLUT[16] = {
		/* LDRU */
	4,	/* 0000 */
	1,	/* 0001 */
	5,	/* 0010 */
	2,	/* 0011 */
	7,	/* 0100 */
	4,	/* 0101 */
	8,	/* 0110 */
	5,	/* 0111 */
	3,	/* 1000 */
	0,	/* 1001 */
	4,	/* 1010 */
	1,	/* 1011 */
	6,	/* 1100 */
	3,	/* 1101 */
	7,	/* 1110 */
	4,	/* 1111 */
};


/* subkeyboard label */

static unsigned char *SubKeyboardLabel[5][4] = {
	{ "↑",  "←",  "→",  "↓",   }, /* raw */
	{ "↑",  "←",  "→",  "↓",   }, /* kbd */
	{ "前頁","後退","空白","次頁", }, /* kbd */
	{ "無変","後退","変換","反転", }, /* conv1 */
	{ "無変","取消","抹消","反転", }, /* conv2 */
};

/* keymap */

typedef struct{
	unsigned mask;
	unsigned char *str;
	int repeat;
}keymap_t;

static keymap_t Keymap_raw[] = {
	{ KEY_A,	KEYSTR_SELECT,		1 },
	{ KEY_B,	KEYSTR_CONVERSION,	1 },
	{ KEY_UP2,	KEYSTR_UP,		1 },
	{ KEY_DOWN2,	KEYSTR_DOWN,		1 },
	{ KEY_LEFT2,	KEYSTR_LEFT,		1 },
	{ KEY_RIGHT2,	KEYSTR_RIGHT,		1 },
	{ KEY_UP1,	KEYSTR_PRIOR,		1 },
	{ KEY_DOWN1,	KEYSTR_NEXT,		1 },
	{ KEY_LEFT1,	KEYSTR_HOME,		1 },
	{ KEY_RIGHT1,	KEYSTR_END,		1 },
	{ KEY_START,	KEYSTR_OPTION,		1 },
	{ 0,NULL,0 },
};

static keymap_t Keymap_kbd_shift[] = {
	{ KEY_UP2,	KEYSTR_PRIOR,		1 },
	{ KEY_DOWN2,	KEYSTR_NEXT,		1 },
	{ KEY_LEFT2,	KEYSTR_BACKSPACE,	1 },
	{ KEY_RIGHT2,	KEYSTR_SPACE,		1 },
	{ KEY_START,	KEYSTR_OPTION,		1 },
	{ 0,NULL,0 },
};

static keymap_t Keymap_kbd[] = {
	{ KEY_UP2,	KEYSTR_UP,		1 },
	{ KEY_DOWN2,	KEYSTR_DOWN,		1 },
	{ KEY_LEFT2,	KEYSTR_LEFT,		1 },
	{ KEY_RIGHT2,	KEYSTR_RIGHT,		1 },
	{ KEY_START,	KEYSTR_OPTION,		0 },
	{ 0,NULL },
};

static keymap_t Keymap_conv1[] = {
	{ KEY_UP2,	KEYSTR_NOCONV,		0 },
	{ KEY_DOWN2,	KEYSTR_TOGGLECONV,	0 },
	{ KEY_LEFT2,	KEYSTR_BACKSPACE,	1 },
	{ KEY_RIGHT2,	KEYSTR_CONVERSION,	1 },
	{ KEY_START,	KEYSTR_OPTION,		0 },
	{ 0,NULL,0 },
};

static keymap_t Keymap_conv2[] = {
	{ KEY_A,	KEYSTR_SELECT,		0 },
	{ KEY_B,	KEYSTR_CONVERSION,	1 },
	{ KEY_UP1,	KEYSTR_PRIOR,		1 },
	{ KEY_DOWN1,	KEYSTR_CONVERSION,	1 },
	{ KEY_LEFT1,	KEYSTR_PRIOR,		1 },
	{ KEY_RIGHT1,	KEYSTR_CONVERSION,	1 },
	{ KEY_UP2,	KEYSTR_NOCONV,		0 },
	{ KEY_DOWN2,	KEYSTR_TOGGLECONV,	0 },
	{ KEY_LEFT2,	KEYSTR_BACKSPACE,	1 },
	{ KEY_RIGHT2,	KEYSTR_DELETEDIC,	0 },
	{ KEY_START,	KEYSTR_OPTION,		0 },
	{ 0,NULL,0 },
};



/*
** clear stick status
*/

static void clear_stick( void )
{
	PressRight = 0;
	PressLeft = 0;
	PressUp = 0;
	PressDown = 0;

	StickCurDir = STICK_NONE;
}


/*
** get stick direction from key bitmap
*/

static int get_stick( unsigned k )
{
	int s;

	if( !(k & KEY_STICK) ){
		s = (StickCurDir == STICK_NONE) ? STICK_NONE : STICK_SETTLE;
		clear_stick();
		return s;
	}

	if( k & KEY_UP1 )
		PressUp = INTENSITY;
	else if( PressUp )
		PressUp--;

	if( k & KEY_DOWN1 )
		PressDown = INTENSITY;
	else if( PressDown )
		PressDown--;

	if( k & KEY_RIGHT1 )
		PressRight = INTENSITY;
	else if( PressRight )
		PressRight--;

	if( k & KEY_LEFT1 )
		PressLeft = INTENSITY;
	else if( PressLeft )
		PressLeft--;

	s = 0;
	if( PressUp )
		s |= 1;
	if( PressRight )
		s |= 2;
	if( PressDown )
		s |= 4;
	if( PressLeft )
		s |= 8;

	s = StickLUT[s];
	if( s == StickCurDir )
		return STICK_NONE;

	StickCurDir = s;
	return s;
}

/*
** set fontdata
*/

static void create_font( int font, unsigned char *label )
{
	int i;
	unsigned char bitmap[16];
	unsigned char buf[32];
	int code;

	font_get_data(font,2,buf);

	if( label == NULL ){
		for( i = 1 ; i < 32 ; i+=2 )
			buf[i] = 0;
	}else{
		if( iskanji(*label) ){
			code = (unsigned)label[0] << 8 | label[1];
			label += 2;
		}else{
			code = *label++;
		}
		text_get_fontdata(code,bitmap);

		if( *label ){
			if( iskanji(*label) )
				code = (unsigned)label[0] << 8 | label[1];
			else
				code = *label;
			text_get_fontdata(code,bitmap+8);
		}else{
			for( i = 0 ; i < 8 ; i++ ){
				bitmap[i+8] = bitmap[i] << 4;
				bitmap[i] >>= 4;
			}
		}
		for( i = 0 ; i < 16 ; i++ )
			buf[i*2+1] = bitmap[i];
	}
	font_set_colordata(font,2,buf);
}


/*
** draw piemenu to virtual screen for piemenu
*/

static void draw_piemenu( pieent_t *p, int dir, int select_bitmap )
{
	unsigned *bp;
	unsigned plt;
	int x,y;

	bp = MapBuf + (dir/3) * 10 + (dir%3) * 2;
	for( y = 0 ; y < 3 ; y++ ){
		for( x = 0 ; x < 3 ; x++,p++ ){
			if( select_bitmap & 1 )
				plt = KS_PLT_KEYBOARD;
			else
				plt = KS_PLT_KEYBOARD_SHADOW;
			
			select_bitmap >>= 1;

			*bp++ = plt | p->font;
			*bp++ = plt | p->font+1;
		}
		bp += 4;
	}
}


/*
** update piemenu and draw to screen
*/

static void update_piemenu( int state, int shift, int dir1, int dir2 )
{
	int i;

	for( i = 0 ; i < 5*10 ; i++ )
		MapBuf[i] = KS_PLT_KEYBOARD | KS_FONT_NONE;

	switch( state ){
	case PIE_STATE_TOP:
		draw_piemenu(Pie[shift],4,0x1ff);
		break;

	case PIE_STATE_FLOAT:
		draw_piemenu(Pie[shift],4,0);
		if( Pie[shift][dir1].item )
			draw_piemenu(Pie[shift][dir1].item,dir1,0x1ff);
		break;

	case PIE_STATE_SUB:
		if( Pie[shift][dir1].item )
			draw_piemenu(Pie[shift][dir1].item,dir1,0x1ff);
		break;

	case PIE_STATE_SELECT:
		if( Pie[shift][dir1].item )
			draw_piemenu(Pie[shift][dir1].item,dir1,1<<dir2);
		break;
	}
	screen_set_char(PIE_SCR,PIE_X,PIE_Y,10,5,MapBuf);
}



static void subkeyboard( void )
{
	int i;
	int font;
	unsigned *p;

	switch( InputMode ){
	case KS_INPUT_MODE_RAW:
		font = KS_FONT_SUBKEYBOARD;
		break;
	case KS_INPUT_MODE_KBD:
		font = Shift ? KS_FONT_SUBKEYBOARD+16 : KS_FONT_SUBKEYBOARD+8;
		break;
	case KS_INPUT_MODE_CONV1:
		font = KS_FONT_SUBKEYBOARD+24;
		break;
	case KS_INPUT_MODE_CONV2:
		font = KS_FONT_SUBKEYBOARD+32;
		break;
	default:
		return;
	}

	p = MapBuf;

	for( i = 0 ; i < 9 ; i++ ){
		if( i & 1 ){
			*p++ = KS_PLT_KEYBOARD | font++;
			*p++ = KS_PLT_KEYBOARD | font++;
		}else{
			*p++ = KS_PLT_KEYBOARD | KS_FONT_NONE;
			*p++ = KS_PLT_KEYBOARD | KS_FONT_NONE;
		}
	}
	screen_set_char(PIE_SCR,PIE_X2,PIE_Y2,6,3,MapBuf);
}




static void initialize_subkeyboard( void )
{
	int i,j,font;

	font = KS_FONT_SUBKEYBOARD;

	for( i = 0 ; i < 5 ; i++ ){
		for( j = 0 ; j < 4 ; j++ ){
			create_font(font,SubKeyboardLabel[i][j]);
			font += 2;
		}
	}
}


/*
** reset status
*/

void KSResetKeyboard( void )
{
	Shift = 0;
	Dir1 = 4;
	Dir2 = 4;
	update_piemenu(PIE_STATE_TOP,0,0,0);
	subkeyboard();
	clear_stick();
}


void KSSetKeyboardMode( int mode )
{
	int font,i,j,shift;
	pieent_t *p,*q;

	if( mode < 0 || mode > 2 )
		return;

	Mode = mode;
	Pie = PieTable[Mode];

	/*
	** create font characters
	*/

	font = KS_FONT_KEYBOARD;
	for( shift = 0 ; shift < 2 ; shift++ ){
		p = Pie[shift];
		for( i = 0 ; i < 9 ; i++,p++ ){
			p->font = font;
			create_font(font,p->label);
			font += 2;
			if( p->item ){
				q = (pieent_t*)p->item;
				for( j = 0 ; j < 9 ; j++,q++ ){
					q->font = font;
					create_font(font,q->label);
					font += 2;
				}
			}
		}
	}
	KSResetKeyboard();
}


/*
** input string
**
*/

static unsigned char *get_raw_keystr( keymap_t *keymap )
{
	for( ; keymap->mask ; keymap++ ){
		if( keymap->repeat ){
			if( KeyRepeated & keymap->mask )
				return keymap->str;
		}else{
			if( KeyPressed & keymap->mask )
				return keymap->str;
		}
	}
	return NULL;
}


unsigned char *input_pie( void )
{
	pieent_t *p;
	int stick,cancel;
	unsigned char *result;
	int nextstate;
	int mode;
	int shift;

	result = NULL;
	nextstate = -1;

	stick = get_stick(KeyState);
	cancel = KeyPressed & KEY_B;

	switch( PieState ){
	case PIE_STATE_TOP:
		if( cancel ){
			result = KEYSTR_CONVERSION;
		}else{
			shift = KeyState & KEY_A ? 1 : 0;
			if( shift != Shift ){
				Shift = shift;
				nextstate = PIE_STATE_TOP;
			}
			if( stick != STICK_SETTLE && stick != STICK_NONE ){
				Dir1 = stick;
				nextstate = PIE_STATE_FLOAT;
			}
		}
		break;

	case PIE_STATE_FLOAT:
		if( cancel ){
			nextstate = PIE_STATE_TOP;
		}else{
			if( KeyPressed & KEY_A ){
				Shift = !Shift;
				nextstate = PIE_STATE_FLOAT;
			}
			if( stick == STICK_SETTLE ){
				nextstate = PIE_STATE_SUB;
			}else if( stick != STICK_NONE ){
				Dir1 = stick;
				nextstate = PIE_STATE_FLOAT;
			}
		}
		break;

	case PIE_STATE_SUB:
		if( cancel ){
			nextstate = PIE_STATE_TOP;
		}else if( KeyPressed & KEY_A ){
			Dir2 = 4;
			nextstate = PIE_STATE_SELECT;
		}else if( stick != STICK_NONE ){
			Dir2 = stick;
			nextstate = PIE_STATE_SELECT;
		}
		break;

	case PIE_STATE_SELECT:
		if( cancel ){
			nextstate = PIE_STATE_TOP;
		}else if( KeyReleased & KEY_A ){
			if(  (KeyState & KEY_STICK) == 0 ){
				p = Pie[Shift][Dir1].item;
				if( p ){
					result = p[4].item;
					if( result == NULL )
						result = p[4].label;
				}
				nextstate = PIE_STATE_TOP;
			}else{
				Dir2 = StickCurDir;
			}
		}else if( stick == STICK_SETTLE ){
			if( (KeyState & KEY_A) == 0 ){
				p = Pie[Shift][Dir1].item;
				if( p ){
					result = p[Dir2].item;
					if( result == NULL )
						result = p[Dir2].label;
				}
				nextstate = PIE_STATE_TOP;
			}else{
				Dir2 = 4;
			}
		}else if( KeyPressed & KEY_A ){
			Dir2 = 4;
			nextstate = PIE_STATE_SELECT;
		}else if( stick != STICK_NONE ){
			Dir2 = stick;
			nextstate = PIE_STATE_SELECT;
		}
		break;
	}

	if( result ){
		switch( result[0] ){
		case KEYCODE_HIRAGANA:
			mode = KS_KBD_MODE_HIRA;
			break;
		case KEYCODE_KATAKANA:
			mode = KS_KBD_MODE_KATA;
			break;
		case KEYCODE_ASCII:
			mode = KS_KBD_MODE_ASCII;
			break;
		default:
			mode = -1;
			break;
		}
		if( mode >= 0 ){
			KSSetKeyboardMode(mode);
			result = NULL;
		}
	}

	if( nextstate >= 0 ){
		PieState = nextstate;
		update_piemenu(PieState,Shift,Dir1,Dir2);
	}

	return result;
}


int KSGetInputMode( void )
{
	return InputMode;
}


void KSSetInputMode( int mode )
{
	if( mode < KS_INPUT_MODE_RAW || mode > KS_INPUT_MODE_CONV2 )
		return;

	InputMode = mode;
	subkeyboard();
}


void KSInitializeKeyboard( void )
{
	int i;
	int tick;

	KSSetMemoryBlockManager(&KeyHandlerMgr,sizeof(key_handler_t));

	InputMode = KS_INPUT_MODE_KBD;
	Shift = 0;
	PieState = PIE_STATE_TOP;

	tick = sys_get_tick_count();

	KeyState = key_press_check();
	for( i = 0 ; i < KEY_NUM ; i++ )
		KeyTime[i] = tick;

	initialize_subkeyboard();
	KSSetKeyboardMode(KS_KBD_MODE_HIRA);
	subkeyboard();

	KeyRepeatDelay = KEY_REPEAT_DELAY;
	KeyRepeatInterval = KEY_REPEAT_INTERVAL;
}

/*
** key repeat
*/

void KSGetKeyRepeat( unsigned *delay, unsigned *interval )
{
	*delay = KeyRepeatDelay;
	*interval = KeyRepeatInterval;
}


void KSSetKeyRepeat( unsigned delay, unsigned interval )
{
	KeyRepeatDelay = delay;
	KeyRepeatInterval = interval;
}

/*
** key
*/

int KSKeyboard( unsigned tick )
{
	int i;
	unsigned k,mask;
	int shift;
	unsigned char *result;
	KSMsg *msg;
	unsigned code;



	/*
	** update status
	*/

	KeyPressed = 0;
	KeyReleased = 0;
	KeyRepeated = 0;

	k = key_press_check();
	mask = 1 << KEY_MIN;
	for( i = 0 ; i < KEY_NUM ; mask <<= 1, i++ ){
		if( k & mask ){
			if( KeyState & mask ){
				if( relative_time(tick,KeyTime[i]) >= 0 ){
					KeyRepeated |= mask;
					KeyTime[i] = tick + KeyRepeatInterval;
				}
			}else{
				KeyPressed |= mask;
				KeyRepeated |= mask;
				KeyTime[i] = tick + KeyRepeatDelay;
			}
		}else{
			if( KeyState & mask ){
				KeyReleased |= mask;
			}
		}
	}
	KeyState = k;


	/*
	** keyboard
	*/

	switch( InputMode ){
	case KS_INPUT_MODE_RAW:
		result = get_raw_keystr(Keymap_raw);
		break;

	case KS_INPUT_MODE_KBD:
		shift = Shift;
		if( Shift )
			result = get_raw_keystr(Keymap_kbd_shift);
		else
			result = get_raw_keystr(Keymap_kbd);

		if( result == NULL ){
			result = input_pie();
		}else if( PieState != PIE_STATE_TOP ){
			KSResetKeyboard();
			shift = Shift;
		}
		if( shift != Shift )
			subkeyboard();
		break;

	case KS_INPUT_MODE_CONV1:
		result = get_raw_keystr(Keymap_conv1);
		if( result == NULL )
			result = input_pie();
		break;

	case KS_INPUT_MODE_CONV2:
		result = get_raw_keystr(Keymap_conv2);
		break;
	}

	if( result == NULL )
		return 0;

	if( KeyHandler == NULL || KeyHandler->handler == NULL )
		return 0;

	msg = KSAllocateMsg();
	if( msg == NULL )
		return -1;	/* TODO: this is fatal error */

	msg->type = KS_MSG_STRING;
	msg->string.receiver = KeyHandler->handler;
	msg->string.call_data = KeyHandler->call_data;

	i = 0;
	while( *result && i < 3 ){
		code = *result++;
		if( iskanji(code) )
			code = code << 8 | *result++;
		msg->string.str[i++] = code;
	}
	msg->string.str[i] = 0;

	KSSendMsg(msg);
	return 0;
}


int KSSetKeyHandler( KSKeyHandler handler, void *call_data )
{
	key_handler_t *p;

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

	p->handler = handler;
	p->call_data = call_data;
	p->next = KeyHandler;
	KeyHandler = p;
	return 0;
}


void KSRemoveKeyHandler( void )
{
	key_handler_t *p;

	p = KeyHandler;
	if( p == NULL )
		return;

	KeyHandler = p->next;
	KSFreeMemoryBlock(&KeyHandlerMgr,p);
}
