/* vi: set sw=4 ts=4: */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h> 
#include <ctype.h>
#include <sys/types.h>
#include <string.h>

#include "defs.h"
#include "convert.h"
#include "hzinput.h"
#include "pinyin.h"
#include "safestring.h"
#include "cin2tab.h"

#define MAX_FREQ_2BYTES  65535

char LineBuf[LINE_BUFFER_LEN];
int lineno;
u_char *cmd, *arg;

int OldCoding; /* source encoding */
int NewCoding; /* target encoding  */
int YinFormat; /* 1 pinyin or 2 zhuyin(libtabe format) */

char CurCINPath[CCE_PATHNAME_MAX];

void print_error(char *fmt,...)
{
  va_list args;

  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr,"\n");
  exit(-1);
}

u_char *skip_space(u_char *s)
{
	while ((*s==' ' || *s=='\t') /* && *s */) s++;
	return s;
}

u_char *to_space(u_char *s)
{
	while (*s && *s!=' ' && *s!='\t' && *s!='\r' && *s != '\n') s++; 
	return s;
}

void del_nl_space(u_char *s)
{
	u_char *t;

	int len=strlen(s);
	if (!*s) return;
	t=s+len-1;
	while ( t >= s && (*t=='\n' || *t==' ' || *t=='\t' || *t == '\r' ) ) t--;
	t[1]='\0';
}

void get_line(FILE *fr, u_char *buf)
{
	while (!feof(fr)) 
    {
		buf[0] = '\0';  /* empty string, file end */
		fgets(buf, LINE_BUFFER_LEN, fr);
		lineno++;
		if (*skip_space(buf) == '#') continue;
		else return;
	}
}

void cmd_arg(FILE *fr, u_char *s, u_char **cmd, u_char **arg)
{
	char *t;

	get_line(fr, s);
	if (!*s) { *cmd=*arg=s; return; } /* all zero */

	s=skip_space(s);
	t=to_space(s);
	*cmd=s;
	if (!(*t))  /* empty */
	{
		*arg=t;
		return;
	}
	*t++ = '\0';
	t=skip_space(t);
	del_nl_space(t);
	*arg=t;
}

int IsGB2312(unsigned char ch, unsigned char cl)
{
    return (ch >= 0xA1 && ch <= 0xFE && cl >= 0xA1 && cl <= 0xFE);
}

int IsGBK(unsigned char ch, unsigned char cl)
{
    return (ch >= 0x81 && ch <= 0xFE && cl >= 0x40 && cl <= 0xFE && cl != 0x7F);
}

int IsBig5(unsigned char ch, unsigned char cl)
{
    return (ch >= 0xA1 && ch <= 0xFE && ((cl >= 0x40 && cl <= 0x7E) || (cl >= 0xA1 && cl <= 0xFE)));
}

int IsJIS(unsigned char ch, unsigned char cl)
{
    return (ch >= 0xA1 && ch <= 0xFE && cl >= 0xA1 && cl <= 0xFE);
}

int IsKSC(unsigned char ch, unsigned char cl)
{
    return (ch >= 0xA1 && ch <= 0xFE && cl >= 0xA1 && cl <= 0xFE);
}

static char *Coding[] = { "Not Checked", /* "ISO-2022-JP", */ "EUC-JP", "Shift-JIS", 
	"Big5", "GB2312", "GBK", "KSC"};

int SysCoding = CODE_GB;

int VerifyCoding(unsigned char ch, unsigned char cl)
{
    int valid;

    switch(SysCoding)
    {
    case CODE_GB:
        valid = IsGB2312(ch, cl);
        break;
    case CODE_GBK:
        valid = IsGBK(ch, cl);
        break;
    case CODE_BIG5:
        valid = IsBig5(ch, cl);
        break;
	case CODE_EUC:
		valid = IsJIS(ch, cl);
		break;
	case CODE_KSC:
		valid = IsKSC(ch, cl);
		break;
    case SYSCODE_NOCHECK:
        return 1; /* don't check */
    default:
        valid = ( ch >= 0x80 && cl >= 0x21);
        break;
    }

    if (!valid)
    {
        return 0;
    }

    return 1;
}

