#include <stdio.h>
#include <locale.h>

#include "ime.h"
#include "codetable.h"

#define WILD_MATCH		0	/* exact match */
#define WILD_PREFIX		1	/* no match, but maybe if go deeper */
#define WILD_UNMATCH		2	/* complete mismatch */

#define SC_FAIL			0
#define SC_OK			1

#define WILDCHAR_MATCHSINGLE	'?'
#define WILDCHAR_MATCHANY	'*'

#define	MAX_CANDIDATES_NUM	16
#define MAX_CANDIDATE_CHAR_NUM	64
#define MAX_INPUT_KEY_NUM	32

/* CodeTable Search Context */
typedef struct _HZSearchContext {

	int depth;					/* the current depth in the DFS traversal */

	char prefix[MAX_INPUT_KEY_NUM+1];		/* the prefix traced codes */
	char wildpattern[MAX_INPUT_KEY_NUM+1];		/* the wildchar pattern codes */
	char repcode[MAX_INPUT_KEY_NUM+1];		/* the tracing codes */
	tableNode *tNstack[MAX_INPUT_KEY_NUM+1];	/* the tableNode stack */
	short int tNnumSb[MAX_INPUT_KEY_NUM+1];		/* number of sibling tableNode */

} HZSearchContext;

/*********************** Load Codetable File ***********************/

/*
 * LoadCodeTableHeader -- load the input table header from codetable binary file
 */
int LoadCodeTableHeader (file_name, hztbl)
char *file_name;
CodeTableStruct *hztbl;
{
	FILE *ifile;
	char ctFlag[256];
	int  ver, i;

	DEBUG_printf("LoadCodeTableHeader ====\n");

	/* read table from file to memory buffer  */
	ifile = fopen (file_name, "r");
	if (! ifile) {
        	fprintf(stderr, "Unable to open the input table file \"%s\"\n",file_name);
        	return(-1);
    	}

	/* Read CodeTable File Flag */
	if (fread (ctFlag, strlen(CODETABLE_FLAG), 1, ifile) != 1) {
		fprintf (stderr, "Codetable File read Error:%s\n", file_name);
		fclose(ifile);
        	return(-1);
	}

	if (strncmp (ctFlag, CODETABLE_FLAG, strlen(CODETABLE_FLAG)) != 0) {
        	fprintf (stderr, "File is not in CodeTable format\n");
		fclose(ifile);
        	return(-1);
	}

	/* Read CodeTable Version Flag */
    	if (fread ((char *)(&ver), sizeof (int), 1, ifile) != 1)  {
		fprintf (stderr, "Codetable File read Error:%s\n", file_name);
		fclose(ifile);
        	return(-1);
	}

	if (ver != CODETABLE_VERSION) {
		fprintf (stderr, "File is not in correct Version Number\n");
		fclose(ifile);
        	return(-1);
	}

	if (fread((char *)hztbl, sizeof(CodeTableStruct), 1, ifile) != 1) {
        	fprintf(stderr, "Error in loading input table for %s\n", file_name);
		fclose(ifile);
        	return(-1);
    	}

    	fclose (ifile);
    	return(0);
}

/*
 * LoadCodeTable -- load the input table from codetable binary file
 */
