/* 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.
 *
 */

#include 	<stdio.h>
#include 	<unistd.h>
#include 	<stdlib.h>
#include 	<stdarg.h>
#include	<errno.h>
#include	<string.h>
#include 	<time.h>
#include	<sys/types.h>

#include 	<defs.h>
#include 	<vc.h>
#include    <vga.h>
#include 	<hzinput.h>
#include 	<pinyin.h>
#include	<term.h>
#include 	<font.h>
#include 	<errors.h>
#include	<convert.h>
#include	<safestring.h>

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

#ifdef HAVE_NETINET_IN_H
#include 	<netinet/in.h>
#endif

#define SUPPORT_ASSOCIATE

#define USE_MMAP

 unsigned char SelTab[MAX_SELECT_KEYS][MAX_PHRASE_LENGTH+1];
 int CurSelNum=0;   /* Current Total Selection Number */

 int EnableKeyPrompt = 1; /* global option, default is disabled */
 unsigned char KeyPrompt[MAX_SELECT_KEYS][MAX_KEY_LENGTH+1];
 
 int InpKey[MAX_INPUT_LENGTH];

 int InputCount,InputMatch, StartIndex,EndIndex;
 int NextPageIndex,CurrentPageIndex,MultiPageMode;

 int SavedPageIndex[MAX_PAGE_INDEX];
 int CurrentPage; /* 0-15 */

 int IsDirectSelect;

 static KEYCODE KeyCode;

 static unsigned int keys_mask[2][19] =
 {
   {  /* key_len == 5, 12 keys (6 keys per integer) */
    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 
    0x3E000000, 0x3FF00000, 0x3FFF8000, 0x3FFFFC00, 0x3FFFFFE0, 0x3FFFFFFF, 0x3FFFFFFF,
    0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF
   },
   { /* key_len == 6, 10 keys (5 keys per integer) */
    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
    0x3F000000, 0x3FFC0000, 0x3FFFF000, 0x3FFFFFC0, 0x3FFFFFFF, 0x3FFFFFFF,
    0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF
   }
 };

#define FULL_KEY_MASK (0x3FFFFFFF)

static inline unsigned char *GET_ITEM(int index)
{
	return (((unsigned char *)cur_table->item) + index * cur_table->ItemBytes);
}

static void CalculateKeys(KEYCODE *kc, int count)
{
	int len, i, keynum;
	unsigned int m;
	int wc1 = 0, wc2 = 0;
	
	kc->KVal1 = kc->KVal2 = kc->WMask1 = kc->WMask2 = 0;
	kc->WPreMask1 = kc->WPreMask2 = 0;

	len = cur_table->key_len; 
	keynum = 32/len;
	m = (len == 5 ? 0x1F : 0x3F);

	for(i = 0; i < keynum; i++)
	{
		kc->KVal1 <<= len;
		kc->KVal1 |= InpKey[i];
		kc->KVal2 <<= len;
		kc->KVal2 |= InpKey[i+keynum];
		if (cur_table->wildcard > 0)
		{
			kc->WMask1 <<= len;
			kc->WMask2 <<= len;
			if (InpKey[i] < cur_table->wildcard) kc->WMask1 |= m;
			else if (wc1 == 0) wc1 = i+1;
			if (InpKey[i+keynum] < cur_table->wildcard) kc->WMask2 |= m;
			else if (wc2 == 0) wc2 = i+1+keynum;
		}
	}
	
	if (wc1 == 0) wc1 = wc2;  /* now wc1 is the position of the 1st wildcard */
	if (wc1 == 0) /* no wildcard in input keys */
	{
		kc->WMask1 = kc->WMask2 =  FULL_KEY_MASK;
		kc->WPreMask1 = kc->WPreMask2 = FULL_KEY_MASK;
	}
	else
	{
		kc->WPreMask1 = keys_mask[len - 5] [ keynum + wc1 - 1 ];
		kc->WPreMask2 = keys_mask[len - 5] [ wc1 - 1 ];

		kc->KVal1 &= kc->WMask1;
		kc->KVal2 &= kc->WMask2;
	}
	
	kc->LMask1 = keys_mask[len - 5] [ keynum + count ];
	kc->LMask2 = (cur_table->ItemBytes < 12 ? 0x0 : keys_mask[len - 5][ count ] );

}

