/* vi: set sw=4 ts=4: */
/*
 * Original Idea:
 * Li ZhenChun  zhchli@163.net  
 *  
 * 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.
 *
 */

/* 
    For Pinyin mode:
	 (For Zhuyin, since all the input keys have been converted to index(1~41)
	  and it's displayed as Zhuyin Symbols, so don't do it.)

	1. If Capital Letter(A-Z) is input at any time, current string will be 
	   treated as English input, will accept ASCII(' ' to 0x7E), ENTER or
	   any other non-ASCII stuff will output it. Candidates will not be displayed.
	   (Like Pinyin++ and ZiGuangPY)
    2. ENTER will always output current input string as English string
	3. SPACE is used to output 1st candidate at all time.
	4. Since we support Chinese/English-mixed input, so we should enable the
	   i/u/v as 1st yin, so all the a-z letters can be parsed as pinyins.

	For Zhuyin mode:
	1. All the tone symbols(7634) will be ignored, but you can use it to 
	   separate yins(like li'ang, not liang), press SPACE for DirectSelect. 
	   Additional SPACE will be treated as PageDown.
	   Press 7634 won't go into DirectSelect state!
	2. If you press ENTER, it'll output 1st candidate.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> 
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>

#include <defs.h>
#include <vc.h>
#include <term.h>
#include <hzinput.h>
#include <pinyin.h>
#include <zhuyin.h>
#include <key.h>
#include <errors.h>
#include <safestring.h>

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

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

static int  SaveUsrPhrase(PinyinModule *inmd, char *pathname);
static int  LoadUsrPhrase(PinyinModule *inmd, char *pathname);
static int  LoadSysPhrase(PinyinModule *inmd, char *pathname);
static void SortCandidates(PinyinModule *inmd);
static int  AdjustPhraseFreq(PinyinModule *inmd);
void RefreshPinyinInput(void);
void ChangePinyinKeyboardMap(int offset);

static u_char *GetPhraseAndConvert(PinyinModule *inmd, CandidateItem *p, u_char *saveOld)
{
	static u_char temp[2*MAX_PYPHRASE_LEN+1];
	int i, len = (int)(p->head->len);
	u_char *cp, *dest;

	cp = p->head->key + OffsetOfPhrase(len, p->index); /*starting point */
	dest = temp;
	
	if (len > MAX_PYPHRASE_LEN) len = MAX_PYPHRASE_LEN; 
	
   	for(i = 0; i < len; i++) 
	{
		if (saveOld)
		{
			*saveOld++ = cp[0];
			*saveOld++ = cp[1];
		}
		ConvertEncoding_Char(inmd->OldCoding /*CODE_GBK*/, 
				lInfo.sysCoding, cp[0], cp[1], dest, dest+1);
		cp += 2;
		dest += 2;
	}
	*dest = '\0';
	if (saveOld) *saveOld = '\0';
	
	return temp;
}

inline PYFREQTYPE *GetFreq(CandidateItem *p)
{
	int len = (int)(p->head->len);

	return (PYFREQTYPE*)(p->head->key + FreqOffsetOfPhrase(len, p->index) );
}

hz_input_table* Init_PinyinInput(hz_input_table *table, char *filename, int sysCoding)
{   
	PinyinModule *inmd; /* = &Pinyin_Module; */

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

	fclose(table->TableFile);

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

	inmd->OldCoding = table->encoding; /* save it */
	table->encoding = sysCoding;  /* always use system coding */
	
	inmd->KeyboardMode = ZhiyinKeyboardMode;
	if (inmd->KeyboardMode == KEYBOARD_MAP_AUTO) /* 0 */
	{
		if (lInfo.sysCoding == CODE_BIG5)
			inmd->KeyboardMode = KEYBOARD_MAP_ZOZY;
		else inmd->KeyboardMode = KEYBOARD_MAP_PINYIN;
	}

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

	table->FirstInputSelectKeyConflict = IS_KB_CONFLICT(inmd->KeyboardMode);  /* using Zhuyin?  */

  	if (LoadSysPhrase(inmd, filename))
	{
		free(table->PrivateData);
		free(table);
		return NULL;
	}
	
	strcat(filename, ".usr");  /* .usr  we reserved at least 5 chars in LoadInputMethod() */
  	LoadUsrPhrase(inmd, filename);   /* not critical even if fail to load? */
	
	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 */
	
  	ResetPinyinInput(inmd);

	table->InputAreaX = cal_input_xpos(table);
    table->InputAreaWidth = PINYIN_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 = Pinyin_HZFilter;
	table->RefreshInput = RefreshPinyinInput;
	table->ChangeKeyboardMap = ChangePinyinKeyboardMap;

    return table;
}

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

}

