/* 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	<errno.h>
#include	<ctype.h>
#include	<string.h>
#include	<sys/types.h>

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

 int EnableFuzzyPinyin; /* global option z-c-s zh-ch-sh */

unsigned char *InputKeyMap[] =  {
    "",  /* auto */

	"abcdefghijklmnopqrstuvwxyz'" "`-/=\\", /* "012345", */

	"abcdefghijklmnopqrstuvwxyz';" "`-/=\\",  /* added ; for shuangpin */
	
    "1qaz2wsxedcrfv5tgbyhn"   
    "8ik,9ol.0p;/-"
    "ujm"
	" 6347", /* 7 is light tone */
	
    "bpmfdtnlvkhg7c,./j;'s"
    "aorwiqzy890-="
    "exu"
	" 2341",

    "2wsx3edcrfvtgb6yhnujm"
    "8ik,9ol.0p;/="
    "-['"
	" qaz1",

    "1234567890-qwertyuiop"
    "fghjkl;zxcvbn"
    "asd"
	" m,./",

	"bpmfdtnlgkhjqxzcsr123"				
	"a4567890o-=[;"
	"ewu"
	" ,./'",

	"bpmfdtnlvkhgvcgycjqws"
	"aorwiqzpmntlh"
	"exu"
	" dfjk",

	"bpmfdtnlgkhjvcjvcrzas"
	"yhgeiawomnkll"
	"exu"
	" dfjs",
};

unsigned char Big5PhoneChars[]=
	"£t£u£v£w£x£y£z£{£|£}£~£¡£¢£££¤£¥£¦£§£¨£©£ª"  	
	"£«£¬£­£®£¯£°£±£²£³£´£µ£¶£·" 				
	"£¸£¹£º"
	"¡@£½£¾£¿£»";			

unsigned char GBPhoneChars[] = 
	"¨Å¨Æ¨Ç¨È¨É¨Ê¨Ë¨Ì¨Í¨Î¨Ï¨Ð¨Ñ¨Ò¨Ó¨Ô¨Õ¨Ö¨×¨Ø¨Ù"
	"¨Ú¨Û¨Ü¨Ý¨Þ¨ß¨à¨á¨â¨ã¨ä¨å¨æ"
	"¨ç¨è¨é"
	"¡¡£¯¡¦£Ü£®";

unsigned char *ZYSymbolToPYString[38] = {
	"", 
	"b", "p", "m", "f", "d", "t", "n", "l", 
	"g", "k", "h", "j", "q", "x",
	"zh", "ch", "sh", "r", "z", "c", "s",
	"a", "o", "e", "e" /* like ye*/, "ai", "ei", "ao", "ou",
	"an", "en", "ang", "eng", "er",
	"i", "u", "v", 
};
 
static int FindOnePinyin(u_char *py);
static int ConvertOneSPToQP(int KBIndex, u_char *py, u_char *npy);
 
int ConvertZhuyinStrToKeys(u_char *buf, u_char key[])
{
	int i, total = 0;
	unsigned char *p, *s;

	p = buf;
	while(*p == ' ' || *p == '\t') p++;
	if (*p != 0xA3 && *p != 0xA8) 
	{
		fprintf(stderr, "ERROR! Not a valid phonetic symbol: *p=0x%02X%02X\n", *p, p[1]);
		return 0;	
	}

	while(*p == 0xA3 || *p == 0xA8)  /* A3 is Big5 A8 is GB/GBK */
	{
		i = 1; 
		s = (*p == 0xA3 ? Big5PhoneChars : GBPhoneChars);
		while(s)
		{
			if (strncmp(p, s, 2) == 0)
			{
				key[total++] = i;
				break;
			}
			s += 2;
			i++;
		}

		if (!s) fprintf(stderr, "ERROR NOT A VALID PHONETIC SYMBOL!\n");
		p += 2;	
		if (i > 37 || total >= MAX_ZY_LEN ) break;
	}

	if (key[total-1] < 38) /* last one not tone */
	{
		if (*p == '0') 
		{
			key[total++] = 42; /* 5th tone, light tone */
			p++;
		}
		else if (*p >= '1' &&  *p <= '5') 
		{
			key[total++] = (*p) - '1' + 38;
			p++;
		}
		else key[total++] = 38; /* no tone, will use 1st tone */
	}

	key[total] = '\0';
	return (u_int)p - (u_int)buf;
}

