/* vi: set sw=4 ts=4: */
/*
 * CCE - Console Chinese Environment -
 * Copyright (C) 1998-2003 Rui He (rhe@3eti.com)
 *
 * 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 ``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 TERRENCE R. LAMBERT 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.
 *
 */

/* 
   This input method is different than xcin's bimsphone and chewing which will
   do candidate-autoselect(based on libtabe). It won't do candidate-autoselect,
   you need to manually select. Since most zhuyin keyboard mappings have conflict
   between input and select keys(0-9 now) and SPACE is used for 1st tone, we 
   have to stay in Shift-Select state until user press SPACE(sometimes SPACE twice,
   one for 1st tone and one for Direct-Selct). This is not so convenient!

   In order to keep the same interface like traditional single-char zhuyin(after 
   tone key, candidates can be selected by 0-9), we will go into Direct-Select
   state if a tone key is pressed.
   
   So for phrase input, usually you just need to enter one tone key for last char,
   (otherwise you may have conflict, 0-9 as select keys have higher priority than
   as select keys). It's OK for phrase input since not too many duplicate mappings
   for phrase.
*/
#include 	<stdio.h>
#include 	<unistd.h>
#include 	<stdlib.h>
#include	<errno.h>
#include	<ctype.h>
#include	<string.h>
#include	<fcntl.h>

#include 	<defs.h>
#include	<font.h>
#include	<vc.h>
#include 	<hzinput.h>
#include	<pinyin.h>
#include	<zhuyin.h>
#include	<key.h>
#include 	<errors.h>
#include	<convert.h>
#include	<getcap.h>

#ifdef HAVE_SYS_MMAN_H
#include	<sys/mman.h>
#endif

#define DEFAULT_PINYIN_KEYBOARD KEYBOARD_MAP_PINYIN /* TONEPY */

static int MmapZhuyinTable(ZhuyinModule *inmd, char *pathname);

static void CreateZYMsg(ZhuyinModule *inmd)
{
	int i,j;

	strncpy(inmd->iapybuf, inmd->HZbuf, sizeof(inmd->iapybuf));

    if (inmd->EnglishPos > 0) /* in English mode */
	{
		strcat(inmd->iapybuf, inmd->ZYbuf);
	    return;
	}

	if (inmd->HZbufLen > 0)
		strcat(inmd->iapybuf, " "); /* add one space */

	if (IS_KB_PINYIN(inmd->KeyboardMode))
	{
		for(i=inmd->CurZYPos; i<inmd->CurZYNum; i++)
		{
		
    		strcat(inmd->iapybuf,inmd->SavedPYStr[i]);   /* MAX_PY_LEN = 7 */
			if (inmd->SavedPYStr[i][ strlen(inmd->SavedPYStr[i]) - 1 ] == '\'')
      			continue;
    		else strcat(inmd->iapybuf," ");
		}
  	}
	else
	{
		unsigned char *p = inmd->iapybuf;
			
		while(*p) p++; /* skip to the zero  */
	
		for(i = inmd->CurZYPos; i < inmd->CurZYNum; i++)
		{	
			j = 0;
			while(inmd->CurZYStr[i][j])
			{
				if (inmd->CurZYStr[i][j] <= ZHUYIN_TOTAL_NUM ||
					inmd->CurZYStr[i][j] > ZHUYIN_FIRST_TONE) /* 37: 1st tone use SPACE to show it */
				{
					strncpy(p, (lInfo.sysCoding == CODE_BIG5? Big5PhoneChars : GBPhoneChars)
						+ 2 * (inmd->CurZYStr[i][j]-1), 2);
					p += 2;
				}
				j++;
			}
			*p++ = ' ';
		}
		*p++ = '\0';
	}
}

#define ENGSTR_MAXLEN (cur_table->SelAreaWidth + cur_table->InputAreaWidth-8)

void RefreshZYInputArea(ZhuyinModule *inmd )
{
	int pos = cur_table->InputAreaX;
	int len = strlen(inmd->iapybuf);

    if (inmd->EnglishPos > 0)
    {
        u_char *str = inmd->iapybuf;

        if (len > ENGSTR_MAXLEN)
        {
            str += len - ENGSTR_MAXLEN;
            len = ENGSTR_MAXLEN;
        }
        InputAreaOutput(lInfo.sysCoding, pos, "Eng: ", INPUT_FGCOLOR, INPUT_BGCOLOR);
        pos += 5;
        InputAreaOutput(lInfo.sysCoding, pos, str, INPUT_FGCOLOR, INPUT_BGCOLOR);
        pos += len;
    }
    else /* output candidates */
    {
	
		InputAreaOutput(lInfo.sysCoding, pos, inmd->iapybuf, INPUT_FGCOLOR, INPUT_BGCOLOR);
		pos += strlen(inmd->iapybuf);
	
		if (pos > cur_table->SelAreaX) pos = cur_table->SelAreaX;
		else
		{	
			for(; pos < cur_table->SelAreaX; pos++)
				pVideoInfo->input_sput(pos,' ',INPUT_BGCOLOR,INPUT_BGCOLOR);   
		}

		InputAreaOutput(lInfo.sysCoding, pos, inmd->iahzbuf, INPUT_FGCOLOR, INPUT_BGCOLOR); 
		pos += strlen(inmd->iahzbuf);
	}

	for(; pos < cur_table->SelAreaX + cur_table->SelAreaWidth; pos++)
	   pVideoInfo->input_sput(pos,' ',INPUT_BGCOLOR,INPUT_BGCOLOR);
}