static int SaveUsrPhrase(PinyinModule *inmd, char *pathname)
{
	int i,fsize = 0, tmpcount;
   	FILE *out;
   	UsrPhrase *p0,*q0;
   	u_short total;
   	u_short len,count;

   	if ((out = fopen(pathname, W_CNTRL)) == NULL)
	{
      	warn("Failed to save user phrase, no enough memory\r\n");
		return -1;
	}
	
	for (i=1; i<MAX_PY_NUM; i++)
   	{
    	total = 0;
      	fsize += sizeof(u_short); /* for total */

      	p0 =  inmd->usrph[i];
      	if (p0 == NULL)
      	{
        	fwrite(&total,sizeof(total),1,out);
        	continue;
      	}

      	total++;
      	for (p0 = p0->next; p0 != NULL; p0 = p0->next)
      	{
        	len = p0->len;
         	for (q0 = inmd->usrph[i]; q0 != p0; q0 = q0->next)
            	if (q0->len == len && !memcmp(p0->key, q0->key, len+1)) break;
         	if (p0 == q0) total++;  
      	}

      	fwrite(&total,sizeof(total),1,out);

      	for (p0 = inmd->usrph[i]; p0 != NULL; p0 = p0->next)
      	{
        	if (p0->count == 0) continue;

          	tmpcount = p0->count;
          	len = p0->len;
          	for(q0 = p0->next; q0 != NULL; q0 = q0->next)
            	if (q0->len == len && !memcmp(q0->key,p0->key,PYKEYLEN(len)))
                 	tmpcount += q0->count;

          	if (tmpcount >= 65535 /* 255 */)
            	fatal("More than 65535 phrases use the same pinyin!");
          	else count = (u_short)tmpcount;
 
          	fsize += SizeOfPhrase(len,count);

          	fwrite(&len,sizeof(len),1,out);
          	fwrite(&count,sizeof(count),1,out);
          	fwrite(p0->key, PYKEYLEN(len),1,out);

          	for(q0 = p0; q0 != NULL; q0 = q0->next)
             	if (q0->len == len && !memcmp(q0->key,p0->key,PYKEYLEN(len)))
            	{
                	fwrite(q0->key+PYKEYLEN(len),q0->count*LenOfPhrase(len),1,out);
                 	q0->count = 0; 
             	}
       	}
	}

    fwrite(&fsize,sizeof(fsize),1,out);
    fclose(out);

    return 0;
}

#if 0  /* not necessary since we share with R/W frequencies is updated */
static int SaveSysPhrase(PinyinModule *inmd, char *pathname, int remove)
{
	FILE *out;
	char *p;

	if ( (out = fopen( pathname, W_CNTRL)) == NULL)
	{
        warn("%s can't open.\r\n",pathname);
		return -1;
	}

   	p = (char*)(inmd->sysph[1]);

   	fwrite(p,inmd->sys_size,1,out);
   	fwrite(&(inmd->sys_size),sizeof(int),1,out);

	fclose(out);

   	return 0;
}

#endif