int IsValidPinyinZhuyinKey( int KBIndex, int EngPos, int BufLen, u_char ch )
{
	if (ch == KEYCODE_CONTROL_H || ch == KEYCODE_BACKSPACE /* BackSpace  */
		|| strchr(InputKeyMap[KBIndex], ch))  /* a valid input key */
		return 1;

	if (!IS_KB_PINYIN(KBIndex)) return 0; /* only pinyin supports PY++ style Eng-mode */

	if (EngPos > 0)
	{
		if (VALID_ENG_CHARS(ch)) return 1;
	}
	else
	{
		if ((ch >= 'A' && ch <= 'Z') 
) return 1; 
	}
	return 0;
}

int ConvertOneET26OrHsu25Yin(int index, u_char key[], u_char nkey[])  
{
	int i, offset = 0;
	u_short map, tmpyin, yin = 0;

	if (key[0] >= 1 && key[0] <= 34 && key[0] != 25) /* a valid bpmf...aoe  non iuv */
	{
		yin = (u_short)( key[0] << (5+2));
		offset++;
		if ((key[1] >= 35 && key[1] <= 37) || key[1] == 25) /* i u v */
		{
			tmpyin = yin | ((u_short)(key[1]==25? 1: key[1]-34) << 5);
		   	if (!ET26HSU25Map[index][tmpyin]) goto go_out;
			yin = tmpyin;
			offset++;
		}
		if (key[offset] >= 1 && key[offset] <=34)
		{
			tmpyin = yin | ((u_short)key[offset] << 0);
			if (!ET26HSU25Map[index][tmpyin]) goto go_out;
			yin = tmpyin;
			offset++;
		}
	}
		
	else if ((key[0] >= 35 && key[0] <= 37) || key[0] == 25) /* a valid iuv, we may have 3rd key */
	{
		yin = (u_short)( (key[0]==25 ? 1 : key[0]-34) << 5);
		offset++;
		if (key[1] >= 1 && key[1] <= 34) /* a valid aoe stuff */
		{
			tmpyin = yin | ( (u_short)key[1] << 0);
			if (!ET26HSU25Map[index][tmpyin]) goto go_out;
			yin = tmpyin;
			offset++;
		}
	}
	
go_out:
	map = ET26HSU25Map[index][yin];
	i = 0;
	if (map & BPMF_MASK)
		nkey[i++] = (map & BPMF_MASK) >> BPMF_OFFSET;
	if (map & IUV_MASK)
		nkey[i++] = ((map & IUV_MASK) >> IUV_OFFSET) + 34;
	if (map & AOE_MASK)
		nkey[i++] = ((map & AOE_MASK) >> AOE_OFFSET) + 21;

	switch(index)
	{
	case 0:  /* ETen26 dfjk for tones */
		if (key[offset] == 38) /*SPACE*/ nkey[i++] = 38;
		else if (key[offset] == 5) /* d */ nkey[i++] = 39;
		else if (key[offset] == 4) /* f */ nkey[i++] = 40;
		else if (key[offset] == 18) /* j */ nkey[i++] = 41;
		else if (key[offset] == 10) /* k */ nkey[i++] = 42;
		break;
	case 1:  /* HSU25  dfjs for tones */
		if (key[offset] == 38) /* SPACE*/ nkey[i++] = 38;
		else if (key[offset] == 5) /* d */ nkey[i++] = 39;
		else if (key[offset] == 4) /* f */ nkey[i++] = 40;
		else if (key[offset] == 12) /* j */ nkey[i++] = 41;
		else if (key[offset] == 21) /* s */ nkey[i++] = 42;
		break;
	}

	nkey[i] = 0;

	return i;
}
	