void RefreshZhuyinInput(void)
{
	RefreshZYInputArea((ZhuyinModule *)cur_table->PrivateData);
}

void ResetZhuyinInput(ZhuyinModule *inmd)
{
	inmd->Keybuf[0] = inmd->ZYbuf[0] = inmd->HZbuf[0] = inmd->HZPYKey[0] ='\0';
  	inmd->HZbufLen = inmd->ZYbufLen = inmd->KeybufLen = 0;

  	inmd->iapybuf[0] = inmd->iahzbuf[0] = '\0';

	inmd->EnglishPos = 0;

  	inmd->TotalSelNum = inmd->CurZYNum = inmd->CurZYPos = 0;
	inmd->FirstZiNum = -1; /* flag to find again */
}

void ChangeZhuyinKeyboardMap(int offset)
{
	ZhuyinModule *inmd = (ZhuyinModule *)cur_table->PrivateData;
	inmd->KeyboardMode += offset;
	if (inmd->KeyboardMode > KEYBOARD_MAP_LASTZY)
		inmd->KeyboardMode = KEYBOARD_MAP_PINYIN;

	if (IS_KB_ZHUYIN(inmd->KeyboardMode) &&
		lInfo.sysCoding != CODE_GB && lInfo.sysCoding != CODE_GBK 
		  && lInfo.sysCoding != CODE_BIG5)
		inmd->KeyboardMode = DEFAULT_PINYIN_KEYBOARD;

	cur_table->FirstInputSelectKeyConflict = IS_KB_CONFLICT(inmd->KeyboardMode);

	ResetZhuyinInput(inmd);
	if (offset != 0)
	{
   		strcpy(inmd->iahzbuf, CiyinKeyboardMapStr[inmd->KeyboardMode]);
		RefreshZYInputArea(inmd);
	}
	inmd->iahzbuf[0] = '\0';  /* temporary display */
}

hz_input_table* Init_ZhuyinInput(hz_input_table *table, char *filename, int sysCoding)
{   
	ZhuyinModule *inmd; 

	table->PrivateData = malloc(sizeof(ZhuyinModule)); /* not zeroed out!!! */
	inmd = (ZhuyinModule *)table->PrivateData;
	if (!inmd || !memset(inmd, 0, sizeof(ZhuyinModule)) 
		)
	{
		error("Failed to init Zhuyin Input Module!\n");
		free(table);
		return NULL;
	}

    strcpy(table->cname, Zhuyin_CNameStr[sysCoding]);
	table->PhraseFileSize = 0;

	fclose(table->TableFile);

	inmd->OldCoding = table->encoding;
	table->encoding = sysCoding;
	
    inmd->KeyboardMode = ZhuyinKeyboardMode;
    if (inmd->KeyboardMode == KEYBOARD_MAP_AUTO || inmd->KeyboardMode > KEYBOARD_MAP_LASTZY) /* 0 */
    {
        if (lInfo.sysCoding == CODE_BIG5)
            inmd->KeyboardMode = KEYBOARD_MAP_ZOZY;
        else inmd->KeyboardMode = DEFAULT_PINYIN_KEYBOARD; 
    }

    if (IS_KB_ZHUYIN(inmd->KeyboardMode) && lInfo.sysCoding != CODE_GB && 
		lInfo.sysCoding != CODE_GBK && lInfo.sysCoding != CODE_BIG5) 
        inmd->KeyboardMode = DEFAULT_PINYIN_KEYBOARD;

    table->FirstInputSelectKeyConflict = IS_KB_CONFLICT(inmd->KeyboardMode);

	if (MmapZhuyinTable(inmd, filename)) 
	{
		free(table->PrivateData);		
		free(table);	
		return NULL; 
	}

	table->FontMapped = 1; /* already mapped! since pinyin will use system's encoding */

    table->auto_select = 0; /* 1; // we dont' need it, right? */
	table->item = (ITEM *)-1; /* 0 unmapped, -1 (no table) other: mapped */
	
  	ResetZhuyinInput(inmd);

	table->InputAreaX = cal_input_xpos(table);
    table->InputAreaWidth = ZHUYIN_AREA_WIDTH; /* 20 */
    table->SelAreaX = table->InputAreaX + table->InputAreaWidth; 
	table->SelAreaWidth = dispInfo.tx_avail - table->SelAreaX - 2
		 - dispInfo.show_terminal * WINDOW_TERMINAL_STR_WIDTH; /* 18 or 0 */

	if (table->SelAreaWidth > MAX_CANDIDATE_BUF) table->SelAreaWidth = MAX_CANDIDATE_BUF;

	table->HZFilter = Zhuyin_HZFilter;
	table->RefreshInput = RefreshZhuyinInput;
	table->ChangeKeyboardMap = ChangeZhuyinKeyboardMap;

    return table;
}