int VerifyMapping(char *key, char *str, int len)
{
	int i;
	int valid = 1;

	for(i = 0; i < len; i += 2)
	{
		if (!VerifyCoding(str[i], str[i+1])) 
		{
			valid = 0;
			break;
		}
	}

	if (!valid)
	{
		fprintf(stderr, "Line %d: Mapping '%s %s' is not a valid %s string!\n", 
				lineno, key, str, Coding[SysCoding]);
		return 0;
	}
	return 1;
}

int identify_encoding(u_char *arg)
{
	if (!strncasecmp(arg, "gbk", 3)) return CODE_GBK;
	if (!strncasecmp(arg, "gb", 2))  return CODE_GB;
	if (!strncasecmp(arg, "big", 3) || !strncasecmp(arg, "b5", 2)) return CODE_BIG5;
	if (!strncasecmp(arg, "jis", 3)) return CODE_EUC;
	if (!strncasecmp(arg, "ks", 2))  return CODE_KSC;

	return -1;
}

static int TotalKeyNum;
static u_char wildcardchar = '\0';

#define HEX_STR  "0123456789abcdef"
#define OCT_STR  "01234567"

static int get_keys_from_string(u_char *str, u_char *dest, int num)
{
	int count, i, len;
	int value;
	u_char *hexstr = HEX_STR;
	u_char *octstr = OCT_STR;
		
	i = 0;
	while(str[i] && str[i] != ' ' && str[i] != '\t' && str[i] != '\r' && str[i] != '\n')  
	{
		str[i] = tolower(str[i]);/* A-Z -> a-z */
		i++;
	}
	str[i] = '\0'; /* terminate it */
	len = i;
	
	dest[0] = '\0';
	count = 0;
	for(i = 0; i < len; i++)
	{
		if (count >= num) return count;

		if (str[i] == '\\')
		{
			if (str[i+1] == 'x') /* \xab */
			{
				if (len-i <=3 || !strchr(hexstr, str[i+2]) || !strchr(hexstr, str[i+3]))
					fprintf(stderr, "Unknown key \\x%c%c, ignored !\n", 
							str[i+2] ? str[i+2] : ' ', str[i+3] ? str[i+3] : ' ');
				else
				{
					value = (u_int)strchr(hexstr, str[i+2]) - (u_int)hexstr;
					value = value * 16 + (u_int)strchr(hexstr, str[i+3]) - (u_int)hexstr;
					dest[count++] = (u_char)(value);
				}
				i += 3;  /* skip \xab */
				continue;
			}
			else if (strchr(octstr, str[i+1]))  /* \0-7 */
			{
 				if (len-i <=3 || !strchr(octstr, str[i+2]) || !strchr(octstr, str[i+3]))
				{
                	fprintf(stderr, "Unknown key \\%c%c%c, ignored !\n", 
						str[i+1], str[i+2] ? str[i+2] : ' ', str[i+3] ? str[i+3] : ' ');
				}
                else
                {
                    value  = (str[i+1] - '0')*64 + (str[i+2] - '0')*8 + (str[i+3] - '0');
                    dest[count++] = (u_char)(value);
                }
                i += 3;  /* skip \xab */
                continue;
			}
			else if (str[i+1] == '\\')
			{
				dest[count++] = str[i+1];
			}
			else
			{
				fprintf(stderr, "Unknown key \\%c, not supported!\n", str[i+1]);
			}
			i++; /* skip the  */
		}
		else
		{
			dest[count++] = str[i];
		}
	}

	dest[count] = '\0';

#ifdef DEBUG
	fprintf(stderr, "Keys(str=%s, len=%d): ", str, len);
	for(i = 0; i < count; i++)
		fprintf(stderr, "%02X ", dest[i]); 
	fprintf(stderr, "\n");
#endif

	return count;
}