static int TestMatchKeys(ITEM *item, int len, KEYCODE *kc)
{
	u_int key1, key2, val1;

	key1 = item->key[0];
	if (cur_table->prefix_match_enable) key1 &= kc->LMask1;

	if ((key1 & kc->WMask1) == kc->KVal1) /* first match */
	{
		if (cur_table->ItemBytes <= 8) return 1; /* only one integer for keys, matched! */
	
		key2 = item->key[1];
		if (cur_table->prefix_match_enable)
			key2 &= kc->LMask2;

		if ((key2 & kc->WMask2) == kc->KVal2) return 1; /* matched! */
	}

	key1 &= kc->WPreMask1;
	val1 = kc->KVal1 & kc->WPreMask1;
	
	if (key1 > val1)
		return -1; /* not-matched, more matches not possible */

	return 0; /* not-matched, more matches possible */
}

#define MMAP_SIZE(table) ((table)->ItemBytes * (table)->TotalEntries + sizeof(hz_input_table))

int MmapInputTable(hz_input_table *table)
{
	unsigned char *p;
	
	if (!table || !table->TableFile) return -1;
	if (table->item) return 0; /* already mapped */
	
	p = (unsigned char *)mmap(0, MMAP_SIZE(table), PROT_READ, 
		MAP_SHARED, fileno(table->TableFile), 0); /* sizeof(hz_input_table)); */
	if (p == MAP_FAILED)
	{
		error("Failed to mmap input table size %d, errno %d!\r\n", MMAP_SIZE(table), errno);
		return -1;
	}
	table->item = (ITEM *)(p + sizeof(hz_input_table));
#ifdef DEBUG
	message("Mmapped input table size %d at address %p\r\n", MMAP_SIZE(table), p);
#endif
	return 0;
}

hz_input_table* LoadInputMethod(const char *filename)
{
	int i;
  	FILE *fd;
	char fname[CCE_PATHNAME_MAX+1];
  	hz_input_table *table;
  
    unsigned char *Shift_ASCII = 
	"                                 !\"#$%&'()*+,-./)!@#$%^&*(:;<=>?"
	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~ ";

	safe_strncpy(fname, filename, sizeof(fname)-5); /* reserved some space for .usr */
 	if (!(fd = fopen(fname, R_CNTRL)))
	{
        safe_strncpy(fname, CCELIB "/", sizeof(fname)-5);
        if (strlen(fname) + strlen(filename) > sizeof(fname)-5) return NULL; /* buffer overrun */
        strcat(fname, filename);
        fd = fopen( fname, R_CNTRL);
	}
  	if (fd == NULL) return NULL;

  	table = malloc(sizeof(hz_input_table));
  	if (table == NULL)
	{
    	error("Out of memory in LoadInputMethod");
		fclose(fd);
		return NULL;
	}
	memset(table, 0, sizeof(hz_input_table));
 
 	if (fread(table, sizeof(hz_input_table), 1, fd) != 1 || 
	    strcmp(INPUT_MAGIC_NUMBER, table->magic_number) /* || 
		table->PhraseBegin < sizeof(hz_input_table)  || 
		fseek(fd, table->PhraseBegin + table->PhraseFileSize, SEEK_SET) ||
		fseek(fd, sizeof(hz_input_table), SEEK_SET) */ ) /* go back to original location */
  	{
    	warn("Cannot load input method %s\n", filename); 
    	fclose(fd);
    	free(table);
    	return NULL;
  	}

  	table->TableFile = fd; /* Mmap need this one, also pinyin's init */

	table->InputSelectKeyConflict = table->FirstInputSelectKeyConflict = 0;
	for(i = 0; i < table->MaxDupSel; i++)
	{
		int keymap;

		table->Shift_SelKey[i] = Shift_ASCII[(int)table->selkey[i]];
		keymap = table->KeyMap[(int)table->selkey[i]];
		if (keymap > 0)
		{
			table->InputSelectKeyConflict = 1;
			if (table->KeyIndex[keymap+1] - table->KeyIndex[keymap] > 0)
				table->FirstInputSelectKeyConflict = 1;  /* first key conflict 1-0 is used as first key */
		}
	}

	if (table->InputType == INPUT_TYPE_INTCODE)
		return Init_IntCodeInput(table, lInfo.sysCoding);
	if (table->InputType == INPUT_TYPE_PINYIN)
		return Init_PinyinInput(table, fname, lInfo.sysCoding);
	if (table->InputType == INPUT_TYPE_ZHUYIN)
		return Init_ZhuyinInput(table, fname, lInfo.sysCoding);
	if (table->InputType != INPUT_TYPE_TABLE)
	{
		error("Unknown input method type %d for %s!\n", table->InputType, filename);
		fclose(fd);
		free(table);
		return NULL;
	}

	if (table->encoding == CODE_GB && lInfo.sysCoding == CODE_GBK)
		table->encoding = CODE_GBK;

	if (table->encoding >= CODE_NUM || 
	    fDRegs[Coding2FontRegDB[(int)table->encoding]].fd < 0)
	{
		warn("No suitable font for input method %s\n", filename);
		fclose(fd); free(table); return NULL;
	}

	if (table->encoding == lInfo.sysCoding || 
		fDRegs[Coding2FontRegDB[(int)table->encoding]].fi != MAP_FAILED)
	{
		table->FontMapped = 1; /* already mapped! */
	}
	
#ifdef DEBUG
  	fprintf(stderr, "Now loading %s, keylen is %d encoding is 0x%x TotalEntry=%d ItemBytes=%d\n", 
            filename, table->key_len, table->encoding, table->TotalEntries, table->ItemBytes);
#endif

#ifdef USE_MMAP
#else
  	table->item = (ITEM *)malloc(table->ItemBytes * table->TotalEntries); 

  	if ( table->item == NULL ) 
  	{
        error("Gosh, cannot malloc enough memory\n");
		free(table);
        return NULL;
  	}
  
	fread(table->item, table->ItemBytes, table->TotalEntries, fd );
	
#endif

  	table->HZFilter = Table_HZFilter;
  	table->RefreshInput = NULL;

	table->InputAreaX = cal_input_xpos(table);
    table->InputAreaWidth = MAX_INPUT_LENGTH;  /* 15 */
    table->SelAreaX = table->InputAreaX + table->InputAreaWidth + 1;
    table->SelAreaWidth = dispInfo.tx_avail - table->SelAreaX - 1
         - dispInfo.show_terminal * WINDOW_TERMINAL_STR_WIDTH; /* 18 or 0 */

	return table;
}