static int MmapZhuyinTable(ZhuyinModule *inmd, char *pathname)
{
	int fd;
	char *p;
	int  size;

	if ( (fd = open(pathname, O_RDWR|CCE_OPENFLAG)) < 0)
	{
		warn("File %s can't open for read-write.\n",pathname);
		return -1;
	}

	if (lseek(fd,-4,SEEK_END) == -1 || 
      	read(fd, &(inmd->sys_size), sizeof(int)) != sizeof(int) /* ||
      	inmd->sys_size != ftell(stream)-4 */)   /* error!! */
  	{
    	warn("%s is not a valid zhuyin phrase file, size=%d.\n",pathname, inmd->sys_size);
		return -1;
  	}
 
   	p = (char *)mmap(0, inmd->sys_size /*+ sizeof(hz_input_table) + sizeof(inmd->pytab) */, 
			PROT_READ | PROT_WRITE, MAP_SHARED, fd /* fileno(stream) */, 0);
   	if (p  == MAP_FAILED)
	{
		warn("Mmapping %s failed. errno=%d %m\n", pathname, errno);
		return -1;
	}

	inmd->MmapPtr = (void *)p;  /* save it */

	p += sizeof(hz_input_table);

	inmd->YinTable = (Yin *)p;
	p += sizeof(Yin) * TOTAL_ZHUYIN_NUM;  /* skip the YinTable */

	size = *((int *)p); /* ZiLibSize */
	p += sizeof(int);
	inmd->ZiLib = (unsigned short *)p; 
	p += size; /* skip the ZiLib */

	inmd->ZiNum = *((int *)p); /* ZiNum */
    p += sizeof(int);
	inmd->ZiTable = (Zi *)p;
	p += inmd->ZiNum * sizeof(Zi);

  	size = *((int *)p); /* CiLibSize */
    p += sizeof(int);
	inmd->CiLib = p;

	close(fd);
  	return 0;
}

void ZhuyinInputCleanup(hz_input_table *table)
{
	ZhuyinModule *inmd = (ZhuyinModule *)(table->PrivateData);
	
	if (inmd->MmapPtr != MAP_FAILED && inmd->MmapPtr != NULL)
		munmap(inmd->MmapPtr, inmd->sys_size);

}

static u_char *GetPhraseAndConvert(ZhuyinModule *inmd, SelItem *p, u_char *saveOld)
{
	static u_char temp[2*MAX_ZYPHRASE_LEN+1];
	int i, len = p->len;
	u_char *dest;

	dest = temp;

	if (saveOld)
	{
		*saveOld++ = (u_char)(p->zi >> 8);
		*saveOld++ = (u_char)(p->zi & 0xFF);
	}
	ConvertEncoding_Char(inmd->OldCoding /*CODE_BIG5*/, 
		lInfo.sysCoding, p->zi >> 8, p->zi & 0xFF, dest, dest+1);
	dest += 2;	

	if (len > 0)
	{
		u_char *cp;

		if (len > MAX_ZYPHRASE_LEN) len = MAX_ZYPHRASE_LEN; 
		
		cp = p->ptr;

   		for(i = 0; i < len; i++) 
		{
			if (saveOld)
			{
				*saveOld++ = cp[0];
				*saveOld++ = cp[1];
			}
			ConvertEncoding_Char(inmd->OldCoding /*CODE_BIG5*/, 
				lInfo.sysCoding, cp[0], cp[1], dest, dest+1);
			cp += 2;
			dest += 2;
		}
	}
	
	*dest = '\0';
	if (saveOld) *saveOld = '\0';
	
	return temp;
}

static void FillForwardSelection(ZhuyinModule *inmd,int startpos)
{
    char *iahzbuf=inmd->iahzbuf;

    int i,count;
    char strtmp[2*MAX_ZYPHRASE_LEN+10];
	
    if (startpos > inmd->TotalSelNum - 1 || startpos < 0)
        return ;   /* non-forwardable, keep the iahzbuf intact */

	if (startpos == 0) CurrentPage = 0;
	if (CurrentPage < MAX_PAGE_INDEX)
		SavedPageIndex[CurrentPage] = startpos;
	
	for(i = 0; i < NUM_ZHUYIN_CAND; i++) inmd->CandidateIndex[i] = -1; /* make it invalid first */

    iahzbuf[0] = '\0';
    if (inmd->TotalSelNum < 1) 
	{
		return;  /* clear the iahzbuf */
	}

    count = 0;   /* backup the starting position */
    inmd->SelStartPos = startpos;
    inmd->SelEndPos = startpos - 1;
	snprintf(inmd->iahzbuf, sizeof(inmd->iahzbuf), inmd->SelStartPos > 0 ? "< %s" : " %s",
			( IsDirectSelect ? "" : "Shift-" ));

    for(; inmd->SelEndPos < inmd->TotalSelNum-1 && count < NUM_ZHUYIN_CAND; inmd->SelEndPos++)
  	{
		strncpy(SelTab[count], GetPhraseAndConvert(inmd, inmd->CurSel + inmd->SelEndPos + 1, NULL),
			                sizeof(SelTab[0]));
	
		for(i = 0; i < count; i++)
			if (!strcmp(SelTab[i], SelTab[count]))  
			{
				break; /* try next one */
			}
		if (i < count) continue;  /* duplicate */

		snprintf(strtmp,sizeof(strtmp),"%d%s ", (count+1)%NUM_ZHUYIN_CAND, SelTab[count]);

		if ( (strlen(iahzbuf)+strlen(strtmp)+2) <= (u_int)cur_table->SelAreaWidth)
	   	{
			strcat(iahzbuf,strtmp);
			inmd->CandidateIndex[count++] = inmd->SelEndPos+1; /* record the mapping */
		}
		else break;
	}

	i = strlen(iahzbuf);
	if (inmd->SelEndPos < inmd->TotalSelNum - 1 && count >= 1)
	{
	  	iahzbuf[i] = '>';
		iahzbuf[i+1] = '\0';
	}
}