int ParseOneZhuyin(u_char key[], ZYString zhuyin,
		u_short yin[], u_short mask[])
{
	int offset = 0;
	u_short zy;

	zhuyin[0] = '\0';
	yin[0] = 0;
	mask[0] = BPMF_MASK | IUV_MASK | AOE_MASK; /* default is exact-match except bpmfdtnlkghjqx */

	if (key[0] >= 1 && key[0] <= 21) /* a valid bpmf... */
	{
		yin[0] = (u_short)( key[0] << BPMF_OFFSET);
		offset++;
		if (key[1] >= 35 && key[1] <= 37) /* i u v */
		{
			zy =  ((u_short)(key[1]-34) << IUV_OFFSET);
			if (!ZYToPYMap[zy | yin[0]]) goto get_out;  
			yin[0] |= zy;
			offset++;
		}
		if (key[offset] >= 22 && key[offset] <=34)
		{
			zy = ((u_short)(key[offset]-21) << AOE_OFFSET);
			if (!ZYToPYMap[zy | yin[0] ]) goto get_out;  
			yin[0] |= zy;
			offset++;
		}

	}
		
	else if (key[0] >= 35 && key[0] <= 37) /* a valid iuv, we may have 3rd key */
	{
		yin[0] = (u_short)( (key[0] - 34) << IUV_OFFSET);
		offset++;
		if (key[1] >= 22 && key[1] <= 34) /* a valid aoe */
		{
			zy = ( (u_short)(key[1]-21) << AOE_OFFSET);
			if (!ZYToPYMap[zy | yin[0]]) goto get_out;	
			yin[0] |= zy;
			offset++;
		}

	}
		
	else if (key[0] >= 22 && key[0] <= 34) /* a valid aoe... */
	{
		yin[0] = (u_short)( (key[0]-21) << AOE_OFFSET);
		offset++;

	}

get_out:	

	if (offset == 1 && key[0] < 15)  /* bpmfdtnlkghjqx 14 letters only, do prefix-match */
		mask[0] &= ~(IUV_MASK | AOE_MASK); 

	if (key[offset] >= 38 && key[offset] <= 42)
	{
		mask[0] |= (u_short)( 1 << (key[offset]-38 + TONE_OFFSET)); /*TONE_MASK; */
		yin[0] |= (u_short)( 1 << (key[offset]-38 + TONE_OFFSET));
		offset++;
	}
	
	if (key[0] <= 37) /* not starting with a tone symbol */
	{
		strncpy(zhuyin, key, offset);
		zhuyin[offset] = '\0';
	}

	return offset;
}
		
int ParseZhuyinKeys(int KBIndex, u_char key[], ZYString zhuyin[],
		u_short yin[], u_short mask[], int nMax)
{
	int offset;
	int total = 0;
	
	while(key[0] && total < nMax)
	{
		if (KBIndex == KEYBOARD_MAP_ET26ZY || KBIndex == KEYBOARD_MAP_HSU25ZY)
		{
			u_char nkey[10]; /* for one zhuyin */
			ConvertOneET26OrHsu25Yin(KBIndex - KEYBOARD_MAP_ET26ZY, key, nkey);
			offset = ParseOneZhuyin(nkey, zhuyin[total], yin+total, mask+total);
		}
		else offset = ParseOneZhuyin(key, zhuyin[total], yin+total, mask+total);

		if (key[0] <= 37) /* not starting with a tone symbol */
		{
			key += offset;
			total++;
		}
		else key++; /* ignore this key! */
	} /* while */
	
	return total;
}