void read_cin_options(FILE *fr, hz_input_table *table)
{

	while(!feof(fr) && strcasecmp(cmd, CIN_KEYNAME_STR) 
			&& strcasecmp(cmd, CIN_CHARDEF_STR) ) /* not equal to keyname & chardef */
	{
		if (!strcasecmp(cmd, "%gen_inp"))
		{ /* skip the first line, for XCIN compatibility */
		}
		else if ((!strcasecmp(cmd, "%encoding") || !strcasecmp(cmd, "%coding")) && (*arg))
		{
			int coding = identify_encoding(arg);
			if (coding >= 0) table->encoding = coding;
		}
		else if (!strcasecmp(cmd,"%ename") && (*arg) )
    	{
        	safe_strncpy(table->ename, arg, sizeof (table->ename));
    	}
    	else if ((!strcasecmp(cmd,"%prompt") || !strcasecmp(cmd, "%cname")) && (*arg) )
		{
    		safe_strncpy(table->cname, arg, sizeof (table->cname));
		}
		else if (strcasecmp(cmd,"%selkey") == 0 && (*arg) )
		{
			safe_strncpy(table->selkey, arg, sizeof (table->selkey));
		}
		else if (strcasecmp(cmd, "%endkey") == 0) 
		{
		}
		else if (strcasecmp(cmd, "%pageprevkey") == 0)
		{
			
			get_keys_from_string(arg, table->pageprevkey, MAX_CHANGEPAGE_KEYS);
		}
		else if (strcasecmp(cmd, "%pagenextkey") == 0)
		{
			get_keys_from_string(arg, table->pagenextkey, MAX_CHANGEPAGE_KEYS);
		}
		else if (strcasecmp(cmd, "%selfirstkey") == 0)
		{
			get_keys_from_string(arg, table->selfirstkey, MAX_SELFIRST_KEYS);
		}
		else if (strcasecmp(cmd, "%wildcardkey") == 0 && (*arg))
		{
			if (arg[0] > ' ' && arg[0] < 127)  /* make sure it's a printable char */
				wildcardchar = arg[0]; /* SPACE and DEL is not allowed */
		}
		else if (strcasecmp(cmd, "%maxpress") == 0 && (*arg))
		{
		}
		else if ((strcasecmp(cmd,"%last_full") == 0  || strcasecmp(cmd, "%auto_select") == 0 ) && (*arg)) 
		{
        	if ( arg[0]=='0' || arg[0] == 'n' || arg[0] == 'N') table->auto_select = 0;
		}
		else if (strcasecmp(cmd, "%prefix_match") == 0 && (*arg))
		{
			if ( arg[0]=='0' || arg[0]=='n' || arg[0]=='N') table->prefix_match_enable = 0;
		}
		else if (strcasecmp(cmd, "%key_prompt") == 0 && (*arg))
		{
			if (arg[0] == '1' || arg[0] == 'y' || arg[0] == 'Y') table->key_prompt = 1;
		}
		else if (strcasecmp(cmd,"%dupsel") == 0 && (*arg) ) 
		{
			table->MaxDupSel=atoi(arg);
		}
		else if ((strcasecmp(cmd,"%keylen") == 0 || strcasecmp(cmd, "%key_len") == 0) && (*arg) ) 
    	{
        	table->key_len = atoi(arg);
	    	if (table->key_len != 5 && table->key_len != 6)
	    	{
				fprintf(stderr, "Warning: unrecognized key length %d! Using default 6.\n", table->key_len);
				table->key_len = 6;
			}
    	}
		else if (strcasecmp(cmd, "%input_type") == 0 && (*arg) )
		{
			if (strcasecmp(arg, "intcode") == 0) 
				table->InputType = INPUT_TYPE_INTCODE;
			else if (strcasecmp(arg, "pinyin") == 0 || strcmp(arg, "zhiyin") == 0) 
				table->InputType = INPUT_TYPE_PINYIN;
			else if (strcasecmp(arg, "zhuyin") == 0 || strcasecmp(arg, "phone") == 0)
				table->InputType = INPUT_TYPE_ZHUYIN;
		}
		else
		{
			if (cmd[0] == '%') /* added this one, otherwise too many? */
				fprintf(stderr, "Unknown CIN keyword %s, ignored\n", cmd);
			else if (cmd[0] > ' ') break;  /* if meet non-% ASCII stuff, go  out directly */
		}
		
        cmd_arg(fr,LineBuf,&cmd, &arg);
	}

	if (feof(fr))
		print_error("It is not a valid CCE input method file.\n");
	
	if (table->MaxDupSel <= 0)
	{
		table->MaxDupSel = strlen(table->selkey);
		if (table->MaxDupSel <= 0)
			print_error("No %%dupsel entry found and %%selkey entry error!");
#ifdef DEBUG
		fprintf(stderr, "Warning: no %%dupsel entry found, using %d\n", table->MaxDupSel);
#endif
	}

	if (table->MaxDupSel > MAX_SELECT_KEYS)
	{
		fprintf(stderr, "Warning: %%dupsel is %d, using maximum allowed value %d\n", 
				table->MaxDupSel, MAX_SELECT_KEYS);
		table->MaxDupSel = MAX_SELECT_KEYS; 
		table->selkey[table->MaxDupSel] = '\0';  /* terminated the selkey string */
	}
}	