int LoadCodeTable (file_name, hztbl)
char *file_name;
CodeTableStruct *hztbl;
{
	FILE *ifile;
	char ctFlag[256];
	int  ver, i;

	ifile = fopen (file_name, "r");
	if (! ifile) {
        	printf("Unable to open the input table file \"%s\"\n",file_name);
        	return(-1);
    	}

	/* Read CodeTable File Flag */
	if (fread (ctFlag, strlen(CODETABLE_FLAG), 1, ifile) != 1) {
		fprintf (stderr, "Codetable File read Error:%s\n", file_name);
    		fclose (ifile);
        	return(-1);
	}

	if (strncmp (ctFlag, CODETABLE_FLAG, strlen(CODETABLE_FLAG)) != 0) {
        	fprintf (stderr, "File is not in CodeTable format\n");
    		fclose (ifile);
        	return(-1);
	}

	/* Read CodeTable Version Flag */
    	if (fread ((char *)(&ver), sizeof (int), 1, ifile) != 1)  {
		fprintf (stderr, "Codetable File read Error:%s\n", file_name);
    		fclose (ifile);
        	return(-1);
	}

	if (ver != CODETABLE_VERSION) {
		fprintf (stderr, "File is not in correct Version Number\n");
    		fclose (ifile);
        	return(-1);
	}

	if (fread((char *)hztbl, sizeof(CodeTableStruct), 1, ifile) == 0) {
        	printf( "Error in loading input table for %s\n", file_name);
    		fclose (ifile);
        	return(-1);
    	}

	/* malloc memory for codetable information */
    	hztbl->nodeList = (tableNode *)calloc(hztbl->sizeNodeList,sizeof(tableNode));
    	hztbl->hzList = (unsigned char *)calloc(hztbl->sizeHZList, sizeof(unsigned char));
	hztbl->keyprompt = (keyPrompt *)calloc(MAX_USEDCODES_NUM, sizeof(keyPrompt));
	hztbl->functionkey = (functionKey *)calloc(MAX_FUNCTIONKEY_NUM, sizeof(functionKey));
    	if ((! hztbl->hzList) || (! hztbl->nodeList) ||
	    (! hztbl->keyprompt) || (! hztbl->functionkey)) {
            	printf( "No memory to load input table for %s\n", file_name);
    		fclose (ifile);
            	return(-1);
    	}

    	if ((fread ((char *)(hztbl->nodeList), sizeof(tableNode),
                (int)hztbl->sizeNodeList, ifile) != hztbl->sizeNodeList) ||
	    (fread ((char *)hztbl->hzList, sizeof(unsigned char),
                (int)(hztbl->sizeHZList), ifile) != hztbl->sizeHZList))
    	{
            	printf( "Error in loading input table for %s\n", file_name);
    		fclose (ifile);
            	return(-1);
    	}
	
	if (GETBIT(hztbl->bSectionsFlag, KEYPROMPT_SECTION)) {
    		if (fread((char *)(&(hztbl->keyprompt[0])), MAX_USEDCODES_NUM, 
			sizeof(keyPrompt), ifile) != sizeof(keyPrompt))
		{
            		printf( "Error in loading input table for %s\n", file_name);
    			fclose (ifile);
            		return(-1);
		}
	}

	if (GETBIT(hztbl->bSectionsFlag, FUNCTIONKEY_SECTION)) {
    		if (fread((char *)(&(hztbl->functionkey[0])), MAX_FUNCTIONKEY_NUM, 
			sizeof(functionKey), ifile) != sizeof(functionKey))
		{
            		printf( "Error in loading input table for %s\n", file_name);
    			fclose (ifile);
            		return(-1);
		}
	}

	for (i=0; i<MAX_USEDCODES_NUM; i++) {
		if (hztbl->keyprompt[i].prompt[0] == 0) {
			hztbl->keyprompt[i].prompt[0] = i;
			hztbl->keyprompt[i].prompt[1] = 0;
		}
	}

    	fclose (ifile);
    	return(0);
}

/*
 * UnloadCodeTable -- unload the input table, free the memory
 */
void UnloadCodeTable(hztbl)
CodeTableStruct *hztbl;
{
	if (! hztbl) return;

        if (hztbl->nodeList)  free ((char *)(hztbl->nodeList));
        if (hztbl->hzList)  free ((char *)(hztbl->hzList));
        if (hztbl->keyprompt)  free ((char *)(hztbl->keyprompt));
        if (hztbl->functionkey)  free ((char *)(hztbl->functionkey));
}

int Is_WildcharMatchAny_Key(hztbl, key)
CodeTableStruct *hztbl;
int key;
{
	if ( index(hztbl->UsedCodes, WILDCHAR_MATCHANY) )
		return(0);

	return(key == WILDCHAR_MATCHANY);

}

int Is_WildcharMatchSingle_Key(hztbl, key)
CodeTableStruct *hztbl;
int key;
{
	if ( index(hztbl->WildChar, key) )
		return(1);

	if ( index(hztbl->UsedCodes, WILDCHAR_MATCHSINGLE) )
		return(0);

	return(key == WILDCHAR_MATCHSINGLE);
}

int Is_Wildchar_Key(hztbl, key)
CodeTableStruct *hztbl;
int key;
{
	int ret;

	ret = ( Is_WildcharMatchSingle_Key(hztbl, key) || Is_WildcharMatchAny_Key(hztbl, key) );
	return(ret);
}

/*********************** Wild Char Matching ***********************/

/*
 * WildcardMatch() returns 3 values.  WILD_MATCH is for an exact match.
 * WILD_PREFIX means the current traversal path (from "top" to the current
 * node) matches a prefix of the wildcard pattern, which might suggest a
 * possible match if the traversal goes deeper.  WILD_UNMATCH is a total
 * mismatch, in which case the traversal should stop go any deeper.
 */