int ConvertOnePinyinToZhuyin( u_char pybuf[], int count, ZYString zy, /* huyin, */
		u_short yin[], u_short mask[], u_char pystr[MAX_PY_LEN+3])
{
	int i, ahead, tone;
	int zykey = 0;
	u_char	t;

   	strncpy(pystr, pybuf, count);  /* security patch! */
   	pystr[count] = '\0';

	ahead = pybuf[0] - 'a';
	for(i = 0; PYMap[ahead][i].key; i++)
	{
		if (!strcmp(pystr, PYMap[ahead][i].py))  /* full match */
		{
			zykey = PYMap[ahead][i].zykey;
			break;
		}
	}
	if (zykey == 0) return count; /* no zhuyin map found, iuv?  */

	i = 0;
	if ((zy[0] = (zykey & BPMF_MASK) >> BPMF_OFFSET)) i++;  /* 1~21 */
	if ((zy[i] = (zykey & IUV_MASK) >> IUV_OFFSET))  zy[i++] += 34; /* 35~37 */
	if ((zy[i] = (zykey & AOE_MASK) >> AOE_OFFSET)) zy[i++] += 21;  /* 22~34 */

	yin[0] = zykey;

    if ((count == 1 && strchr("bpmfdtnlkghjqxzcsryw", pybuf[0])) ||
        (count == 2 && pystr[1] == 'h' && strchr("zcs", pybuf[0])))   /* zh/ch/sh? */
	{
		mask[0] = BPMF_MASK;  /* prefix match */
	}
	else mask[0] = BPMF_MASK | IUV_MASK | AOE_MASK; /* exact match */

	t = pybuf[count];
	if (t >= '0' && t <= '5')
	{
		pystr[count++] = t;
		pystr[count] = '\0';
		tone = (t == '0' ? 4 : t - '1'); /* 0~4 */
		zy[i++] = 38 + tone; /* 38~42 */

		mask[0] |= (u_short)( 1 << (tone + TONE_OFFSET)); 
        yin[0] |= (u_short)( 1 << (tone + TONE_OFFSET));
	}

	if (pybuf[count] == '\'')
	{
		if (!strchr("012345", pystr[count-1] )) /* will only allow one */
		{
			pystr[count++] = '\'';
			pystr[count] = '\0';
		}
		else count++; /* skip  it */
	}

	return count;
}

int ConvertPinyinKeysToZhuyin(int KBIndex, u_char pybuf[], ZYString zhuyin[],
		u_short yin[], u_short mask[], u_char pystr[][MAX_PY_LEN+3], SPString spstr[],int nMax)
{
	int len, total = 0;
	int offset, count;
	u_char npybuf[MAX_PY_LEN+3];
	
	len = strlen(pybuf);

	if (len < 1 || len > MAX_ZYPHRASE_LEN * MAX_PY_LEN ) return 0;
 
	while(*pybuf)
	{	
		if (IS_KB_SHUANGPIN(KBIndex))
		{	
			int pycnt;

			count = ConvertOneSPToQP(KBIndex, pybuf, npybuf);
			pycnt = strlen(npybuf);
			if (pybuf[count] >= '0' && pybuf[count] <= '5')
			{	
				count++; pycnt--; 
			}
			if (pybuf[count] == '\'')
			{
				count++; pycnt--;	
			}

			if (spstr)
			{
				strncpy(spstr[total], pybuf, count);  /* save sp string */
				spstr[total][count] = '\0';  
			}

			offset = ConvertOnePinyinToZhuyin(npybuf, pycnt, zhuyin[total], 
					 yin+total, mask+total, pystr[total]);

			if (offset > 0) 
			{
				pybuf += count; /* shuangpin */
				total++;
			}
			else pybuf++; /* ignore this key */
		}
		else
		{
			count = FindOnePinyin(pybuf);

			offset = ConvertOnePinyinToZhuyin(pybuf, count, zhuyin[total], 
					 yin+total, mask+total, pystr[total]);
		
			if (offset > 0)
			{
				pybuf += offset;
				total++;
			}
			else pybuf++; /* ignore this key */
		}
	}

  	return total;
}

#ifdef ENABLE_IUV_PINYIN

void HandleIUVPinyin(char *destpy, char *srcpy)
{
	if (*srcpy == 'v')
	{
		*destpy++ = 'y'; *destpy++ = 'u'; srcpy++;  /* v->yu */
	}
	else if (*srcpy == 'i')
	{
		*destpy++ = 'y'; 
		if (srcpy[1] == 'a' || srcpy[1] == 'o' || srcpy[1] == 'e') srcpy++;
	}
	else if (*srcpy == 'u')
	{
		*destpy++ = 'w'; 
		if (srcpy[1] == 'a' || srcpy[1] == 'o') srcpy++;
	}
	
	while(*srcpy) /* copy the trailing  */
		*destpy++ = *srcpy++;
	*destpy = '\0';
}
#endif