static void FillBackwardSelection(ZhuyinModule *inmd,int lastpos)
{
	char *iahzbuf=inmd->iahzbuf;
	int count,ialen;
	char strbuf[2*MAX_ZYPHRASE_LEN+10];

	if (lastpos < 0 || lastpos > inmd->TotalSelNum-1)
		return;   /* iahzbuf intact */
	iahzbuf[0] = '\0';
	if (inmd->TotalSelNum < 1) return; /* clear iahzbuf */

	count = 0;
	inmd->SelEndPos = lastpos;
	ialen = 2;   /* leftmost "< " or "  " */
	inmd->SelStartPos = lastpos+1;
	while(inmd->SelStartPos > 0 && count < NUM_ZHUYIN_CAND)
	{
		strncpy(strbuf, GetPhraseAndConvert(inmd, inmd->CurSel+inmd->SelStartPos-1,NULL), 
			sizeof (strbuf));

		ialen += 2 + strlen(strbuf);
		if (ialen+2 > cur_table->SelAreaWidth) break;
        count++;
        inmd->SelStartPos--;
    }
    FillForwardSelection(inmd,inmd->SelStartPos);
}

static void FillMatchZi(ZhuyinModule *inmd, int index, int tone)
{
	Yin *y = &inmd->YinTable[index];
	unsigned short *pZi = inmd->ZiLib + y->offset;
	int i, j, k;
	
	for(i = 0; i < MAX_TONE_NUM; pZi += y->ZiNum[i], i++, tone >>= 1)
	{
		if ( y->ZiNum[i] == 0 || (tone & 1) == 0 ) continue;
		for(j = 0; j < y->ZiNum[i]; j++)
		{
			if (inmd->FirstZiNum >= sizeof(inmd->FirstZiAry)/sizeof(inmd->FirstZiAry[0]))
				break;
			
			if (ConvertEncoding_Char( inmd->OldCoding /*CODE_BIG5*/,
                     lInfo.sysCoding, pZi[j] >> 8, pZi[j] & 0xFF, NULL, NULL) <= 0)
				continue; /* can't convert to target encoding */

			for(k = 0; k < inmd->FirstZiNum; k++)
				if (pZi[j] == inmd->FirstZiAry[k]) 
				{
					break;
				}
			
			if ( k == inmd->FirstZiNum) 
				inmd->FirstZiAry[inmd->FirstZiNum++] = pZi[j];
		}
	}
}

static int FindFirstChar(ZhuyinModule *inmd, u_short ZYKey[], u_short ZYMask[])
{
	int i, j;
	int key, tone, endkey;

	inmd->FirstZiNum = 0; /* clear it  */

	key = ZYKey[0] & 0x07FF & ZYMask[0]; /* this one is the starting key without tone */
	tone = (ZYKey[0] & TONE_MASK) >> TONE_OFFSET;
	if (tone == 0) tone = 0x1F; /* enable all bits */

	endkey = (key & 0x07C0) + 0x0040; /* won't bigger than TOTAL_ZHUYIN_NUM */
	for(j = key; j < endkey /* TOTAL_ZHUYIN_NUM */; j++)
	{
		if ((j & ZYMask[0]) == key) /* matched */
		{
			FillMatchZi(inmd, j, tone);
		}
	}

	for(i = 0; i < inmd->FirstZiNum; i++)
	{
		int freq_i, freq_j;
		u_short zi_i, zi_j;
		
		zi_i = inmd->FirstZiAry[i];
		freq_i = inmd->ZiTable[ CalculateCharIndex(inmd->OldCoding, 
				zi_i >> 8, zi_i & 0xFF) ].freq;

		for(j = i+1; j < inmd->FirstZiNum; j++)
		{
			zi_j = inmd->FirstZiAry[j];
			freq_j = inmd->ZiTable[ CalculateCharIndex(inmd->OldCoding,
					zi_j >> 8, zi_j & 0xFF)].freq;
			if (freq_i < freq_j)
			{
				freq_i = freq_j;
				inmd->FirstZiAry[i] = zi_j;
				inmd->FirstZiAry[j] = zi_i;
				zi_i = zi_j;
			}
		}
	}

	return 0;
}