static int LoadUsrPhrase(PinyinModule *inmd, char *pathname)
{
	FILE *stream;
   	UsrPhrase *kph,*tmp;
   	int i,j,ahead,fsize;
   	u_short count;
   	u_short len,size;

   	if ((stream = fopen(pathname, R_CNTRL)) == NULL )
	{
         warn("File %s can't open.\n",pathname);
		 return -1;
	}

   	if (fseek(stream,-4,SEEK_END) == -1 ||
       fread(&fsize,sizeof(int),1,stream) != 1  /* ||
       fsize != ftell(stream)-4 */ )   /* error!! */
   	{
       	warn("%s is not a valid pinyin phrase file.\n",pathname);
		fclose(stream);
		return -1;
   	}

   	fseek(stream,0,SEEK_SET);

   	for(i = 1; i < MAX_PY_NUM; i++)
   	{
    	inmd->usrph[i] = NULL;
       	fread(&count,sizeof(count),1,stream);
       	if (count == 0) continue;

       	for(j = 0; j < count; j++)
       	{
        	fread(&len,sizeof(len),1,stream);
          	fread(&size,sizeof(size),1,stream);
          
          	if ((kph = (UsrPhrase *)malloc(4 + SizeOfPhrase(len, size))) == NULL)
            	fatal("No enough memory when loading user phrase.\n");

          	kph->len = len;
          	kph->count = size;
          	kph->next = NULL;
          	fread(kph->key,sizeof(u_char),PYKEYLEN(len),stream);
          	fread(kph->key + PYKEYLEN(len), LenOfPhrase(len), size, stream);

          	ahead = kph->key[1];
          	ahead |= (kph->key[0] & 0x01) << 8;

         	if (inmd->usrph[ahead] == NULL) 
            	inmd->usrph[ahead] = kph;
         	else
         	{
            	tmp = inmd->usrph[ahead];
            	while (tmp->next != NULL)
                	tmp = tmp->next;
            	tmp->next = kph;
         	}
      	}
   	}

   	fclose(stream);
   	return 0;
}

static int LoadSysPhrase(PinyinModule *inmd, char *pathname)
{
	int fd;
	Phrase *kph;
	SysPhrase *sysph;
	char *p;
	int i,j;

	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 pinyin phrase file, size=%d.\n",pathname, inmd->sys_size);
		return -1;
  	}
 
   	p = (char *)mmap(0, inmd->sys_size, 
			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);

   	for(i = 1; i < MAX_PY_NUM; i++)
   	{
    	inmd->sysph[i] = sysph = (SysPhrase*)p;
      	p = (char*)sysph->phrase;
      	for(j = 0; j < sysph->count; j++)
      	{
        	kph = (Phrase*)p;
         	p += SizeOfPhrase(kph->len,kph->count);  /* skip the string  */
      	}
   	}

	close(fd);
  	return 0;
}

static void SaveUsrPhraseToMem(PinyinModule *inmd,u_char *str,u_char *key,int len,int freq)
{
	UsrPhrase *kph, *tmp;
	short ahead;
  
	if (len<1) return;

	ahead = (short)key[1];
	ahead |= (key[0] & 0x01) << 8;

	if ((kph = (UsrPhrase *)malloc(4+SizeOfPhrase(len,1))) == NULL)
	{
    	warn("Not enough memory when saving user phrase.\n");
		return;
	}
	
	kph->len = len;
	memcpy(kph->key,key,PYKEYLEN(len));
	kph->count = 1; 
	kph->next = NULL;
	memcpy(kph->key + PYKEYLEN(len), str, len*2);
	*(PYFREQTYPE *)(&kph->key[PYKEYLEN(len)+2*len]) = (PYFREQTYPE)freq;

	if (inmd->usrph[ahead] == NULL) 
    	inmd->usrph[ahead] = kph;
  	else
  	{
    	tmp = inmd->usrph[ahead];
      	while (tmp->next != NULL)
        	tmp = tmp->next;
      	tmp->next = kph;
  	}
}

static int AdjustPhraseFreq(PinyinModule *inmd)
{
	UsrPhrase *uph;
	SysPhrase *sysph;
	Phrase *sph;
	int i,j,k,index;
	PYFREQTYPE *pFreq;
	char *p;

	for(i = 1; i < MAX_PY_NUM; i++)
	{
     	for(uph = inmd->usrph[i]; uph != NULL; uph = uph->next)
     	{
        	for(k =0; k < uph->count; k++)
         	{
				index = FreqOffsetOfPhrase(uph->len, k);
				pFreq = (PYFREQTYPE *)&(uph->key[index]);
            	if (*pFreq > 25)
                	*pFreq = 25 + (*pFreq-25)/10;
         	}
     	}   

     	sysph = inmd->sysph[i];
     	p = (char*)sysph->phrase;   /* count = total pinyin number */
     	for(j = 0; j < sysph->count; j++)
     	{
        	sph = (Phrase *)p;
        	for(k = 0; k < sph->count; k++)
        	{
				index = FreqOffsetOfPhrase(sph->len, k);
				pFreq = (PYFREQTYPE *)&(sph->key[index]);
            	if (*pFreq > 25)
                	*pFreq = 25 + (*pFreq - 25)/10;
        	}   
        	p += SizeOfPhrase(sph->len,sph->count);
     	}
  	}

  	return SUCCESS;
}