static int FindOnePinyin(u_char *py)
{
	int i, ahead, count, valid;
	u_char *py1;
#ifdef ENABLE_IUV_PINYIN
	u_char ch; 
#endif

	py1 = py+1; /* first letter always valid */

#ifdef ENABLE_IUV_PINYIN
	ch = py[0];  /* first letter */
	if (ch == 'v')
	{
		py[0] = 'u'; ahead = 'y' - 'a'; py1 = py; /* ->yuX */
	}
	else if (ch == 'i')
	{
		ahead = 'y' - 'a'; 
		if (py[1] != 'a' && py[1] != 'o' && py[1] != 'e') py1 = py;
	}
	else if (ch == 'u')
	{
		ahead = 'w' - 'a'; 
		if (py[1] != 'a' && py[1] != 'o') py1 = py;
	}
	else
#endif
	{
		ahead = py[0] - 'a';
		if (py[1] == 'v' && py[2] == 'e')  /* ve->ue  jqxnl + ue  (ue==ve) */
			py[1] = 'u';  /* will change the display! */
	}
	
	if (ahead < 0 || ahead > 25) return 0; /* not allowed to begins with ' */

	for(count = 1; py1[count-1]; count++)
  	{
      	valid = 0;
      	for(i=0; PYMap[ahead][i].key; i++)
      	{
        	if ( !strncmp(PYMap[ahead][i].py+1, py1, count) )
         	{
            	valid = 1; break;
         	}
      	} 
		if (!valid) break;
	}

#ifdef ENABLE_IUV_PINYIN
	py[0] = ch; /* recover 'v' */
	if (py1 == py) count--; /* count the leading y/w */
#endif
	
	return count;
}

static int FillExactMatchPinyinArray(u_char *py, int PYLen, u_short PYAry[])
{
	int TmpKey;
    int j;
    int ahead;
    int PYAryCnt;
    u_char ch, *curpy;

	ch = py[0];
   	ahead = ch - 'a';
	if (ahead < 0 || ahead > 25) return -1; /* for safety, if not a-z */

   	PYAryCnt = 0 /* 1 */; /* starting from 0(prefix-match) */
	PYAry[0] = 0;

   	for(j=0; (TmpKey = PYMap[ahead][j].key); j++)
   	{
		curpy = PYMap[ahead][j].py;
   		if (!strcmp(py, curpy)) /* exact match, will have only one */
   		{
			PYAry[/* 0 */ PYAryCnt++] = TmpKey;  /* only one possible */
		}
		else if (ch == 'z' || ch == 'c' || ch == 's') /* special handling for zcs */
		{
			if (py[1] != 'h') /* z/c/s only, will match zcsXYZ (x!= h if EnableFuzzyPinyin not on) */
			{
				if (PYLen == 1 && curpy[1] != 'h') /* Do not match zh/ch/shXYZ  */
					PYAry[PYAryCnt++] = TmpKey;				
			}
			else  /*zh/ch/shXYZ */
			{
				if (PYLen == 2 && curpy[1] == 'h')  /* zh/ch/sh only, will match zcshXYZ */
					 PYAry[PYAryCnt++] = TmpKey;
			}
		}
		else if (PYLen == 1 && strchr("bpmfdtnlgkhjqxr", ch))  /* prefix-match, shengmu only */
		{
			PYAry[PYAryCnt++] = TmpKey;
		}

   	} /* for j */

   	PYAry[PYAryCnt] = 0;

	return PYAryCnt;
}

static int FillPrefixMatchPinyinArray(u_char *py, int PYLen, u_short PYAry[])
{
	int TmpKey;
    int j;
    int ahead;
    int PYAryCnt;
    u_char ch, *curpy;

	ch = py[0];
   	ahead = ch - 'a';
	if (ahead < 0 || ahead > 25) return -1; /* for safety, if not a-z */

   	PYAryCnt = 1;
	PYAry[0] = 0;

   	for(j=0; (TmpKey = PYMap[ahead][j].key); j++)
   	{
		curpy = PYMap[ahead][j].py;
   		if (!strcmp(py, curpy))
   		{
			PYAry[0] = TmpKey;  /* only one possible */
		}
		else if (PYLen == 1 || !strncmp(py, curpy, PYLen))
		{
			PYAry[PYAryCnt++] = TmpKey;
		}
   		else if (EnableFuzzyPinyin && (ch == 'z' || ch == 'c' || ch == 's')) 
   		{ 
       		if (py[1] != 'h') /* z,c,s->zh,ch,sh  */
			{
				if (curpy[1] == 'h' && !strncmp(py+1, curpy+2, PYLen-1))
           			PYAry[PYAryCnt++] = TmpKey;
       		}
       		else /* zh,ch,sh->z,c,s */
       		{
				if (curpy[1] != 'h' && !strncmp(py+2, curpy+1, PYLen-2))
           			PYAry[PYAryCnt++] = TmpKey;
       		}  
   		}

   	} /* for j */

   	PYAry[PYAryCnt] = 0;

	return PYAryCnt;
}