static void get_input_keys(u_char *p, u_char TmpKeyName[][2], hz_input_table *table)
{
	int i;

	table->keyname_bytes = 1; /* always ASCII */
	TotalKeyNum = strlen(p);

	if (TotalKeyNum > MAX_INPUT_KEYS-3) TotalKeyNum = MAX_INPUT_KEYS-3;
	for(i = 0; i < TotalKeyNum; i++)
	{
		table->KeyMap[p[i]] = i+1;
		TmpKeyName[i+1][0] = p[i];
		TmpKeyName[i+1][1] = '\0';
	}
}

void read_cin_keyname(FILE *fr, hz_input_table *table)
{
	int i, j, k;
	u_char TmpKeyName[MAX_INPUT_KEYS][2];

	if (strcasecmp(cmd, CIN_KEYNAME_STR))
	{
		fprintf(stderr, "Line %d: %" CIN_KEYNAME_STR " unfound, using default a-z.\n", lineno);
		get_input_keys(DEFAULT_INPUT_KEYS, TmpKeyName, table);
	}
	else if (strcasecmp(arg,"begin")) 
	{
		get_input_keys(arg, TmpKeyName, table);
	}
	else  /* multiple lines format */
	{
		TotalKeyNum=0;
		while(!feof(fr)) 
		{
			cmd_arg(fr,LineBuf,&cmd, &arg);
		
			if (cmd[0] == '%' && cmd[1] > ' ' ) 
			{
				if (strcasecmp(cmd, CIN_KEYNAME_STR) || strcasecmp(arg, "end")) 
					fprintf(stderr, "Line %d: '%" CIN_KEYNAME_STR " end' expected.\n", lineno);
				if (!strcasecmp(cmd, CIN_KEYNAME_STR))
					cmd_arg(fr, LineBuf, &cmd, &arg); /* read-one-more, that's the tradition */
				break;  /* keyname end */
			}

			if (!cmd[0] || !arg[0]) continue; /* ignore incomplete line */

			k = (int)cmd[0];
			if (table->KeyMap[k] > 0) 
			{
fprintf(stderr, "Line %d:  key %c is already used\n",lineno, k);
				continue;  /* ignore 2nd one */
			}
		
			if (k == wildcardchar) 
			{
				fprintf(stderr,"Line %d: key %c is same as the wildcard key", lineno, k);
				wildcardchar = '\0';
			}
			
			table->KeyMap[k] = ++TotalKeyNum;

        	if (TotalKeyNum > MAX_INPUT_KEYS-3)
            	print_error("Error, at most %d key can be defined!\n", MAX_INPUT_KEYS-3);

			strncpy(TmpKeyName[TotalKeyNum], arg, 2);  /* we copy 2 bytes, for ASCII 2nd byte is 0 */

			if ((arg[1] && table->keyname_bytes == 1 && TotalKeyNum  > 1) || 
            	(arg[1] == '\0' &&  table->keyname_bytes == 2))
					print_error("%d:  You should either use ASCII or Chinese as keyname, can't use both!", lineno);

	    	if (arg[1])	table->keyname_bytes = 2;
		}
	} /* else */

	fprintf(stderr, "InputKeys: \t");
	j = 0;
	for(i = 0; i < 128; i++)	
		if (table->KeyMap[i] > 0)
		{
			j++; /* increase the keymap (starting from 1) */
			table->KeyName[j][0] = TmpKeyName[table->KeyMap[i]][0];
			table->KeyName[j][1] = TmpKeyName[table->KeyMap[i]][1];
			table->KeyName[j][2] = i; /*back map to the input key */
			table->KeyMap[i] = j;  /* reorder keymap according to ASCII */

			fprintf(stderr, "%c", i);
		}

	if (wildcardchar > 0)
		fprintf(stderr, " (WildCard %c)", wildcardchar);

	fprintf(stderr, "\nSelectKeys:\t%s\n", table->selkey);

	++TotalKeyNum;

	if (wildcardchar > 0)
	{
		table->KeyMap[(int)wildcardchar] = ++TotalKeyNum;
		table->KeyName[TotalKeyNum][0] = wildcardchar;
		table->KeyName[TotalKeyNum][1] = '\0';
		table->KeyName[TotalKeyNum][2] = '\0';  
		table->wildcard = TotalKeyNum; 
	}

	table->KeyMap[32] = 0;  /* SPACE = 32, to index 1 */
	table->KeyName[0][0] = ' ';
	table->KeyName[0][1] = (table->keyname_bytes == 2 ? ' ' : '\0');
	table->KeyName[0][2] = '\0'; 

	if (TotalKeyNum >= 32 && table->key_len <= 5)
	{
		fprintf(stderr, "Warning: Using %d input keys, key length needs to be 6!\n", TotalKeyNum);
		table->key_len = 6;
	}

	table->TotalKey = TotalKeyNum;    /* include space */
}