void UnloadInputMethod(int i)
{
	if (input_table[i] == NULL) return;

	if (i == current_method)
	{
    	if (IsHanziInput)
        	ToggleInputMethod();
      	current_method = 0;

    }

	switch(input_table[i]->InputType)
	{
	case INPUT_TYPE_PINYIN:
		PinyinInputCleanup(input_table[i]);
		break;
	case INPUT_TYPE_INTCODE:
	case INPUT_TYPE_TABLE:
    	if (input_table[i]->AssocFile)
        	fclose(input_table[i]->AssocFile);

		if (input_table[i]->TableFile) /* for example, pinyin and intcode has NULL */
	 		fclose(input_table[i]->TableFile);

#ifdef USE_MMAP
		if (input_table[i]->item && input_table[i]->item != (ITEM *)-1)
		{
#ifdef DEBUG
			message("Munmapped input table size %d at address %p\r\n", MMAP_SIZE(input_table[i]), 
				((u_char *)input_table[i]->item)-sizeof(hz_input_table));
#endif
			munmap(((u_char *)input_table[i]->item)-sizeof(hz_input_table), MMAP_SIZE(input_table[i]));
			input_table[i]->item = NULL;
		}
#else
#endif
		break;
	}
	
	free(input_table[i]);
    input_table[i] = NULL;
}

void ResetInput(int ClearInput, int ClearSelect)
{
	memset( InpKey, 0, sizeof(InpKey) );
	memset( SelTab, 0, sizeof(SelTab) );

	StartIndex = EndIndex = MultiPageMode = NextPageIndex = CurrentPageIndex = 0;
	CurSelNum = InputCount = InputMatch = 0;
	IsAssociateMode = 0;   /* lian xiang */
	IsDirectSelect = 0;
	
    if (ClearInput) ClearInputArea();
	if (ClearSelect) ClearSelectionArea();
}