static int TestPhraseZhuyin(ZhuyinModule *inmd, u_char *Zi, int cilen, int yinlen,
		u_short ZYKey[], u_short ZYMask[])
{
	int i,j;
	int index, key;
	
	for(i = 0; i < cilen; i++)
	{
		if (ConvertEncoding_Char( inmd->OldCoding /*CODE_BIG5*/,
                     lInfo.sysCoding, Zi[0], Zi[1], NULL, NULL) <= 0)
				return 0; /* can't convert to target encoding */

		if (i < yinlen)
		{
			index = CalculateCharIndex( inmd->OldCoding, Zi[0], Zi[1]); 
			key = ZYKey[i] & ZYMask[i]; 	
			for(j = 0; j < MAX_YIN_NUM; j++)
			{
				if (inmd->ZiTable[index].yin[j] == 0) 
					return 0; /* no match */
			
				if ((inmd->ZiTable[index].yin[j] & ZYMask[i]) == key)
				{
					break; /* found */
				}
			}
		
			if (j >= MAX_YIN_NUM) /* no matched */
				return 0;
		}

		Zi += 2;
	}

	return 1; 	
}

static void SortMatchPhrase(ZhuyinModule *inmd, int start, int end)
{
	int i, j;
	u_short freq_i, freq_j;
	SelItem tmpSel;

	for(i = start; i < end; i++)
	{
		freq_i = *(ZYFREQTYPE *)(inmd->CurSel[i].ptr + 2 * inmd->CurSel[i].len);
		for(j = i+1; j < end; j++)
		{
			freq_j = *(ZYFREQTYPE *)(inmd->CurSel[j].ptr + 2 * inmd->CurSel[j].len);
			if (freq_i < freq_j)
			{
				freq_i = freq_j;
				tmpSel = inmd->CurSel[i];
				inmd->CurSel[i] = inmd->CurSel[j];
				inmd->CurSel[j] = tmpSel;
			}
		}
	}
}

static int FindMatchPhrase(ZhuyinModule *inmd, u_short Zi, int cilen, int yinlen,
		u_short ZYKey[], u_short ZYMask[])
{
	int i;
	int index, count, curnum;
	u_char *p;
	
	index = CalculateCharIndex(inmd->OldCoding, Zi >> 8, Zi & 0xFF); 
	count = (cilen == 1 ? inmd->ZiTable[index].phnum.PhraseNum1 : inmd->ZiTable[index].phnum.PhraseNum[cilen]);
	if (count == 0) return 0;

	curnum = inmd->TotalSelNum;

	p = inmd->CiLib + inmd->ZiTable[index].offset;
	for(i = 1; i < cilen; i++)
		p +=  (i == 1 ? inmd->ZiTable[index].phnum.PhraseNum1 : inmd->ZiTable[index].phnum.PhraseNum[i]) *(2*i + sizeof(ZYFREQTYPE));

	for(i = 0; i < count; i++)
	{
		if (TestPhraseZhuyin(inmd, p, cilen, yinlen, ZYKey, ZYMask)) /* match */
		{
			SelItem *pSel = &inmd->CurSel[inmd->TotalSelNum];
			
			pSel->zi = Zi;
			pSel->len = cilen;
			pSel->ptr = p;
			inmd->TotalSelNum++;	

			if (inmd->TotalSelNum >= MAX_CAND_NUM) break; /* overflow */
		}	
		p += 2*cilen + sizeof(ZYFREQTYPE);
	}

	return inmd->TotalSelNum - curnum;
}

static int FindMatchCharPhrase(ZhuyinModule *inmd, u_short ZYKey[], u_short ZYMask[],int ZYNum)
{
	int i, j;
	int CurSelNum;
	
	inmd->TotalSelNum = 0;
	inmd->iahzbuf[0] = '\0'; /* clear it */
	CurrentPage = 0;

	if (!IS_KB_CONFLICT(inmd->KeyboardMode)) 

		IsDirectSelect = 1; /* pinyin is DirectSlect if last key is 0-5 tone */
	else
	{
		if (inmd->ZYbufLen == 0 || inmd->ZYbuf[inmd->ZYbufLen-1] >= ZHUYIN_FIRST_TONE)
			IsDirectSelect = 1;
		else IsDirectSelect = 0; 
	}

	if (ZYNum == 1 || inmd->FirstZiNum == -1) /* more than 1, then it's already filled */
		FindFirstChar(inmd, ZYKey, ZYMask);

	if (ZYNum > 1)
	{
		for(j = ZYNum; j > 1; j--)
		{
			CurSelNum = inmd->TotalSelNum;
			for(i = 0; i < inmd->FirstZiNum; i++)
			{
				FindMatchPhrase(inmd, inmd->FirstZiAry[i], j-1, j-1, ZYKey+1, ZYMask+1); 
				if (inmd->TotalSelNum >= MAX_CAND_NUM)
				{
					j = 1; /* end condition for j loop */
					break;
				}
			}
			SortMatchPhrase(inmd, CurSelNum, inmd->TotalSelNum); /* sorted */
		}

	}

	for(i = 0; i < inmd->FirstZiNum; i++)
	{
		if (inmd->TotalSelNum >= MAX_CAND_NUM) break; /* overflow */
		inmd->CurSel[inmd->TotalSelNum].zi = inmd->FirstZiAry[i];
		inmd->CurSel[inmd->TotalSelNum++].len = 0; /* single char */
	}

	return inmd->TotalSelNum;
}