void read_msmb_format(FILE *fr, hz_input_table *table)
{
	u_char UsedCodes[128];

	UsedCodes[0] = '\0';
	
	table->MaxDupSel = strlen(table->selkey);

	while(!feof(fr))   
	{
		cmd_arg(fr, LineBuf, &cmd, &arg); 
		if (!strcasecmp(cmd, MSMB_TEXT_STR) || !cmd[0]) break; /* exit */

		if (!strncasecmp(cmd, "Name=", strlen("Name=") ))
			safe_strncpy(table->cname, cmd+strlen("Name="), sizeof(table->cname));
		else if (!strncasecmp(cmd, "UsedCodes=", strlen("UsedCodes=") ))
		{
			safe_strncpy(UsedCodes, cmd+strlen("UsedCodes="), sizeof(UsedCodes));
		}	
		else if (!strncasecmp(cmd, "WildChar=", strlen("WildChar=")))
		{
			wildcardchar = cmd[strlen("WildChar=")];
		}
	}

	if (!UsedCodes[0])
	{
		fprintf(stderr, "No 'UsedCodes=' found, assuming a-z.\n");
		strncpy(UsedCodes, DEFAULT_INPUT_KEYS, sizeof(UsedCodes));
	}
	
	cmd =  CIN_KEYNAME_STR;
	arg = UsedCodes;  /* hopefully it's not 'begin' :) */
	read_cin_keyname(fr, table);
}
		
u_char *FilterFormatOptions(u_char *p, u_char *str)
{
	p = skip_space(p); /* skip space */
    if (!*p || *p == '#' || *p == '\r' || *p == '\n') return PTR_CONTINUE;

	if (*p != '%') return p; /* process it */

    if (!strncasecmp(p, str, strlen(str)))
    {
        p += strlen(str);
        p = skip_space(p);
        if (!strncasecmp(p, "begin", 5)) return PTR_CONTINUE;  /* read one line more */
        else if (!strncasecmp(p, "end", 3)) return PTR_BREAK;  /* end */
    }
	else if (!strncasecmp(p, ENCODING_STR, strlen(ENCODING_STR)))
	{
		int coding;
				
        p += strlen(ENCODING_STR);
		p = skip_space(p);
 
		coding = identify_encoding(p);
		if (coding >= 0) OldCoding = coding; 
	}
	else if (!strncasecmp(p, FORMAT_STR, strlen(FORMAT_STR)))
    {
        p += strlen(FORMAT_STR);
		p = skip_space(p);
		if (!strncasecmp(p, "zhuyin", 6) || !strncasecmp(p, "libtabe", 7))
			YinFormat = ZHUYIN_FORMAT;
		else if (!strncasecmp(p, "pinyin", 6))
			YinFormat = PINYIN_FORMAT;
    }

	return PTR_CONTINUE; /* skip this line */
}