int FindMatchPinyins(int KBIndex, u_char *py, u_short PYAry[])
{
	u_char str[MAX_PY_LEN+5];
	int PYLen;
	int i, count, total;

#ifdef ENABLE_IUV_PINYIN
	HandleIUVPinyin(str, py);  /* i->yi, u->wu, v->yu */
#else
	if (strchr("iuv", py[0]))
    	return -1;  /* ignore the i/u/v beginning */
	strcpy(str, py); /* won't overrun */
#endif

	PYLen = strlen(str);
	while (PYLen > 0 && strchr("012345'", str[PYLen-1]))
		str[--PYLen] = '\0'; /* ignore the tone and ' */
	if (PYLen == 0) return -1;  /* strange nothing? */

	if (KBIndex == KEYBOARD_MAP_PINYIN) 
		return FillPrefixMatchPinyinArray(str, PYLen, PYAry);

	count = FillExactMatchPinyinArray(str, PYLen, PYAry);
	if (count < 0) return -1; /* error */

	total = count;
	if (EnableFuzzyPinyin)
	{
		if (strchr("zcs", str[0])) /* zcs/zhchsh fuzzy */
		{
			if (str[1] != 'h') /* z/c/s */
			{
				for(i = PYLen; i > 0; i--)
					str[i+1] = str[i];	
				str[1] = 'h';
				PYLen++;
			}
			else  /* zh/ch/sh */
			{
				for(i = 2; i <= PYLen; i++)
					str[i-1] = str[i];
				PYLen--;
			}

			count = FillExactMatchPinyinArray(str, PYLen, PYAry + total);
			if (count < 0) return -1;
			total += count;
		}

	}

	return total;
}

int ParsePinyinKeys( int KBIndex, char *pybuf, PYString pinyin[], SPString sp[])
{
	int len, total = 0;
	int count;
	u_char npybuf[MAX_PY_LEN+3];

	len = strlen(pybuf);
	if (len < 1 || len > MAX_PYPHRASE_LEN * MAX_PY_LEN ) return 0;
	
	while(*pybuf)
	{
     	if (IS_KB_SHUANGPIN(KBIndex))
        {
            count = ConvertOneSPToQP(KBIndex, pybuf, npybuf);

			if (count > 0) 
			{
				strncpy(pinyin[total], npybuf, sizeof(pinyin[0])); 
				if (pybuf[count] == '\'') count++;
				
				strncpy(sp[total], pybuf, count);
				sp[total][count] = '\0';
			}
        }
        else
        {
			count = FindOnePinyin(pybuf);
			if (count > 0)
			{
 				if (pybuf[count] == '\'') count++;  /* add it at the end */

				strncpy(pinyin[total], pybuf, count); 
   				pinyin[total][count] = '\0';
			}
		}

		if (count > 0) 
		{
			pybuf += count;
			total++;
		}
		else pybuf++; /* skip this key */
	}

  	return total;
}

int ConvertZhuyinKeysToPinyin( int KBIndex, char *key, PYString pinyin[], ZYString zhuyin[])
{
    int len, i, py, total = 0;
    int count;
	u_short yin[MAX_PYPHRASE_LEN+1], mask[MAX_PYPHRASE_LEN+1];

    len = strlen(key);
    if (len < 1 || len > MAX_PYPHRASE_LEN * MAX_ZY_LEN ) return 0;

	count = ParseZhuyinKeys(KBIndex, key, zhuyin, yin, mask, MAX_PYPHRASE_LEN);
	for(i = 0; i < count; i++)
	{
		py = ZYToPYMap[yin[i] & 0x07FF];
		if (py)
		{
			strcpy(pinyin[total++], GET_PYMAP_PY(py));
		}
	}

    return total;
}