#define car(s)		(*(s))
#define cdr(s)		((s)+1)
#define empty(s)	(!(*(s)))

/*
 * recursively WildcharMatch string (with no wildcard) against pattern (possibly with wildcard)
 */
static int WildcharMatch(hztbl, string, pattern)
CodeTableStruct *hztbl;
char *string;
char *pattern;
{
	if (empty(pattern))
		return (empty(string) ? WILD_MATCH : WILD_UNMATCH);
	else if (Is_WildcharMatchAny_Key(hztbl, car(pattern))) {
		int x = WildcharMatch(hztbl, string, cdr(pattern));
		if (x == WILD_UNMATCH)
			return (WildcharMatch(hztbl, cdr(string), pattern));
		else
			return x;
	} 
	else if (empty(string))
		return WILD_PREFIX;
	else if (Is_WildcharMatchSingle_Key(hztbl, car(pattern)) || car(pattern) == car(string))
		return WildcharMatch(hztbl, cdr(string), cdr(pattern));
	else
		return WILD_UNMATCH;
}

/*********************** Traversal in Codetable Node ***********************/

/*
 * Search in the trie for the given suffix in the input sequent
 * that contains wildcard(s).  The "top" node matches the longest
 * prefix in the input sequent that contains no wildcard.
 * The traversal is a Deep-First-Search starting from "top".
 * But it visits the node before visiting any child under the node.
 *
 */

/*
 * Move to next sibling, if no more sibling, move up to parent's sibling
 */
static int Get_NextNode(pSC)
HZSearchContext *pSC;
{
	while (pSC->tNnumSb[pSC->depth] == 0) 
	{
		/* no more sibling, go up */
		if (pSC->depth == 0) {
			/* now at the topmost; we've tried everything! */
			pSC->tNstack[0] = NULL;
			return(SC_FAIL);
		} else {
			pSC->depth--;
			pSC->repcode[pSC->depth] = '\0';
		}
    	}

	/* go to sibling node */
	pSC->tNnumSb[pSC->depth]-- ;
	pSC->tNstack[pSC->depth]++ ; 
	pSC->repcode[pSC->depth-1] = pSC->tNstack[pSC->depth]->key;
	return(SC_OK);
}

int is_valid_candidate(unsigned char *hzptr, int hzlen, int dict_encode, int output_encode)
{
	int ret;

	if (dict_encode == output_encode)
		return(1);

	if (dict_encode == ENCODE_UTF8) {
		char *ip, *op, buffer[512];
		int ileft, oleft;
		
		ip = (char *)hzptr;
		ileft = hzlen;
		op = buffer;
		oleft = 512;
		memset(buffer, 0, 512);
		ret = Convert_UTF8_To_Native(output_encode, ip, ileft, &op, &oleft);
		DEBUG_printf("ctim: Convert UTF-8 to native, ret: %d\n", ret);
		if (ret == -1) {
			return(0);
		} else {
			if(buffer[0] && is_valid_encode_string(output_encode, buffer, strlen(buffer)) == 0)
				return(1);
			else
				return(0);
		}
	}

	if (dict_encode == ENCODE_GB18030) {
		if (output_encode == ENCODE_GB2312 || output_encode == ENCODE_GBK) {
			/* need check whether valid candidate*/
			ret = is_valid_encode_string(output_encode, hzptr, hzlen);
			if (ret == -1) return(0);
		}
		return (1);
	}
		
	if (dict_encode == ENCODE_GBK) {
		if (output_encode == ENCODE_GB2312) {
			/* need check whether valid candidate*/
			ret = is_valid_encode_string(output_encode, hzptr, hzlen);
			if (ret == -1) return(0);
		}
		return (1);
	}
	
	return (1);
}