static int get_freq(char *f, int mode)
{
	int freq;
        	
	if (f == NULL || mode < 0) return 0;

	sscanf(f, "%d", &freq);
	if (freq < 0)  freq = 0;
	
	switch (mode)
	{
	case 2:
		if (freq > MAX_FREQ_2BYTES - 5000) 
		{
			freq = (MAX_FREQ_2BYTES - 5000) + freq/500;
		}
	
		if (YinFormat == PINYIN_FORMAT)
		{
			if (freq <= 255) freq *= 100;
			else
			{
				fprintf(stderr, "ERROR: Assuming pinyin format's freq is 0-255, but freq=%d!\n", 
						freq);
				fprintf(stderr, "ERROR: Please modify the source code!\n");
				exit(1);
			}
		}
		
		if (freq > MAX_FREQ_2BYTES - 16) freq = MAX_FREQ_2BYTES - 16; /* leave some space at top */
		break;

	case 1:
    	if (freq > 200*250) freq = 250;
   		else if (freq < 250) freq /= 5;  /* 0-50 */
    	else freq = freq / 250 + 50;  /* 50-250 */
		break;

	case 0:
		if (freq > 250) freq = 250;
		break;

	default:
		break;
	}

	return freq;
}

u_char *FilterFormatCharFreq(u_char *p, u_char *str, u_char **cp, int *lenp, int *freqp, int freqmode )
{
	int i, len, freq = 0;
	u_char *c, *f;
	
	p = FilterFormatOptions(p, str);
	if (p == PTR_CONTINUE || p == PTR_BREAK) return p;
	  
	len = 0;
   	c = p;
   	while(p[0] >= 0x80 && p[1] > 0) /* all the CJK characters, first byte >= 0x80 */
   	{
   		p += 2;
   		len++;
   	}
	if (!*p || len <= 0) return PTR_CONTINUE; /* continue */
	*p++ = '\0';
    		
	for(i = 0; i < len; i++)
    {
    	if (ConvertEncoding_Char(OldCoding, NewCoding, c[2*i], c[2*i+1],
           		c+2*i, c+2*i+1) <= 0
			|| (NewCoding == CODE_BIG5 && c[2*i] >= 0xFA)
			)
		{
#ifdef DEBUG
			fprintf(stderr, "Phrase %s can't be converted into target encoding, ignored.\n", c);
#endif
			return NULL;
		}
	}

	*cp = c;
	*lenp = len;

	p = skip_space(p); /* freq is optional */
	f = p;
	while(*p >= '0' && *p <= '9') p++;
	if (*p) *p++ = '\0';
		
	freq = get_freq(f, freqmode); /*sscanf(f, "%d", &freq); */
	*freqp = freq;

	return p;
}

int LoadFileSection( u_char *cmd, u_char *arg, u_char *section,
        FILE *fr, int (*LoadFunc)(FILE *))
{
    FILE *fp;
	char tmpF[CCE_PATHNAME_MAX];
	
    if (!cmd[0] || !arg[0] || strcasecmp(cmd, section))
        return -1;
    if (!strcasecmp(arg, "begin")) /* built-in */
    {
        (*LoadFunc)(fr);
    }
    else
    {
        if ( (fp = fopen(arg, "r")) == NULL)  /* for text file, ok in DOS? */
		{
			if (strlen(arg) + strlen(CurCINPath) < CCE_PATHNAME_MAX)
			{
				strcpy(tmpF, CurCINPath);
				strcat(tmpF, arg);  /* won't overflow */
				fp = fopen(tmpF, "r");
			}
		}

		if (fp == NULL)
            print_error("Failed to open file %s", arg);
		
        (*LoadFunc)(fp);
        fclose(fp);
    }
    return 0;
}