void DisplayInput(void)
{
	int i,CurPos = cur_table->InputAreaX;
	unsigned char c1, c2;
	int EndPos = cur_table->InputAreaWidth + cur_table->InputAreaX;

	for( i = 0; i < cur_table->InputAreaWidth; i++)
	{
      	if (i == InputMatch && InputCount > InputMatch && i != 0) 
		{
        	pVideoInfo->input_sput(CurPos++,  '-' /* ' ' */, INPUT_FGCOLOR,INPUT_BGCOLOR);
		}
		
    	if (i < InputCount)
		{
			c1 = input_table[current_method]->KeyName[InpKey[i]][0];
			c2 = input_table[current_method]->KeyName[InpKey[i]][1];
			if (c1 >= 0x80 && c2 != 0)  /* >= 0x80, should be valid for all Chinese/Japanese/Korean */
			{
				pVideoInfo->input_wput(cur_table->encoding, CurPos,c1, c2, INPUT_FGCOLOR,INPUT_BGCOLOR);
				CurPos += 2;
			}
      		else
			{
				if (c1) pVideoInfo->input_sput(CurPos++, c1, INPUT_FGCOLOR,INPUT_BGCOLOR);
				if (c2) pVideoInfo->input_sput(CurPos++, c2, INPUT_FGCOLOR,INPUT_BGCOLOR);
 			}
		}
		else pVideoInfo->input_sput(CurPos++, ' ', INPUT_FGCOLOR,INPUT_BGCOLOR); /* output a space */

		if (CurPos >= EndPos) break;
  	}
}

void  DisplaySelection(void)
{
	int i, len; 
	int CurPos = cur_table->SelAreaX;
	int EndPos = cur_table->SelAreaX + cur_table->SelAreaWidth;

	if (CurSelNum > 0) /* we do have something */
	{
		if (MultiPageMode && CurrentPageIndex != StartIndex) /* not first page */
		{
    		InputAreaOutput(cur_table->encoding, CurPos, "< ", INPUT_FGCOLOR,INPUT_BGCOLOR);    
      		CurPos += 2;
  		}

		if (!IsDirectSelect)
		{
			InputAreaOutput(cur_table->encoding, CurPos, "Shift-", INPUT_FGCOLOR, INPUT_BGCOLOR);
			CurPos += 6; /* length */
		}
	
  		for( i = 0; i < CurSelNum; i++ )
  		{
     		if ( !SelTab[i][0] ) 
     		{
				continue; /* just in case, if an empty string is put into SelTab, just skip to next one */
     		}

     		len = strlen(SelTab[i]);
     		if (CurPos+len+1+MultiPageMode >= EndPos) break;  

			{	
     			pVideoInfo->input_sput(CurPos++, cur_table->selkey[i],
                	INPUT_FGCOLOR,INPUT_BGCOLOR);
			}
			
     		InputAreaOutput(IsAssociateMode ? lInfo.sysCoding /* cur_table->AscEncoding */: cur_table->encoding, 
				CurPos,SelTab[i],INPUT_FGCOLOR,INPUT_BGCOLOR);
     		CurPos += len;

			pVideoInfo->input_sput(CurPos++, ' ', INPUT_FGCOLOR,INPUT_BGCOLOR);
  		}

  		if ( MultiPageMode && NextPageIndex != StartIndex)  /* not last page */
    		InputAreaOutput(cur_table->encoding, CurPos++, ">", INPUT_FGCOLOR,INPUT_BGCOLOR);
	}
	
	for(i = CurPos; i < EndPos; i++)
		pVideoInfo->input_sput(i, ' ', INPUT_FGCOLOR,INPUT_BGCOLOR);

}

void OutputSelection(int inEncoding, int tty_fd, unsigned char *p )
{
	int i, len = strlen(p);
	unsigned char LastChar[2];

	if (inEncoding != lInfo.sysCoding)
	{
    	for(i = 0; i < len ; i += 2) /* only output even bytes */
    	{
        	ConvertEncoding_Char(/* cur_table->encoding */ inEncoding, lInfo.sysCoding, p[i], p[i+1], p+i, p+i+1);
    	}
	}

    LastChar[0] = p[len-2];
	LastChar[1] = p[len-1]; /* save it, otherwise ResetInput() will clear it */

	OutputCJKStr(tty_fd, p, len);

#if 0 /* def DEBUG */
	{
		int i;

		fprintf(stderr, "Now in hzinput.c's OutputSelection, tty_fd=%d, String=");
		for(i = 0; i < len; i++) fprintf(stderr, "%02X", p[i]);
		fprintf(stderr, "\n");
	}
#endif

	if (InputCount <= InputMatch)  /* All Match */
    {
        ResetInput(1,1);

#ifdef SUPPORT_ASSOCIATE
        if (IsAssocEnable && /* cur_table->EnableAssociate && */ cur_table->AssocFile)
             FindAssociateKeyAndFill(LastChar);  /* using the input method's encoding to find   */
#endif

    }
    else
    {
		int Unmatched = InputCount - InputMatch, nMatch = InputMatch, i;
		int save_InpKey[MAX_INPUT_LENGTH];

		for(i = 0; i < Unmatched; i++)
        	save_InpKey[i] = InpKey[nMatch+i];

		ResetInput(1, 1);  /* reset everything, including InpKey */

      	for(i = 0; i < Unmatched; i++)  /* feed the additional keys */
      	{
        	InpKey[InputCount++] = save_InpKey[i];
         	if (InputCount <= InputMatch+1)
            	FindMatchKeyAndFill();
      	}

		DisplayInput();
      	DisplaySelection(); 
    }
}