static void OutputHZBuf(ZhuyinModule *inmd, int index, int hzlen, int tty_fd)
{
	ZYFREQTYPE *pFreq;
	unsigned char LastChar[2];

   	if ( hzlen == inmd->HZbufLen )  /* found an existing phrase */
   	{
		if (index >= 0) /* index==-1, in PinyinArea */
		{
			if (inmd->CurSel[index].len == 0) /* single char */
			{
				int char_index = CalculateCharIndex(inmd->OldCoding, inmd->CurSel[index].zi >> 8, 
						inmd->CurSel[index].zi & 0xFF);
				pFreq = &inmd->ZiTable[char_index].freq;
			}
			else /* phrase */
			{
				pFreq = (ZYFREQTYPE *)(inmd->CurSel[index].ptr + 2*inmd->CurSel[index].len);
			}	

			if (*pFreq < MAX_FREQ_ALLOWED) *pFreq += 100;  /* How to adjust frequency? */
		}
   	}
   	else if(inmd->HZbufLen > 1) /* user just make a new phrase */
   	{
   	}

	OutputCJKStr(tty_fd, inmd->HZbuf, 2 * inmd->HZbufLen);

	LastChar[0] = inmd->HZbuf[2*inmd->HZbufLen-2];
	LastChar[1] = inmd->HZbuf[2*inmd->HZbufLen-1]; /* save it, will reset next */

   	ResetZhuyinInput(inmd);

   	RefreshZYInputArea(inmd);

    if (IsAssocEnable && cur_table->AssocFile)
	{
        FindAssociateKeyAndFill(LastChar);  /* using the input method's encoding to find */
	}
}

static void OutputZYBuf(ZhuyinModule *inmd, int tty_fd)
{
    if (inmd->HZbufLen > 0) /* 1 Char 2 Bytes, should we output the selected hanzis? */
        OutputCJKStr(tty_fd, inmd->HZbuf, 2 * inmd->HZbufLen);

    if (inmd->ZYbufLen > 0)
        OutputCJKStr(tty_fd, inmd->ZYbuf, inmd->ZYbufLen);

    ResetZhuyinInput(inmd);

    RefreshZYInputArea(inmd);
}

static int SelectKeyPressed(ZhuyinModule *inmd, int idx, int tty_fd)
{
	int j, index;
	int hzlen;

	index = inmd->CandidateIndex[idx]; /* get the mapping */

	if (index > inmd->SelEndPos)  return 1;  /* out of range selection! we ignore it */

   	strcat(inmd->HZbuf, GetPhraseAndConvert(inmd, inmd->CurSel + index, 
				inmd->HZbufOldCoding + 2*inmd->HZbufLen));
	hzlen = strlen(SelTab[idx])/2;
	inmd->HZbufLen += hzlen;

  	if (inmd->HZbufLen >= inmd->CurZYNum)
  	{

		OutputHZBuf(inmd, index, hzlen, tty_fd);
  	}
  	else  /* not yet, some unselected zhuyin exist */
  	{
		inmd->CurZYPos += hzlen;

		inmd->FirstZiNum = -1; /* force it to find again */
      	FindMatchCharPhrase(inmd, inmd->CurZYKey + inmd->CurZYPos, 
			inmd->CurZYMask + inmd->CurZYPos, inmd->CurZYNum - inmd->CurZYPos);

      	FillForwardSelection(inmd,0);

      	inmd->ZYbuf[0] = '\0';  /* put the rest of the pinyin into ZYbuf */
		for(j = inmd->CurZYPos; j < inmd->CurZYNum; j++)
		{	 
			if (IS_KB_ZHUYIN(inmd->KeyboardMode)) 
            	strcat(inmd->ZYbuf, inmd->CurZYStr[j]);
			else if (IS_KB_SHUANGPIN(inmd->KeyboardMode))
				strcat(inmd->ZYbuf, inmd->CurSPStr[j]);
			else strcat(inmd->ZYbuf, inmd->SavedPYStr[j]);
		}
		inmd->ZYbufLen = strlen(inmd->ZYbuf);

      	CreateZYMsg(inmd);
      	RefreshZYInputArea(inmd);
   	}

	return 1;
}