static int ConvertOneSPToQP(int KBIndex, u_char *py, u_char *npy)
{
	int count = 1;
	int n, k1, k2, len;

	if (py[0] < 'a' || py[0] > 'z') return 0;
	k1 = py[0]-'a'; /* 0 ~ 25 */
	k2 = (py[1] == ';' ? 27 : (py[1] >= 'a' && py[1] <= 'z' ? py[1]-'a'+1: 0) ); /* 0,1-26(a-z), 27(;) */
	
	n = SPToPYKeyMap[k1][k2];
	if (k2 > 0 && n == 0) /* two keys not valid */
	{
		k2 = 0; /* single key only */
		n = SPToPYKeyMap[k1][0];		
	}

	if (n > 0) 
	{
		strcpy(npy, GET_PYMAP_PY(n)); /* copy the py, won't overflow */
		len = strlen(npy);
	}
	else
	{
		npy[0] = py[0]; /* just copy the 1st letter! (like just input aoeiuv and it's not defined for ShengMu) */
		npy[1] = '\0';
		len = 1;
	}	

	if (k2 > 0) count = 2;  /* two letters */

	if (py[count] >= '0' && py[count] <= '5')
	{
		npy[len++] = py[count++];
		npy[len] = '\0';
	}

	if (py[count] == '\'')
	{
		npy[len++] = '\'';
		npy[len] = '\0';
	}

	return (k2 > 0 ? 2 : 1);
}
 
static u_char ShengMu[26][3]; /* aoeiuv is open 18+yw+6=26 */

static int SearchPYKey(u_char *py)
{
	int i;
	int ahead = py[0] - 'a';

    for(i=0; PYMap[ahead][i].key; i++)
    {
    	if ( !strcmp(PYMap[ahead][i].py, py) )  /* exact match */
			return (ahead << 8) | (i+1);
	}
	return 0; /* not found */
}

static int AddToSPTable(u_char *py, int l1, int l2)
{
	int	n = SearchPYKey(py);

	if (n == 0)
	{
		return -1;
	}

	if (SPToPYKeyMap[l1][l2] && strlen(py) < strlen(GET_PYMAP_PY(SPToPYKeyMap[l1][l2])))
		return -1; /* will keep the long one(er, etc), not aoev etc */

	SPToPYKeyMap[l1][l2] = n;

	return 0;
}

static int AddToShuangPinTable(u_char *py, u_char *key)
{
	u_char pystr[MAX_PY_LEN+4];
	int i, l1, l2;
	
	l1 = (key[0] == ';' ? 26: key[0] - 'a');
	if (!strcmp(py, "zh") || !strcmp(py, "ch") || !strcmp(py, "sh")) 
	{
		if (ShengMu[l1][0]) /* already has a mapping */
		{
			fprintf(stderr, "%c is used for shengmu %s already!\n", key[0], ShengMu[l1]);
			return -1;
		}	
		else strncpy(ShengMu[l1], py, sizeof(ShengMu[0]));
	}	
	else if (!key[1]) /* key len == 1, YunMu definition  sometimes they use a different letter for v(DOS/MSPY) */
	{
		for(i = 0; i < 26; i++)
		{
			if (!ShengMu[i][0]) continue; /* skip */
			strcpy(pystr, ShengMu[i]);  /* won't overflow, tested length before */
			strcat(pystr, py);
			AddToSPTable(pystr, i, l1+1);
		}
		return 0;
	}
	else /* 0-ShengMu definition */
	{
		l2 = (key[1] == ';' ? 27: key[1]-'a'+1);
		AddToSPTable(py, l1, l2);
		return 0;
	}
	
	return -1;
}