static int QueryPhrase(PinyinModule *inmd, u_char *key, int len, int PYNum)
{
	short ahead;
  	UsrPhrase *uph;
  	char *p;
  	SysPhrase *sysph;
  	Phrase *sph;
  	int i;
  	u_char phkey[MAX_PYPHRASE_LEN+1];
  	short mask=0;
  	int j,count = 0;

  	if (len<1) return 0;

  	ahead = (short)key[1];
  	ahead |= (key[0] & 0x1) << 8;

	for(i=0;i<len;i++)
    	mask += 1<<i;
   
   	for( uph = inmd->usrph[ahead]; uph != NULL; uph = uph->next)
   	{
      	if (uph->len < len) continue;

      	memcpy(phkey,uph->key, PYKEYLEN(len));

      	phkey[0] &= mask;
      	if (!memcmp(phkey,key, PYKEYLEN(len)))  /* match */
      	{
          	if (uph->len == len || (PYNum > 4 && len == PYNum) )   /* exact match */
          	{
			  	if (inmd->TmpSelNum[len-1] < MAX_TEMP_SELECT_PH) /* need to check */
              		inmd->TmpSel[len-1][ inmd->TmpSelNum[len-1]++ ] = 
                   		(Phrase*)( ((char*)uph) + 4 ); /* skip the pointer */
          	}
          	else count++;  /* calculate the phrase longer than len */
      	}
   	}

    sysph = inmd->sysph[ahead]; 
    p = (char*)sysph->phrase;   /* count = total pinyin number */
    for(j = 0; j < sysph->count; j++)
    {
    	sph = (Phrase *)p;
        if (sph->len >= len)
        {
           	memcpy(phkey,sph->key, PYKEYLEN(len));

           	phkey[0] &= mask;
           	if (!memcmp(phkey,key,PYKEYLEN(len))) /* match */
           	{
            	if (sph->len == len || (PYNum > 4 && len == PYNum) )
			   	{
					if (inmd->TmpSelNum[len-1] < MAX_TEMP_SELECT_PH) /* need to check */
                    	inmd->TmpSel[len-1][ inmd->TmpSelNum[len-1]++ ] = sph;
			   	}
               	else count++; /* longer than expected */
           	}
        }
        p += SizeOfPhrase(sph->len,sph->count);
  	}
   
  	return count;
}

static void SortCandidates(PinyinModule *inmd)
{
	int i,j,k, totalph = 0;
	CandidateItem *ph = inmd->CurSel;
	
	for(i = MAX_PYPHRASE_LEN-1; i >= 0; i--)
	{
		int phcount = 0, exactcnt = 0;  /* exact match count */

      	for(j = 0; j < inmd->TmpSelNum[i]; j++)
      	{
			if (j == inmd->TmpSelExactCnt[i]) exactcnt = phcount;
			
			for(k = 0; k < inmd->TmpSel[i][j]->count; k++)
			{
				if (totalph + phcount < MAX_SELECT_PH) 
         		{ 
                   	int    len = inmd->TmpSel[i][j]->len;
                   	u_char *cp; /* starting of the Chinese chars */
                   	int    cnt, valid = 1;
					int    outchar;

					cp = inmd->TmpSel[i][j]->key + OffsetOfPhrase(len, k);
					for(cnt = 0; cnt < len; cnt++, cp += 2) /* we will use GB here */
					{
								
						outchar = ConvertEncoding_Char( inmd->OldCoding /*CODE_GBK*/, 
							lInfo.sysCoding, cp[0], cp[1], NULL, NULL);
						if (outchar <= 0 || 
							(lInfo.sysCoding == CODE_BIG5 && outchar > 0xF9FE)
							)
   						{
							valid = 0;
							break;
						}
					}
					if (!valid) continue; /* skip to next entry */

           			inmd->CurSel[ totalph + phcount ].head = inmd->TmpSel[i][j];
           			inmd->CurSel[ totalph + phcount ].index = k;
           			phcount++;
				} /* if */
			} /* for k */
		} /* for j */

		for(k=0; k<phcount; k++)
		{
			int freqk = (int)(*GetFreq(ph+k));
			
			if (k < exactcnt) freqk += freqk/2; /* 10; */
			
   			for(j=k+1; j<phcount; j++)
			{
				int  freqj = (int)(*GetFreq(ph+j));

				if (j < exactcnt) freqj += freqj/2; /* 10; */

   				if (freqk < freqj)
   				{
					CandidateItem phtmp = ph[k];
       				ph[k] = ph[j];
       				ph[j] = phtmp;
					freqk = freqj; /* the new bigger freq */
   				}
			}
		}
		
   		totalph += phcount; 
   		ph += phcount;
		
	} /* for i */

	inmd->TotalSelNum = totalph;  /* total possible phrase selection */
}