static int ProcessZhuyinKeys(ZhuyinModule *inmd, u_char ch, int tty_fd)
{
  
	if (IS_KB_ZHUYIN(inmd->KeyboardMode))
	{
		ch = ((u_int)strchr(InputKeyMap[inmd->KeyboardMode], ch) - 
				(u_int)InputKeyMap[inmd->KeyboardMode]) + 1;
		if (ch >= ZHUYIN_FIRST_TONE) /* it's a tone symbol */
		{
			if (inmd->ZYbufLen == 0)  return 0;

			if (inmd->ZYbuf[inmd->ZYbufLen-1] >= ZHUYIN_FIRST_TONE)
			{
				if (ch == ZHUYIN_FIRST_TONE)  /* 1 tone symbol + 1 SPACE */
					return 0; /* not handled */
				else return 1;
			}

			IsDirectSelect = 1;
		}
	}
	else /* check invalid '  (two consecutive ') */
	{
        if (inmd->EnglishPos > 0) /* already in English */
        {

        }
        else if ((ch >= 'A' && ch <= 'Z') || ch == ':') /* still in Chinese mode but Capital Letter enter */
        {
            inmd->EnglishPos = inmd->ZYbufLen+1; /* position, starting from 1 */
            inmd->iahzbuf[0] = '\0';
            inmd->TotalSelNum = 0;
        }
		else if (ch < 'a' || ch > 'z') /* not pinyin letter */
		{
			static u_char *tonestr = "`-/=\\";
			u_char *tone = strchr(tonestr, ch);
				
			if (tone) ch = (u_int)tone - (u_int)tonestr + '0';
				
			if (ch == '\'' || (ch >= '0' && ch <= '5')) /* a tone symbol or ' */
			{
				if (inmd->ZYbufLen == 0) return 0;  /* not allowed for the first one */
				if (strchr("012345'", inmd->ZYbuf[inmd->ZYbufLen-1])) 
					return 1; /* ignore it? */
			}
		}
	}
	
	if (inmd->KeybufLen >= MAX_ZYINPUT_BUF) /* will overflow Keybuf soon if not output or ignored */
    {
        return 1;
    }

   	inmd->Keybuf[inmd->KeybufLen++] = ch; 
	inmd->Keybuf[inmd->KeybufLen] = '\0';
   	inmd->ZYbuf[inmd->ZYbufLen++] = ch;
	inmd->ZYbuf[inmd->ZYbufLen] = '\0';

	return -1;
}

static int ZhuyinKeyPressed(ZhuyinModule *inmd, u_char ch, int tty_fd)
{
	int ret;

  	if (ch == KEYCODE_CONTROL_H || ch == KEYCODE_BACKSPACE)  /* BackSpace */
  	{
    	if (inmd->KeybufLen == 0) return 0;  /* output ch */

      	if (inmd->ZYbufLen == 0) /* no additional unconverted zhuyin */
      	{
        	strcpy(inmd->ZYbuf, inmd->Keybuf);
			inmd->ZYbufLen = inmd->KeybufLen;
          	inmd->HZbuf[0] = '\0';  /* clear all the selected chars, reparse */
			inmd->HZbufLen = 0; 
      	}
      	else
      	{
			if (IS_KB_ZHUYIN(inmd->KeyboardMode) &&
				inmd->ZYbufLen > 1 && inmd->ZYbuf[inmd->ZYbufLen-1] == ZHUYIN_FIRST_TONE) 
			{
				inmd->Keybuf[--inmd->KeybufLen] = '\0';
            	inmd->ZYbuf[--inmd->ZYbufLen] = '\0'; /* delete the SPACE tone symbol */
			}
        	inmd->Keybuf[--inmd->KeybufLen] = '\0';
          	inmd->ZYbuf[--inmd->ZYbufLen] = '\0';  /* cut one pinyin-char off */
          	if (inmd->KeybufLen == 0)
          	{
            	ResetZhuyinInput(inmd);
              	RefreshZYInputArea(inmd);
              	return 1;   /* clear all, refresh */
          	}

            if (inmd->ZYbufLen < inmd->EnglishPos)
                inmd->EnglishPos = 0;

			if (inmd->ZYbufLen == 0) /* should clear the selection */
			{
				inmd->iahzbuf[0] = '\0';
				inmd->TotalSelNum = 0;
			}
      	}
  	}
	else /* a-z A-Z or other keys */
    {
        ret = ProcessZhuyinKeys(inmd, ch, tty_fd);
        if (ret == 0 || ret == 1) return ret;  /* -1 for continue */
    }

    if (inmd->EnglishPos > 0)
    {
        CreateZYMsg(inmd);
        RefreshZYInputArea(inmd);
        return 1; /* handled in English mode */
    }

	if (inmd->HZbufLen == 0) inmd->CurZYPos = 0;

   	if (IS_KB_ZHUYIN(inmd->KeyboardMode))
	{
		inmd->CurZYNum = inmd->CurZYPos + ParseZhuyinKeys( inmd->KeyboardMode, 
			inmd->ZYbuf, inmd->CurZYStr + inmd->CurZYPos, inmd->CurZYKey + inmd->CurZYPos, 
			inmd->CurZYMask + inmd->CurZYPos, MAX_ZYPHRASE_LEN - inmd->CurZYPos);
	}
	else
	{
  		inmd->CurZYNum = inmd->CurZYPos + ConvertPinyinKeysToZhuyin( inmd->KeyboardMode,
				inmd->ZYbuf, inmd->CurZYStr + inmd->CurZYPos, inmd->CurZYKey + inmd->CurZYPos, 
				inmd->CurZYMask + inmd->CurZYPos, inmd->SavedPYStr + inmd->CurZYPos,
				inmd->CurSPStr + inmd->CurZYPos, MAX_ZYPHRASE_LEN - inmd->CurZYPos);
	
  		if (inmd->CurZYNum > 0)
  		{
    		char chtmp = inmd->SavedPYStr[inmd->CurZYNum-1][0];
      		if (chtmp == 'i' || chtmp == 'u' || chtmp == 'v')
      		{
        		inmd->Keybuf[--inmd->KeybufLen] = '\0';
          		inmd->ZYbuf[--inmd->ZYbufLen] = '\0';
          		inmd->CurZYNum--;
          		return 1; /* ignore the key */
      		}
  		}
	}

  	if (inmd->CurZYNum > MAX_ZYPHRASE_LEN)
  	{
    	inmd->Keybuf[--inmd->KeybufLen] = '\0';
      	inmd->ZYbuf[--inmd->ZYbufLen] = '\0';
      	inmd->CurZYNum--;
      	return 1;
  	}

  	FindMatchCharPhrase(inmd,inmd->CurZYKey + inmd->CurZYPos,
           inmd->CurZYMask + inmd->CurZYPos, inmd->CurZYNum-inmd->CurZYPos);
  	FillForwardSelection(inmd,0);

  	CreateZYMsg(inmd);
  	RefreshZYInputArea(inmd);
	return 1;
}