int CheckPhraseEncoding(int currentEncoding, int targetEncoding, unsigned char *buf)
{
	int i, len;

    len = strlen(buf);
    if (len < 2 || len % 2 == 1) return -1; /* len should be even */

    for(i = 0; i < len ; i += 2)
    {

        if (ConvertEncoding_Char(currentEncoding, targetEncoding, buf[i], buf[i+1], NULL, NULL) == -1)
        {  /* failed to convert it to target encoding or currentEncoding error */
            return -1;
        }
    }

    return 0;

}

#define GET_OFFSET(p) (((u_int)(p.Phrase[0])<<24) | ((u_int)(p.Phrase[1])<<16) | \
                       ((u_int)(p.Phrase[2])<<8) | ((u_int)(p.Phrase[3])))
                      
static int GetSelection(ITEM *item, int SelTabIdx, int IdxEnd, int nMatch)
{
	unsigned char buf[MAX_PHRASE_LENGTH+1];
	int i, len;
	unsigned int offset;

	if (item->phr.Phrase[0] < 0x80) /* 0-0x7FFFFFFF, phrase offset */
	{ 
		offset = GET_OFFSET(item->phr);
		if (fseek(cur_table->TableFile, cur_table->PhraseBegin + offset, SEEK_SET) ||
		    fread(buf, 1, MAX_PHRASE_LENGTH, cur_table->TableFile) < 2)
			return -1; 
		buf[MAX_PHRASE_LENGTH] = '\0'; /* just in case */
	}
	else  /* Two 2 or 4 bytes Char/Phrase */
    {
		if (item->phr.Phrase[2] >= 0x80) /* a valid char? */
			len = 4;
		else len = 2;
       	memcpy( buf, item->phr.Phrase, len);
       	buf[len] = '\0';
    }	
	
	if (CheckPhraseEncoding(cur_table->encoding, lInfo.sysCoding, buf))
		return -1;

	if (IdxEnd == 0) /* filling forward */
	{
		for(i = 0; i < SelTabIdx; i++)
			if (strcmp(SelTab[i], buf) == 0) return -1;
	}
	else  /* filling backward */
	{
		for(i = SelTabIdx+1; i < IdxEnd; i++)
			if (strcmp(SelTab[i], buf) == 0) return -1;
	}
	
	strcpy(SelTab[SelTabIdx], buf); /* it's safe here */

	if (EnableKeyPrompt && cur_table->key_prompt)
	{
		for(i = nMatch+1; i <= cur_table->MaxPress; i++)
		{
			int key, keynum = 32 / cur_table->key_len; /* how many keys per integer */
			
			if (i > keynum) /* using key[1] */
				key = item->key[1] >> (cur_table->key_len * (2*keynum - i)) ;
			else key = item->key[0] >> (cur_table->key_len * (keynum-i)); 

			KeyPrompt[SelTabIdx][i-nMatch-1] = cur_table->KeyName[key & 0x3F][2];
		
		}
		KeyPrompt[SelTabIdx][i-nMatch-1] = '\0'; 
	}

	return 0;
}