static u_char PYKeyMask[MAX_PYPHRASE_LEN+1] =
{ 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};

void QueryPinyinCombinations(PinyinModule *inmd, int i, u_char PYKey[], int PYNum, 
		unsigned short PYAry[][MAX_PY_PER_LETTER+1], int exactMatch)
{
	int k = (PYAry[i][0] ? 0 : 1);
	
	for(; PYAry[i][k]; k++)
	{
		PYKey[i+1] = PYAry[i][k] & 0xff;
		PYKey[0] = (PYKey[0] & PYKeyMask[i]) | (PYAry[i][k] & 0x0100) >> (8-i);
	
   		if ( QueryPhrase(inmd, PYKey, i+1, PYNum) > 0 && i < PYNum-1) /* has some more matches */
			QueryPinyinCombinations(inmd, i+1, PYKey, PYNum, PYAry,
					k == 0 ? exactMatch : 0); /* recursive call */

		if ( exactMatch && k == 0 )  /* still exact match */
			inmd->TmpSelExactCnt[i] = inmd->TmpSelNum[i];
	}
}

static void FindMatchPhrase(PinyinModule *inmd,PYString pinyin[],int PYNum)
{
	int RealPYNum=0;
	int i;
	u_short PYAry[MAX_PYPHRASE_LEN][MAX_PY_PER_LETTER+1];
	u_char PYKey[MAX_PYPHRASE_LEN+1];

   	inmd->TotalSelNum = 0;
	inmd->iahzbuf[0] = '\0';
	if (!PYNum)
  	{
    	return;
  	}

	if (PYNum > MAX_PYPHRASE_LEN) PYNum= MAX_PYPHRASE_LEN;

	for (i=0; i<PYNum; i++)
	{	
		if (FindMatchPinyins(inmd->KeyboardMode, pinyin[i], PYAry[RealPYNum]) < 0) 
			continue; /* -1 for error */
		RealPYNum++;

  	}  /* for i = 1 to PYNum, PYAry array filled */

	for(i=0; i<MAX_PYPHRASE_LEN; i++) 
		inmd->TmpSelExactCnt[i]= inmd->TmpSelNum[i] = 0;

	QueryPinyinCombinations(inmd, 0, PYKey, RealPYNum, PYAry, 1);

  	SortCandidates(inmd);

	if (IS_KB_CONFLICT(inmd->KeyboardMode))
    {
        if (inmd->PYbufLen == 0 || inmd->PYbuf[inmd->PYbufLen-1] /*>=*/ == ZHUYIN_FIRST_TONE)
            IsDirectSelect = 1;
        else IsDirectSelect = 0;
    }
	else IsDirectSelect = 1;

}

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

    int i,count;
    char strtmp[2*MAX_PYPHRASE_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_PINYIN_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_PINYIN_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;

		snprintf(strtmp,sizeof(strtmp),"%d%s ", (count+1) % NUM_PINYIN_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(PinyinModule *inmd,int lastpos)
{
	char *iahzbuf=inmd->iahzbuf;
	int count,ialen;
	char strbuf[2*MAX_PYPHRASE_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_PINYIN_CAND)
	{
		strncpy(strbuf, GetPhraseAndConvert(inmd, inmd->CurSel+inmd->SelStartPos-1,NULL), 
			sizeof (strbuf));
		ialen += strlen(strbuf)+2;
		if (ialen+2 > cur_table->SelAreaWidth) break;
        count++;
        inmd->SelStartPos--;
    }
    FillForwardSelection(inmd,inmd->SelStartPos);
}

static void CreatePYMsg(PinyinModule *inmd)
{
	int i,j;

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

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

	if (IS_KB_PINYIN(inmd->KeyboardMode))
	{
		for(i=inmd->CurPYPos; i<inmd->CurPYNum; i++)
		{
		
    		strcat(inmd->iapybuf,inmd->CurPYStr[i]);   /* MAX_PY_LEN = 7 */
    		if (inmd->CurPYStr[i][ strlen(inmd->CurPYStr[i]) - 1] == '\'')
      			continue;
    		else strcat(inmd->iapybuf," ");
		}
  	}
	else
	{
		unsigned char *p = inmd->iapybuf;
			
		while(*p) p++; /* skip to the zero  */
	
		for(i = inmd->CurPYPos; i < inmd->CurPYNum; i++)
		{	
			j = 0;
			while(inmd->CurZYStr[i][j])
			{
				if (inmd->CurZYStr[i][j] <= ZHUYIN_TOTAL_NUM) /* 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 RefreshPYInputArea(PinyinModule *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 += len;
		
		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 RefreshPinyinInput(void)
{
	RefreshPYInputArea((PinyinModule *)cur_table->PrivateData);
}

void ChangePinyinKeyboardMap(int offset)
{
	PinyinModule *inmd = (PinyinModule *)cur_table->PrivateData;
	inmd->KeyboardMode += offset;

	if (IS_KB_ZHUYIN(inmd->KeyboardMode))
	{
		if (lInfo.sysCoding != CODE_GB && lInfo.sysCoding != CODE_GBK 
		  		&& lInfo.sysCoding != CODE_BIG5)
			inmd->KeyboardMode = KEYBOARD_MAP_PINYIN;
	}
	else /* pinyin now */
	{
		if (inmd->KeyboardMode > KEYBOARD_MAP_LASTZY)
			inmd->KeyboardMode = KEYBOARD_MAP_PINYIN;

	}

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

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

void ResetPinyinInput(PinyinModule *inmd)
{
	inmd->Keybuf[0] = inmd->PYbuf[0] = inmd->HZbuf[0] = inmd->HZPYKey[0] ='\0';
  	inmd->HZbufLen = inmd->PYbufLen = inmd->KeybufLen = 0;

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

  	inmd->TotalSelNum = inmd->CurPYNum = inmd->CurPYPos = 0;
	inmd->EnglishPos = 0;
}

static void OutputHZBuf(PinyinModule *inmd, CandidateItem *phr, int hzlen, int tty_fd)
{
	PYFREQTYPE *fq;
	unsigned char LastChar[2];

   	if ( hzlen == inmd->HZbufLen )  /* found an existing phrase */
   	{
		if (phr)
		{
       		fq = GetFreq(phr);
       		if (*fq < 250) (*fq)++;
		}

   	}
   	else if(inmd->HZbufLen > 1) /* user just make a new phrase */
   	{
       	SaveUsrPhraseToMem(inmd, inmd->HZbufOldCoding,inmd->HZPYKey, inmd->HZbufLen,1);
   	}
      
	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 */

   	ResetPinyinInput(inmd);

   	RefreshPYInputArea(inmd);

    if (IsAssocEnable && cur_table->AssocFile)
	{
        FindAssociateKeyAndFill(LastChar);  /* using the input method's encoding to find */
	}
}
           
static void OutputPYBuf(PinyinModule *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->PYbufLen > 0)
		OutputCJKStr/*write*/(tty_fd, inmd->PYbuf, inmd->PYbufLen);

   	ResetPinyinInput(inmd);

   	RefreshPYInputArea(inmd);
}

static int SelectKeyPressed(PinyinModule *inmd, int idx, int tty_fd)
{
	CandidateItem *phr=inmd->CurSel;
	int i,j;
	int hzlen;

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

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

   	strcat(inmd->HZbuf, GetPhraseAndConvert(inmd, phr+idx, 
				inmd->HZbufOldCoding + 2*inmd->HZbufLen));
	hzlen = phr[idx].head->len;

	inmd->HZPYKey[0] |= phr[idx].head->key[0] << inmd->HZbufLen;
	for(i = 1; i <= hzlen; i++)
    	inmd->HZPYKey[++inmd->HZbufLen] = phr[idx].head->key[i];

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

		OutputHZBuf(inmd, phr+idx, hzlen, tty_fd);
  	}
  	else  /* not yet, some unselected pinyin exist */
  	{
		inmd->CurPYPos += hzlen;

      	FindMatchPhrase(inmd, inmd->CurPYStr + inmd->CurPYPos,
               inmd->CurPYNum - inmd->CurPYPos);

      	FillForwardSelection(inmd,0);

      	inmd->PYbuf[0] = '\0';  /* put the rest of the pinyin into PYbuf */
		for(j = inmd->CurPYPos; j < inmd->CurPYNum; j++)
		{	 
			if (IS_KB_ZHUYIN(inmd->KeyboardMode))
				strcat(inmd->PYbuf, inmd->CurZYStr[j]);
			else if (IS_KB_SHUANGPIN(inmd->KeyboardMode))
				strcat(inmd->PYbuf, inmd->CurSPStr[j]);
			else strcat(inmd->PYbuf, inmd->CurPYStr[j]);  /* quanpin */
		}
		inmd->PYbufLen = strlen(inmd->PYbuf);

		CreatePYMsg(inmd);
      	RefreshPYInputArea(inmd);
   	}

	return 1;
}

static int ProcessPinyinKeys(PinyinModule *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->PYbufLen == 0)  return 0;

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

		}
	}
	else /* check invalid '  (two consecutive ') in PINYIN mode */
	{
		if (inmd->EnglishPos > 0) /* already in English  */
		{
			if (!VALID_ENG_CHARS(ch)) /* non-ASCII */
			{
				OutputPYBuf(inmd, tty_fd);
				return 0; /* not processed */
			}
		}
		else if (VALID_SWITCHENG_CHARS(ch)) /* still in Chinese mode but Capital Letter enter */
		{
			inmd->EnglishPos = inmd->PYbufLen+1; /* position, starting from 1 */
			inmd->iahzbuf[0] = '\0';
			inmd->TotalSelNum = 0;
		}
		else if (ch == '\'')
		{
			if (inmd->PYbufLen == 0) return 0;  /* not allowed for the first one */
			if (inmd->PYbuf[inmd->PYbufLen-1] == '\'') /*return 1; // ignore it? */
				return 0;
		}
		else if ((ch < 'a' || ch > 'z') 
				&& !(IS_KB_SHUANGPIN(inmd->KeyboardMode) && ch == ';')) return 0; 
	}

	if (inmd->KeybufLen >= MAX_PYINPUT_BUF) /* will overflow Keybuf soon if not output or ignored */
	{
		return 1;
	}
	
   	inmd->Keybuf[inmd->KeybufLen++] = ch; 
	inmd->Keybuf[inmd->KeybufLen] = '\0';
   	inmd->PYbuf[inmd->PYbufLen++] = ch;
	inmd->PYbuf[inmd->PYbufLen] = '\0';

	return -1;  /* go on to display py/candidates */
}

static int PinyinKeyPressed(PinyinModule *inmd,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->PYbufLen == 0) /* no additional unconverted pinyin */
      	{
        	strcpy(inmd->PYbuf, inmd->Keybuf);
			inmd->PYbufLen = inmd->KeybufLen;
          	inmd->HZbuf[0] = '\0';  /* clear all the selected chars, reparse */
			inmd->HZbufLen = 0; 
      	}
      	else
      	{
			if (IS_KB_ZHUYIN(inmd->KeyboardMode) && inmd->PYbufLen > 1 && 
				inmd->PYbuf[inmd->PYbufLen-1] >= ZHUYIN_FIRST_TONE) /* space for TONE */
			{
				inmd->Keybuf[--inmd->KeybufLen] = '\0';
            	inmd->PYbuf[--inmd->PYbufLen] = '\0'; /* delete the SPACE tone symbol */
			}
        	inmd->Keybuf[--inmd->KeybufLen] = '\0';
          	inmd->PYbuf[--inmd->PYbufLen] = '\0';  /* cut one pinyin-char off */
          	if (inmd->KeybufLen == 0)
          	{
            	ResetPinyinInput(inmd);
              	RefreshPYInputArea(inmd);
              	return 1;   /* clear all, refresh */
          	}

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

			if (inmd->PYbufLen == 0) /* should clear the selection */
			{
				inmd->iahzbuf[0] = '\0';
				inmd->TotalSelNum = 0;
			}
      	}
  	}
  	else /* a-z A-Z or other keys */
	{
		ret = ProcessPinyinKeys(inmd, ch, tty_fd);
		if (ret == 0 || ret == 1) return ret;  /* -1 for continue */
	}
	
	if (inmd->EnglishPos > 0)
	{
		CreatePYMsg(inmd);
		RefreshPYInputArea(inmd);
		return 1; /* handled in English mode */
	}

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

   	if (IS_KB_ZHUYIN(inmd->KeyboardMode))
	{
		inmd->CurPYNum = inmd->CurPYPos + ConvertZhuyinKeysToPinyin( inmd->KeyboardMode,
			inmd->PYbuf, inmd->CurPYStr + inmd->CurPYPos, inmd->CurZYStr + inmd->CurPYPos);
	}
	else
	{
  		inmd->CurPYNum = inmd->CurPYPos + ParsePinyinKeys( inmd->KeyboardMode, 
			inmd->PYbuf, inmd->CurPYStr + inmd->CurPYPos, inmd->CurSPStr + inmd->CurPYPos);
	
#ifndef ENABLE_IUV_PINYIN
  		if (inmd->CurPYNum > 0)
  		{
    		char chtmp = inmd->CurPYStr[inmd->CurPYNum-1][0];
      		if (chtmp == 'i' || chtmp == 'u' || chtmp == 'v' )
      		{
        		inmd->Keybuf[--inmd->KeybufLen] = '\0';
          		inmd->PYbuf[--inmd->PYbufLen] = '\0';
          		inmd->CurPYNum--;
          		return 1; /* ignore the key */
      		}
  		}
#endif
	}

  	if (inmd->CurPYNum > MAX_PYPHRASE_LEN)
  	{
    	inmd->Keybuf[--inmd->KeybufLen] = '\0';
      	inmd->PYbuf[--inmd->PYbufLen] = '\0';
      	inmd->CurPYNum--;
      	return 1;
  	}

  	FindMatchPhrase(inmd,inmd->CurPYStr + inmd->CurPYPos,
           inmd->CurPYNum-inmd->CurPYPos);
  	FillForwardSelection(inmd,0);

	CreatePYMsg(inmd);
  	RefreshPYInputArea(inmd);
	return 1;
}