int LoadShuangPinData(u_char *filename)
{
	u_char *p, *py, *l;
	u_char buf[128];
	FILE *fp;
	int i, n;

	if (!filename || !(fp = fopen(filename, R_CNTRL))) return -1;

	fprintf(stderr, "CCE> Now loading ShuangPin map file %s\n", filename);

	memset(SPToPYKeyMap, 0, sizeof(SPToPYKeyMap));
	memset(ShengMu, 0, sizeof(ShengMu));

	for(i = 0; i < 26; i++)
		ShengMu[i][0] = ( strchr("aoeiuv", i+'a') ? '\0'  : i+'a');

	while(!feof(fp))
	{
		if (!fgets(buf, sizeof(buf), fp)) break; /* eof? */
		p = buf;
		while (*p == ' ' || *p == '\t') p++;
		if (!*p || !((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z'))) continue; /* ignore everything except */
		py = p;
		l = NULL;

		while(*p)
		{
			*p = tolower(*p);
			if (*p == '=' && l == NULL) 
			{	
				*p++ = '\0'; /* terminate the py string */
				l = p; /* l is the letter string */
				continue;
				
			}	

			if ((l == NULL && (*p < 'a' || *p > 'z')) || (l && *p != ';' && (*p < 'a' || *p > 'z'))) 
			{
				*p = '\0'; /* terminate l */
				break;
			}
			p++;
		}	

		if (l == NULL || !py[0] || !l[0] || strlen(py) > 4 || strlen(l) > 2) 
		{
			fprintf(stderr, "Error shuangpin definition: %s=%s\n", py, l);
			continue;
		}

		AddToShuangPinTable(py, l);
	}

	AddToShuangPinTable("a", "a");
	AddToShuangPinTable("o", "o");
	AddToShuangPinTable("e", "e");
	AddToShuangPinTable("i", "i");
	AddToShuangPinTable("u", "u");
	AddToShuangPinTable("v", "v");
	
	for(i = 0; i < 26; i++)
	{
		n = 0;

		if (ShengMu[i][0])	
			n = SearchPYKey(ShengMu[i]);
		else
		{

		}

		if (n == 0) continue;  /* not a valid one!? */
			
		SPToPYKeyMap[i][0] = n;
	}

	fclose(fp);

	return 0;
}

#define INDEX2CAPITAL(j)  ((j) == 0 ? ' ' : ((j) == 27 ? ';' : (j)+'A'-1))

void PrintSPKeyMapTable(FILE *fp)
{
	int i,j;

	if (!fp) return;

	fprintf(fp, "\nu_short SPToPYKeyMap[26][28]=\n" );
	fprintf(fp, "{   /* First Key: a-z(0-25)   Second Key: none(0) a-z(1-26) ;(27) */\n");
	for(i = 0; i < 26; i++)
	{
		fprintf(fp, "\n  {  ");
		for(j = 0; j < 28; j++)
		{
			fprintf(fp, "0x%04X,%s", SPToPYKeyMap[i][j], j%8==7? "\n     " : " ");
		}
		fprintf(fp, "},  /* %2d: %c */\n", i, i+'a');
	}

    fprintf(fp, "};\n");
}

void TestShuangPinCompleteness(FILE *fout)
{
	int i, j, m, n;
	int found, m1, n1;

	fprintf(stderr, "\nTesting ShuangPin Table...\n");
	for(i = 0; i < 26; i++)
	{
		for(j = 0; PYMap[i][j].key; j++)
		{
			found = m1 = n1 = 0;
			for(m = 0; m < 26; m++)
			for(n = 0; n < 28; n++)
			{
				if (!strcmp(GET_PYMAP_PY(SPToPYKeyMap[m][n]), PYMap[i][j].py)) 
				{
					if (found)
					{  /* duplicate SP->PY map found */
						fprintf(stderr, "Duplicate SP To PY map detected: %s -> %c%c & %c%c.\n",
							PYMap[i][j].py, INDEX2CAPITAL(m1+1), INDEX2CAPITAL(n1), INDEX2CAPITAL(m+1), INDEX2CAPITAL(n));
					}
					else 
					{
						found = 1;
						m1 = m;
						n1 = n;
					}
				}
			}
			if (!found)
				fprintf(stderr, "Pinyin %s not in SPToPYMap.\n", PYMap[i][j].py);
		}
	}
}