static int TestInputSelectKeyConflict(int nCount)
{
	if (!cur_table->InputSelectKeyConflict || nCount >= cur_table->MaxPress)  
	{
		return 0;
	}
	else
 	{
		int i, j;
		
		for(i = 0; i < cur_table->MaxDupSel; i++)
		{
			unsigned int k0, k1;
			KEYCODE kc;
			unsigned char *item;

			InpKey[nCount] = cur_table->KeyMap[cur_table->selkey[i]];
			if (InpKey[nCount] == 0) continue; /* select key not conflict with input key */
			
			CalculateKeys(&kc, nCount+1); 
			
    		InpKey[nCount] = 0;  /* it's important to restore it */

			item = GET_ITEM(StartIndex);
			for(j = StartIndex; j < EndIndex; j++, item += cur_table->ItemBytes)
			{
				k0 = ((ITEM *)item)->key[0] & kc.LMask1 & kc.WMask1;
			    k1 = ((ITEM *)item)->key[1] & kc.LMask2 & kc.WMask2;  /* k1=0 if ItemBytes <=8 */
				
				if (k0 > kc.KVal1) break;  /* bigger, no match */
				if (k0 < kc.KVal1) continue; /* smaller, try next */
				if (k1 < kc.KVal2) continue; /* now k0==v0, k1<v1 */
				if (k1 == kc.KVal2) return 1;  /* k0=v0, k1=v1, found a match */
		        break;   /* k0==v0, k1>=v1 */
			}
		}
	}
	return 0;
}

int FindMatchKeyAndFill(void)
{
	unsigned int key1 = 0, key2 = 0;
	KEYCODE tmpKC;
	int tmpStartIndex, tmpEndIndex;
	unsigned char *item;

 	static int LastStartIndex[MAX_INPUT_LENGTH]; 
	
	CalculateKeys(&tmpKC, InputCount); /* tmpKeyVal, tmpWmask);  */

 	if (InputCount == 1)
     	tmpStartIndex = cur_table->KeyIndex[InpKey[0]];
  	else
     	tmpStartIndex = LastStartIndex[InputCount-1];

  	tmpEndIndex = cur_table->KeyIndex[InpKey[0]+1];

	item = GET_ITEM(tmpStartIndex);

  	for (; tmpStartIndex < tmpEndIndex; tmpStartIndex++, item += cur_table->ItemBytes)
  	{
		int middleIndex;
		unsigned char *middleItem;

      	key1 = ((ITEM *)item)->key[0] & tmpKC.LMask1 & tmpKC.WMask1;

		if (tmpKC.WMask1 >= FULL_KEY_MASK) /* no wildcard, 30 bits */
		{
      		if (key1 > tmpKC.KVal1) break; /* no match */

			middleIndex = (tmpStartIndex + tmpEndIndex)/2;
			middleItem = GET_ITEM(middleIndex);
        	if (( ((ITEM *)middleItem)->key[0] & tmpKC.LMask1 & tmpKC.WMask1) < tmpKC.KVal1)
			{
				tmpStartIndex = middleIndex; 
				item = middleItem;
				continue;
			}
     		
			if (key1 < tmpKC.KVal1) continue;
		}
		else 
		{
			if (key1 < tmpKC.KVal1) continue;

			if ((key1 & tmpKC.WPreMask1) > (tmpKC.KVal1 & tmpKC.WPreMask1))
        		break; /* not-matched, more matches not possible */

			if (key1 > tmpKC.KVal1) continue; 
		}

      	key2 = ((ITEM *)item)->key[1] & tmpKC.LMask2 & tmpKC.WMask2;
		if (tmpKC.WMask2 >= FULL_KEY_MASK) /* 30 bits no wildcard  */
		{
      		if (key2 < tmpKC.KVal2) continue;
      		break; /* found a match or no match */
		}
		else /* with wildcard */
		{
			if (key2 != tmpKC.KVal2) continue; 
			
		}
  	}

  	LastStartIndex[InputCount] = tmpStartIndex;

	if (key1 == tmpKC.KVal1 && key2 == tmpKC.KVal2)
		InputMatch = InputCount; /* record the current match pointer (prefix-match)*/

	if (FillMatchCharsForward(1, InputCount, &tmpStartIndex, tmpEndIndex, &tmpKC) > 0
		|| !cur_table->prefix_match_enable)
	{
		StartIndex = tmpStartIndex;
		EndIndex = tmpEndIndex; /* These values are valid and won't be restored */
		KeyCode = tmpKC;

		IsDirectSelect = !TestInputSelectKeyConflict(InputCount);
		return CurSelNum;
	}
	else  /* no further matches found */
	{
		if (CurSelNum > 0) IsDirectSelect = 1;
	}

	return 0;
}