static int ChangeDisplayPage_Filter(PinyinModule *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);
 
        	RefreshPYInputArea(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);
       	RefreshPYInputArea(inmd);
		return 1;
		
	}
	return 0; /* not processed */
}

int Pinyin_HZFilter(int tty_fd, u_char ch)
{
	PinyinModule *inmd = (PinyinModule *)cur_table->PrivateData;
	unsigned int SelIdx;
	u_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 (PinyinKeyPressed(inmd,ch, tty_fd))
			return 1;
	}

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

	if (/*inmd->TotalSelNum > NUM_PINYIN_CAND && */
		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))
		{
			OutputPYBuf(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 */
       	ResetPinyinInput(inmd);
       	RefreshPYInputArea(inmd);
		return 1;

	case KEYCODE_SPACE: /* SPACE */
		if (inmd->TotalSelNum == 0)
		{
			if (inmd->PYbufLen == 0 && inmd->HZbufLen > 0) /* output the current selected one */
			{
				OutputHZBuf(inmd, NULL, 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);
            RefreshPYInputArea(inmd);
			return 1; 
		}

	}
 
    if ( strchr(cur_table->selfirstkey, ch) &&
            inmd->TotalSelNum > 0 && inmd->CandidateIndex[0] >= 0 ) 
    {
		return SelectKeyPressed(inmd, 0, tty_fd);
    }

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