int normal_search(hztbl, pSC, outbuf, attrbuf, pos, num)
CodeTableStruct *hztbl;
HZSearchContext *pSC;
unsigned char **outbuf;
unsigned char **attrbuf;
int pos;
int num;
{
	tableNode *tnptr;
	unsigned char *hzptr, tmpbuf[MAX_CANDIDATE_CHAR_NUM];
	int outptr, i, j, hzlen, len;
	int num_matched = 0;	/* pointer that how many items match the conditions */
	int num_selected = 0;	/* pointer that how many items be selected */
	char dict_encode, output_encode;

	dict_encode = hztbl->Encode;
	output_encode = hztbl->Output_Encode;

	DEBUG_printf("dict_encode:%d, output_encode:%d\n", dict_encode, output_encode);
	/* traversal all the subnodes of pSC->tNstack[0] */
	while (1) 
	{
		tnptr = pSC->tNstack[pSC->depth];

		/* check if any HZ choices on this node */
		if (tnptr->num_HZchoice > 0) 
		{
			/* if any HZ choices on this node,then check if match search conditions */
/*
			DEBUG_printf("repcode:%s  \t%d\n", pSC->repcode, tnptr->num_HZchoice);
*/

			/* get HZ choices */
			hzptr = hztbl->hzList + tnptr->pos_HZidx;
			for (i=0; i<tnptr->num_HZchoice; i++) 
			{
	    			if (*hzptr == HZ_PHRASE_TAG) {
					hzlen = *(hzptr + 1);
					hzptr += 2;
				} else {
					hzlen = get_char_len_by_encodeid(hztbl->Encode, hzptr);
				}

				if (is_valid_candidate(hzptr, hzlen, dict_encode, output_encode)) {
					num_matched ++;

					outptr = 0; 
					if (num_matched > pos) {
						DEBUG_printf("pos:%d, matched:%d\n", pos, num_matched);
						len = hzlen;
						if (len>MAX_CANDIDATE_CHAR_NUM) 
							len = MAX_CANDIDATE_CHAR_NUM;
						for (j=0; j< len; j++)
							tmpbuf[outptr++] = *(hzptr+j);
						tmpbuf[outptr++] = '\0';
					}
	
					if (outptr > 0) {
						strncpy((char *)outbuf[num_selected], tmpbuf, MAX_CANDIDATE_CHAR_NUM);
						snprintf((char *)attrbuf[num_selected], MAX_CANDIDATE_CHAR_NUM, "%s%s", 
							pSC->prefix, pSC->repcode); 
						num_selected ++;
					}
					if (num_selected >= num) return (num);
				}

				hzptr += hzlen;
			}
		}

		if (!(hztbl->nKeyByKeyMode))
			return (num_selected);

		/* if have any addition input key, move down */
		if (tnptr->num_NextKeys > 0) {
			tableNode *new_tnptr = &(hztbl->nodeList[tnptr->pos_NextKey]);

			pSC->depth++ ;
			pSC->tNnumSb[pSC->depth] = tnptr->num_NextKeys - 1;
			pSC->tNstack[pSC->depth] = new_tnptr;
			pSC->repcode[pSC->depth-1] = new_tnptr->key;
			continue;
	    	}

		/* No more additional key, hence no match for this node. */
		/* Don't go down, move forward */
		if ( Get_NextNode(pSC) == SC_FAIL )
			return (num_selected);
	}

}

int wildchar_search(hztbl, pSC, outbuf, attrbuf, pos, num)
CodeTableStruct *hztbl;
HZSearchContext *pSC;
unsigned char **outbuf;
unsigned char **attrbuf;
int pos;
int num;
{
	tableNode *tnptr;
	unsigned char *hzptr, tmpbuf[MAX_CANDIDATE_CHAR_NUM];
	int outptr, i, j, hzlen, len;
	int num_matched = 0;    /* pointer that how many items match the conditions */
	int num_selected = 0;   /* pointer that how many items be selected */
	int bMatched;

	char dict_encode, output_encode;

	dict_encode = hztbl->Encode;
	output_encode = hztbl->Output_Encode;

	DEBUG_printf("wildpattern:%s\n", pSC->wildpattern);

	/* traversal all the subodes of pSC->tNstack[0] */
	while (1) 
	{
		tnptr = pSC->tNstack[pSC->depth];

		/* check if any HZ choices on this node */
		bMatched = 0xff;
		if (tnptr->num_HZchoice > 0) {
			DEBUG_printf("repcode:%s  ", pSC->repcode);

			/* if any HZ choices on this node, then check match search conditions */
			bMatched = WildcharMatch(hztbl, pSC->repcode, pSC->wildpattern);
		}

		if (bMatched == WILD_MATCH) 
		{
			/* if any HZ choices on this node,then check if match search conditions */

			DEBUG_printf("repcode:%s  \t%d\n", pSC->repcode, tnptr->num_HZchoice);

			/* get HZ choices */
			hzptr = hztbl->hzList + tnptr->pos_HZidx;
			for (i=0; i<tnptr->num_HZchoice; i++) {
	    			if (*hzptr == HZ_PHRASE_TAG) {
					hzlen = *(hzptr + 1);
					hzptr += 2;
				} else {
					hzlen = get_char_len_by_encodeid(hztbl->Encode, hzptr);
				}

				if (is_valid_candidate(hzptr, hzlen, dict_encode, output_encode)) {
					num_matched ++;

					outptr = 0; 
					if (num_matched > pos) {
						len = hzlen;
						if (len>MAX_CANDIDATE_CHAR_NUM) 
							len = MAX_CANDIDATE_CHAR_NUM;
						for (j=0; j< len; j++)
							tmpbuf[outptr++] = *(hzptr+j);
						tmpbuf[outptr++] = '\0';
					}
	
					if (outptr > 0) {
						strncpy((char *)outbuf[num_selected], tmpbuf, MAX_CANDIDATE_CHAR_NUM);
						snprintf((char *)attrbuf[num_selected], MAX_CANDIDATE_CHAR_NUM, "%s%s", 
							pSC->prefix, pSC->repcode); 
						num_selected ++;
					}
					if (num_selected >= num) return (num);
				}

				hzptr += hzlen;
			}
		} else if (bMatched == WILD_UNMATCH) {
			/* need not to go down */
			if ( Get_NextNode(pSC) == SC_FAIL )
				return (num_selected);
			continue;
		}

		/* if have any addition input key, move down */
		if (tnptr->num_NextKeys > 0) {
			tableNode *new_tnptr = &(hztbl->nodeList[tnptr->pos_NextKey]);

			pSC->depth++ ;
			pSC->tNnumSb[pSC->depth] = tnptr->num_NextKeys - 1;
			pSC->tNstack[pSC->depth] = new_tnptr;
			pSC->repcode[pSC->depth-1] = new_tnptr->key;
			continue;
	    	}

		/* No more additional key, hence no match for this node. */
		/* Don't go down, move forward */
		if ( Get_NextNode(pSC) == SC_FAIL )
			return (num_selected);
	}
}