int FillMatchCharsForward(int FirstFill, int nCount, int *CurIndex, int CurEndIndex, KEYCODE *kc)
{
	int tmpSelNum = 0, CurLen = 0;
	int index = *CurIndex;
	int tmpStart = -1;
	int match;
	unsigned char *item;

	item = GET_ITEM(index);
    while(tmpSelNum < cur_table->MaxDupSel && index < CurEndIndex)
	{
		match = TestMatchKeys((ITEM *)item, nCount, kc); 
		if (match == 1)
		{
			if (!GetSelection((ITEM *)item, tmpSelNum, 0, nCount))
			{
        		CurLen += strlen(SelTab[tmpSelNum]) + 2; 
				if (tmpStart == -1) tmpStart = index; /* record the actual start key */
				
				if (CurLen > cur_table->SelAreaWidth-3) break; /* -3 for "< >" */
				tmpSelNum++;
			}
		}
	
        index++; /* try the next key */
		item += cur_table->ItemBytes;

		if (match == -1) break; /* should we reset CurEndIndex */
	}
    
    if (tmpSelNum == 0)  /* no new match found */
	{
		if (!FirstFill)
		{
			EndIndex = *CurIndex; /* starting from *CurIndex, no more match found */
			if (NextPageIndex != StartIndex) NextPageIndex = StartIndex;
			if (CurrentPageIndex == NextPageIndex) MultiPageMode = 0;
		}
        if (cur_table->prefix_match_enable) return 0;   
	}

    CurSelNum = tmpSelNum;
    while (tmpSelNum < MAX_SELECT_KEYS) 
       SelTab[tmpSelNum++][0] = '\0';  /* zero out the unused area */

	if (FirstFill)
	{
		*CurIndex = CurrentPageIndex = NextPageIndex = tmpStart;
		MultiPageMode = 0; 
		CurrentPage = 0;
		SavedPageIndex[0] = tmpStart;
	}
	else if (CurrentPage < MAX_PAGE_INDEX) SavedPageIndex[CurrentPage] = *CurIndex;	
	
    if ( index < CurEndIndex && TestMatchKeys((ITEM *)item, nCount, kc) >= 0)
    {

        NextPageIndex = index;
        MultiPageMode = 1;
    }  
    else if (MultiPageMode)  
    {
        NextPageIndex = StartIndex; /* rotate selection */ 
		if (CurrentPageIndex == NextPageIndex) MultiPageMode = 0;
    }
	
	return CurSelNum;
}

int FillMatchCharsBackward(int CurIndex, int nCount, KEYCODE *kc) 
{
	int tmpSelNum, CurLen = 0;
	unsigned char *item;
	int match;
	int i, index = CurIndex-1;

	i = cur_table->MaxDupSel - 1; /* fill this one first	 */
	item = GET_ITEM(index);
    while( i >= 0 && index >= StartIndex)
	{
        match = TestMatchKeys((ITEM *)item, nCount, kc);
		if (match == 1)
		{
			if (!GetSelection((ITEM *)item, i, cur_table->MaxDupSel, nCount))
			{
        		CurLen += strlen(SelTab[i]) + 2; 
           		if (CurLen >= cur_table->SelAreaWidth-3) break;
				i--;
			}
		}
        index--; /* try the next key */
		item -= cur_table->ItemBytes;

	}
    
	tmpSelNum = cur_table->MaxDupSel - i - 1; /* how many do we get? */

    if (tmpSelNum == 0)  /* no new match found */
        return 0;   /* keep the original selection */

	if (i >= 0)
	{
		while(++i < cur_table->MaxDupSel)
			strcpy(SelTab[i-cur_table->MaxDupSel+tmpSelNum], SelTab[i]);
	}

    CurSelNum = tmpSelNum;
    while (tmpSelNum < MAX_SELECT_KEYS) 
       SelTab[tmpSelNum++][0] = '\0';  /* zero out the unused area */

	MultiPageMode = 1; 
	NextPageIndex = CurIndex;
	CurrentPageIndex = index+1;

	return CurSelNum;
}