static int ChangeDisplayPage_Filter(ZhuyinModule *inmd, unsigned char key)
{
	if (index(cur_table->pageprevkey, key))    /* Select Backward */
	{
		if (inmd->SelStartPos > 0)  /* not first page */
        {
			if (--CurrentPage < MAX_PAGE_INDEX)
				FillForwardSelection(inmd, SavedPageIndex[CurrentPage]);
            else  FillBackwardSelection(inmd,inmd->SelStartPos-1);
 
        	RefreshZYInputArea(inmd);
		}
		return 1;
	}
	else if (index(cur_table->pagenextkey, key)   /* Select Forward */
			 || (key == ' ' &&  IsDirectSelect &&
			 IS_KB_ZHUYIN(inmd->KeyboardMode)) ) /* we are using Zhuyin */

	{
		if (inmd->SelEndPos < inmd->TotalSelNum-1) /* should we go back to 0 if last page? yes! */
			CurrentPage++;
		else 
		{
			CurrentPage = 0;
			inmd->SelEndPos = -1;
		}
       	FillForwardSelection(inmd,inmd->SelEndPos+1);
       	RefreshZYInputArea(inmd);
		return 1;

	}
	return 0; /* not processed */
}

int Zhuyin_HZFilter(int tty_fd, u_char ch)
{
	ZhuyinModule *inmd = (ZhuyinModule *)cur_table->PrivateData;
	unsigned int SelIdx;
	char *SelKeyStr;
	
	 SelKeyStr = ( IsDirectSelect ? cur_table->selkey : cur_table->Shift_SelKey);

	if ((inmd->TotalSelNum > 0) && (SelIdx = (unsigned int)index(SelKeyStr, ch)))
	{
		SelIdx -= (unsigned int)SelKeyStr; /* converted to 0-9	 */
 		if (inmd->CandidateIndex[SelIdx] >= 0 &&  /* a valid candidate? */
				SelectKeyPressed(inmd, SelIdx, tty_fd))
	        return 1;
	}

    if ( IsValidPinyinZhuyinKey( inmd->KeyboardMode, inmd->EnglishPos, inmd->KeybufLen, ch) )
    {
		if (ZhuyinKeyPressed(inmd,ch, tty_fd))
			return 1;
	}

	if (inmd->KeybufLen == 0)  /* Keybuf is the input key buffer no key yet */
		return 0;

	if (ChangeDisplayPage_Filter(inmd, ch)) return 1; /*handled */
	
	switch(ch)
	{
    case KEYCODE_ENTER:  /* Process ENTER to output original English string (pinyin only) */
        if (IS_KB_PINYIN(inmd->KeyboardMode))
        {
            OutputZYBuf(inmd, tty_fd);
            return 1;
        }
        else if (inmd->TotalSelNum > 0 && inmd->CandidateIndex[0] >= 0 )
        {
            return SelectKeyPressed(inmd, 0, tty_fd);
        }
        break;

	case KEYCODE_ESCAPE:  /*ESCAPE */
       	ResetZhuyinInput(inmd);
       	RefreshZYInputArea(inmd);
		return 1;
	case KEYCODE_SPACE: /* SPACE */
		if (inmd->TotalSelNum == 0)
		{
			if (inmd->ZYbufLen == 0 && inmd->HZbufLen > 0) /* output the current selected one */
			{
				OutputHZBuf(inmd, -1, inmd->HZbufLen, tty_fd);
				return 1;
			}
			return 0; /* we don't handle it */
		}
		
		if (!IsDirectSelect)  /* follow Big5 custom, SPACE switch to DirectSelect */
		{
			IsDirectSelect = 1;
			FillForwardSelection(inmd,inmd->SelStartPos);
            RefreshZYInputArea(inmd);
			return 1; 
		}

	}
 
    if ( strchr(cur_table->selfirstkey, ch)  && inmd->TotalSelNum > 0 
			 && inmd->CandidateIndex[0] >= 0 )  /* do we need to check IsDirectSelct? */
    {
        return SelectKeyPressed(inmd, 0, tty_fd);
    }

	return 0; /* other keys: we didn't handle it */
}