int codetable_search(hztbl, inbuf, outbuf, attrbuf, pos, num)
CodeTableStruct *hztbl;
char *inbuf;
unsigned char **outbuf;
unsigned char **attrbuf;
int pos;
int num;
{
	HZSearchContext search_context, *pSC;
	char tmp_inbuf[MAX_INPUT_KEY_NUM+1];

	int matched, i, j, len;
	tableNode *tnptr, *tCurTNptr;
	int search_num;
	
	memset(tmp_inbuf, 0, MAX_INPUT_KEY_NUM+1);
	for (i = 0; i < strlen(inbuf); i++) {
		if (i >= MAX_INPUT_KEY_NUM) break;
		tmp_inbuf[i] = tolower(inbuf[i]);
	}

	inbuf = tmp_inbuf;

	/* search maxinum matched top node that not include wildchar */
	tCurTNptr = &(hztbl->nodeList[0]);
	i = 0;
	while (i < strlen(inbuf))
	{
		if (	(Is_WildcharMatchSingle_Key(hztbl, inbuf[i])) || 
			(Is_WildcharMatchAny_Key(hztbl, inbuf[i])) )
			break;

		matched = 0;
		j = 0;
		tnptr = &(hztbl->nodeList[tCurTNptr->pos_NextKey]);
		while (j < tCurTNptr->num_NextKeys)
		{
			if (inbuf[i] == tnptr->key) {
				matched = 1;
				break;
			}
			tnptr ++;
			j ++;
		}
		if (matched == 0)
			return(0);

		tCurTNptr = tnptr;
		i++;
	}

	/* set search context */
	pSC = &search_context;
	pSC->depth = 0;
	pSC->tNnumSb[0] = 0;
	pSC->tNstack[0] = tCurTNptr;
	memset(pSC->repcode, 0, MAX_INPUT_KEY_NUM+1);
	memset(pSC->prefix, 0, MAX_INPUT_KEY_NUM+1);
	memset(pSC->wildpattern, 0, MAX_INPUT_KEY_NUM+1);

	if (i>0) {
		len = MAX_INPUT_KEY_NUM+1>i ? i : MAX_INPUT_KEY_NUM+1;  
		strncpy(pSC->prefix, inbuf, len);
	}

	search_num = num > MAX_CANDIDATES_NUM ? MAX_CANDIDATES_NUM : num;
	if (i == strlen(inbuf)) {
		/* no wildchar,  begin normal mode search */
		num = normal_search(hztbl, pSC, outbuf, attrbuf, pos, search_num);
	} else {
		/* have any wildchar, begin wildchar mode search */
		strncpy(pSC->wildpattern, inbuf+i, MAX_INPUT_KEY_NUM+1);
		num = wildchar_search(hztbl, pSC, outbuf, attrbuf, pos, search_num);
	}
	return(num);
}