static int ChangeDisplayPage_Filter(unsigned char key)
{
	if (index(cur_table->pageprevkey, key))
	{
    	if (CurrentPage != 0)
        {
        	if (--CurrentPage < MAX_PAGE_INDEX)
			{
            	CurrentPageIndex = SavedPageIndex[CurrentPage];
				FillMatchCharsForward(0, InputMatch, &CurrentPageIndex, EndIndex, &KeyCode);
			}
            else   
				FillMatchCharsBackward(CurrentPageIndex, InputMatch, &KeyCode);
   			DisplaySelection();
		}
		return 1;
	}
	else if (index(cur_table->pagenextkey, key) 
            )

	{

		if (NextPageIndex <= CurrentPageIndex) /* last page, need to rorate */
        	CurrentPage = 0;
        else CurrentPage++;

    	CurrentPageIndex = NextPageIndex;
		FillMatchCharsForward(0, InputMatch, &CurrentPageIndex, EndIndex, &KeyCode);
   		DisplaySelection();
		return 1;
	}
	return 0; /* not processed */
}

int Table_HZFilter(int tty_fd, unsigned char key)
{
	int inkey = 0,SelKeyIndex;
	char *SelKeyStr;
	char *is_sel_key = (char*)0;

    SelKeyStr = ( IsDirectSelect ? cur_table->selkey : cur_table->Shift_SelKey);
    if (CurSelNum > 0 && (is_sel_key = strchr( SelKeyStr, key)))  /* not NULL, we fount it */
    {
        SelKeyIndex = is_sel_key - SelKeyStr;

        if (SelKeyIndex < CurSelNum && SelTab[SelKeyIndex][0])
        {
            OutputSelection(cur_table->encoding, tty_fd, SelTab[SelKeyIndex]);
            return 1;  /* it's a valid select key, output and return */
        }
    }

	inkey = cur_table->KeyMap[key];
	
	if (inkey > 0)  /* valid input key? */
	{
        if ( InputCount < (MAX_INPUT_LENGTH / cur_table->keyname_bytes) )
           	InpKey[InputCount++] = inkey;
             
        if ( cur_table->prefix_match_enable == 0 || InputCount - InputMatch <= 1) 
        {
           	FindMatchKeyAndFill();
			
           	if (/* InputCount */ InputMatch >= cur_table->MaxPress && CurSelNum == 1 &&
               	cur_table->auto_select)
           	{  /* left only one selection  */
               	OutputSelection(cur_table->encoding, tty_fd, SelTab[0]);        
				return 1;
           	}

           	DisplaySelection(); /* maybe from Shift- to Direct State or some new matches */

#if 0

			if (InputCount > InputMatch) /* new key has no match */
			{
				if (ChangeDisplayPage_Filter(key) ||
				    cur_table->KeyIndex[inkey+1] - cur_table->KeyIndex[inkey] <= 0)
				{
					InpKey[--InputCount] = '\0';				
					return 1; /* should we output this key? */
				}
			}
#endif

		} /* if  */

        DisplayInput();
		return 1;
	}

  	switch ( key )
  	{
    	case '\010':  /* BackSpace Ctrl+H */
     	case '\177':  /* BackSpace 127 DEL */
        	if ( InputCount > 0 /* || IsAssociateMode*/ ) 
        	{
           		if (InputCount > 0) InpKey[--InputCount] = '\0';
           		if (InputCount == 0)
               		ResetInput(0,1);
           		else if (InputCount < InputMatch)
           		{
					InputMatch = InputCount; /* fix the InputMatch now */
               		FindMatchKeyAndFill();
               		DisplaySelection();
           		}
           		DisplayInput();
				return 1;
        	}
        	break;

   		case '\033':  /* ESCAPE */
        	if (InputCount > 0 ) 
			{
           		ResetInput(1,1);
				return 1;
			}
        	break;

     	case ' ': /* should we use SPACE to do PageNext or SelectFirstItem? */
			if (IsDirectSelect) /* maybe in Shift-Select State */
			{

			}
			else
			{
				if (InputCount > 0) 
				{
					IsDirectSelect = 1; /* switch state */
					DisplaySelection();  /* refresh display */
					return 1;
				}
			}
       		break;

  	} /* switch */

	if (/* CurSelNum > 0  && */ MultiPageMode && ChangeDisplayPage_Filter(key)) return 1; 

	if (CurSelNum > 0 && strchr(cur_table->selfirstkey, key) 
			&& SelTab[0][0] )  /* do we need to check IsDirectSelct? */
	{
		OutputSelection(cur_table->encoding, tty_fd, SelTab[0]);
		return 1;
	}

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

