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

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

#define MAX_LEN2PHRASE_NUM	512  /* max 65535 16 bits, actually only ~300 */
#define MAX_PHRASE_NUM  	255

#define MAX_PHRASE_LEN  32

#define FREQ_UNKNOWN 0xFFFF

static int zinum; /* total zi number */

static Zi *ZiTable; /* Big5 space */
static Yin yin_table[TOTAL_ZHUYIN_NUM];

static u_short NumZi[TOTAL_ZHUYIN_NUM][MAX_TONE_NUM];
static u_short YinZiMap[TOTAL_ZHUYIN_NUM][MAX_TONE_NUM][150];
static int maxyinchar = 0, maxyinyin = 0;

static int file_size = 0;

typedef struct
{
	int PhraseNum[MAX_ZYPHRASE_LEN]; /* 2,3/4,5 */
	u_char *PhrasePtr[MAX_ZYPHRASE_LEN];
}Ci;

static Ci *CiAry; /* Big5 space for phrases */
static int CiStat[MAX_ZYPHRASE_LEN+1], TotalCi, MaxCiNum; /* how many cis */
static int MinCiIndex = 0xffff, MaxCiIndex = -1;

static void AddToYinZiMapTable(unsigned short yin, unsigned short zi)
{
    int i, j;
    int index = yin & 0x07FF; /* 11 bits */

    for(i = 0; i < 5; i++)
    {
        if ((1 << (TONE_OFFSET + i)) & yin) /* valid tone */
        {
			for(j = 0; j < NumZi[index][i]; j++)
				if (YinZiMap[index][i][j] == zi) return; /* ignore it */
			
            YinZiMap[index][i][ NumZi[index][i]++ ] = zi; 
            if (NumZi[index][i] > maxyinchar)
            {
                maxyinchar = NumZi[index][i];
                maxyinyin = index | ( 1<< (TONE_OFFSET + i));
            }
        }
    }
}

static int AddOneYin(u_short from_yin, u_short to_yin[], int numyin)
{
	int i;

    for(i = 0; i < numyin && to_yin[i] ; i++)
   	{
       	if ((to_yin[i] & 0x07FF) == (from_yin & 0x07FF))
       	{ /* same yin different tones */
           	to_yin[i] |= from_yin;
           	return 0; /* done */
       	}
   	}

	if (i >= numyin) return -1; /* no space */
	to_yin[i] = from_yin;

	return 0;
}

static int ConvertOneYinToZhuyin(int format, unsigned char *buf, u_short yin[MAX_ZHUYIN_NUMBER])
{
	unsigned char key[10];
    ZYString zhuyin[1];
	unsigned short y[1];
	unsigned short mask[1];
	
	memset(zhuyin, 0, sizeof(zhuyin));

	if (format == ZHUYIN_FORMAT)
	{
		if ( ConvertZhuyinStrToKeys(buf, key) <= 0 ) /* failure  */
			return 0;
	 
	    if (ParseZhuyinKeys(KEYBOARD_MAP_ZOZY, key, zhuyin, y, mask, 1) != 1)
    	{
			 return 0;
    	}
	}
	else if (format == PINYIN_FORMAT)
	{
		u_char pystr[1][MAX_PY_LEN+3];
		
		buf = skip_space(buf);
		if (ConvertPinyinKeysToZhuyin(KEYBOARD_MAP_PINYIN, buf, zhuyin, y, mask, pystr, NULL, 1) != 1)
		{
			return 0;
		}
	}
	else return 0; /* unknown format */

	AddOneYin(y[0], yin, MAX_ZHUYIN_NUMBER);
			
	return y[0];

}

static int ConvertMultiYinToZhuyin(int format, unsigned char *buf, u_short yin[MAX_ZHUYIN_NUMBER])
{
	int total = 0;
	int n;
	u_char *p;

	p = buf;
	while(*p)
	{
		n = ConvertOneYinToZhuyin(format, p, yin);	
		
		if (n == 0) /* 0 for failure */
			return 0;
		
		total++;

		while (*p && *p != ',') p++;
		if (*p == ',') p++;
	}	

	return total;
}

int convert_to_zhuyins(int format, u_char *p, u_short yin[][MAX_ZHUYIN_NUMBER], int len)
{
	int yincount = 0, ret;
	u_char *m1, *m2;

	while(*p && *p != '\r' && *p != '\n' && *p != '#')
	{
		p = skip_space(p); /* skip space */
		if (*p == '\r' || *p == '\n' || *p == '#') break;

		if (*p == '[') /* multiyin */
		{
			m1 = ++p;
			while(*p && *p != ']') p++;
			if (*p == ']') *p++ = '\0';
	
			ret = ConvertMultiYinToZhuyin(format, m1, yin[yincount % len]);
			if (ret == 0)
			{
				fprintf(stderr, "Error MultiYin Yin=%s\n", m1);
				return yincount;
			}
		}	
		else /* single yin */
		{
			m2 = p;
			while(*p && *p != ' ' && *p != '#' && *p != '\t' && *p != '\r' && *p != '\n') p++;
			if (*p) *p++ = '\0';
			ret = ConvertOneYinToZhuyin(format, m2, yin[yincount % len]);
				
			if (ret == 0)
			{
				fprintf(stderr, "Error SingleYin Yin=%s\n", m2);
				return yincount;
			}
		}
		yincount++;
	}

	return yincount;
}

#define  MAXNUM(len) (len == 2? MAX_LEN2PHRASE_NUM : MAX_PHRASE_NUM)
#define  PHR_SIZE(num, len)  ( (num) * (2 * (len-1) + sizeof(ZYFREQTYPE)) )

static void AddToCiAry(u_char *c, int len, int freq)
{
	int j, index;
	u_char *p; /*, tmpbuf[3]; */
	u_int phr_size;

	index = CalculateCharIndex(NewCoding, c[0], c[1]);
	if (CiAry[index].PhrasePtr[len-1] == NULL) /* have not allocated memory yet */
	{
		phr_size = PHR_SIZE(/* MAXNUM(len) */ MAX_PHRASE_NUM, len); 

		CiAry[index].PhrasePtr[len-1] = (u_char *)malloc(phr_size);
		if (CiAry[index].PhrasePtr[len-1] == NULL)
		{
			fprintf(stderr, "Failed to allocate %d bytes! (len=%d)\n", phr_size, len);
			return;
		}
		
		memset(CiAry[index].PhrasePtr[len-1], 0, phr_size);
	}

	p = CiAry[index].PhrasePtr[len-1]; 
	for(j = 0; j < CiAry[index].PhraseNum[len-1]; j++)
	{
		if (strncmp(c+2, p, 2*(len-1)) == 0) break;
		p += 2*(len-1) + sizeof(ZYFREQTYPE); /* also skip freq */
	}
	if (j < CiAry[index].PhraseNum[len-1]) /*  */
	{
#ifdef DEBUG
		fprintf(stderr, "Duplicate phrase %s found, ignored.\n", c);
#endif
		return;  
	}

	if (len == 2 && CiAry[index].PhraseNum[len-1] == MAX_PHRASE_NUM) /* realloc */
	{
		u_char *tmpp = CiAry[index].PhrasePtr[len-1];
		int tmpoff = (u_int)p - (u_int)tmpp;

		phr_size = PHR_SIZE(MAX_LEN2PHRASE_NUM, len);
		CiAry[index].PhrasePtr[len-1] = realloc(tmpp,phr_size);
		if (CiAry[index].PhrasePtr[len-1] == NULL)
		{
			fprintf(stderr, "Failed to allocate %d bytes.\n", phr_size);
			return;
		}
		p = CiAry[index].PhrasePtr[len-1]+tmpoff;  	
	}

	CiAry[index].PhraseNum[len-1]++;
	if (CiAry[index].PhraseNum[len-1] > MaxCiNum) 
		MaxCiNum = CiAry[index].PhraseNum[len-1];

	if (CiAry[index].PhraseNum[len-1] >=  MAXNUM(len) )
	{
		if (CiAry[index].PhraseNum[len-1] ==  MAXNUM(len) )
		{
			fprintf(stderr, "Only max %d phrase supported per char/length(Char=%c%c len=%d)\n", 
					MAX_PHRASE_NUM, c[0], c[1], /*tmpbuf[0], tmpbuf[1],*/ len);
		}
		return;
	}

	strncpy(p, c+2, (len-1)*2); /* save it */
	p += 2*(len-1);
	*((ZYFREQTYPE *)p) = (ZYFREQTYPE)freq;

	if (index < MinCiIndex) MinCiIndex = index;
	if (index > MaxCiIndex) MaxCiIndex = index;
	CiStat[len-1]++;
	TotalCi++;

}

static int LoadCharPhraseFromFile(FILE *fp)
{
	int i;
	unsigned char *p, *c; 
	int len, freq, yincount, index;
	unsigned short zhi, yin[MAX_PHRASE_LEN][MAX_ZHUYIN_NUMBER];
	
	YinFormat = ZHUYIN_FORMAT; /* default */

 	while ( !feof( fp )) 
  	{
    	if ( fgets(LineBuf,LINE_BUFFER_LEN,fp) == NULL) continue;

		p = FilterFormatCharFreq(LineBuf, /*ZIYINMAP_STR*/SYSPH_MAP_STR, &c, &len, &freq, 2);
		if (p == NULL || len <= 0) continue; 
		else if (p == (u_char *)-1) break; /* end */

		if (len == 1) /* single-char: add to ZiTable */
		{	
			if (!*p)
			{
				fprintf(stderr, "ERROR %c%c no freq or yin information! \n", c[0], c[1]);
				continue;
			}

			memset(yin, 0, sizeof(yin));
			yincount = convert_to_zhuyins(YinFormat, p, yin, len);

			if (yincount < 1)
				fprintf(stderr, "Single Char %c%c has no yin information!\n", c[0], c[1]);

			zhi = ((unsigned short)c[0]) << 8 | c[1];
			index = CalculateCharIndex(NewCoding, c[0], c[1]);
	
  			ZiTable[index].freq = freq;
	
			for(i = 0; yin[0][i]; i++)
			{
				AddOneYin(yin[0][i], ZiTable[index].yin, MAX_YIN_NUM);

				AddToYinZiMapTable(yin[0][i], zhi);
			}
		}
		else /* phrase: add to CiAry */
		{
			AddToCiAry(c, len, freq);	
		}
	}

	return 0;
}

static int SaveYinTable(FILE *fout, FILE *fmonitor)
{
	int i, j;
	int offset = 0, pad = 0;

	for(i = 0; i < TOTAL_ZHUYIN_NUM; i++)
	{
		yin_table[i].offset = offset;
		for(j = 0; j < MAX_TONE_NUM; j++)
		{
			yin_table[i].ZiNum[j] = NumZi[i][j];
			offset += NumZi[i][j];
		}
	}

	fwrite(yin_table, sizeof(yin_table), 1, fout);
	
	if (offset % 2) { offset++; pad = 1; }
	offset *= sizeof(unsigned short); /* conver to bytes */
	fwrite(	&offset, sizeof(int), 1, fout); /* how many bytes */

#ifdef DEBUG
	fprintf(stderr, "ZiLibSize=%d\n", offset);
#endif

	for(i = 0; i < TOTAL_ZHUYIN_NUM; i++)
	{
		for(j = 0; j < MAX_TONE_NUM; j++)
		{
			if (NumZi[i][j] == 0) continue;
			fwrite(	&YinZiMap[i][j], sizeof(unsigned short), NumZi[i][j], fout);
		
			if (fmonitor)
			{
				int k;
				for(k = 0; k < NumZi[i][j]; k++)
					fprintf(fmonitor, "%c%c", YinZiMap[i][j][k] >> 8, YinZiMap[i][j][k] & 0xFF);
				fprintf(fmonitor, "\n");
			}
		}
	}

	if (pad) /* pad two bytes */
		fwrite(&offset, sizeof(unsigned short), 1, fout);

	file_size += offset + sizeof(yin_table) + 4;  /* including the yin_table and ZiLib */

	return 0;
}

static int SaveZiCiTable(FILE *fout, FILE *fmonitor)
{
	int i, j;
	int offset = 0, pad = 0;

	fwrite(&zinum, sizeof(int), 1, fout); /* zi_num */
	for(i = 0; i < zinum; i++)
	{
		ZiTable[i].offset = offset;

		ZiTable[i].phnum.PhraseNum1 = CiAry[i].PhraseNum[1]; /* two bytes for 1 */
		offset += CiAry[i].PhraseNum[1] * (2 + sizeof(ZYFREQTYPE));

		for(j = 2; j < MAX_ZYPHRASE_LEN; j++)
		{
			ZiTable[i].phnum.PhraseNum[j] = CiAry[i].PhraseNum[j];
			offset += CiAry[i].PhraseNum[j] * (j * 2 + sizeof(ZYFREQTYPE));  
		}
		fwrite(&ZiTable[i], sizeof(ZiTable[0]), 1, fout);
	}

	pad = offset % 4 ? 4 - (offset % 4) : 0;
   	offset += pad;
	fwrite(	&offset, sizeof(int), 1, fout); /* how many bytes */

	for(i = 0; i < zinum; i++)
	{
		for(j = 0; j < MAX_ZYPHRASE_LEN; j++)
		{
			if (CiAry[i].PhraseNum[j] == 0 || !CiAry[i].PhrasePtr[j]) continue;
			fwrite(CiAry[i].PhrasePtr[j], (2*j + sizeof(ZYFREQTYPE))*CiAry[i].PhraseNum[j], 1, fout);
			if (fmonitor)
			{
				fprintf(fmonitor, "%s\n", CiAry[i].PhrasePtr[j]);
			}		
		}
			
	}
	
	if (pad)
		fwrite(&offset, 1, pad, fout);

	file_size += 8 + offset + sizeof(ZiTable[0]) * zinum;
	
#ifdef DEBUG
	fprintf(stderr, "ZiNum=%d CiLibSize=%d TotalCi=%d, MinCiIndex=%d MaxCiIndex=%d MaxCiNum=%d\n", 
		zinum, offset, TotalCi, MinCiIndex, MaxCiIndex, MaxCiNum);
	
	fprintf(stderr, "Ci Statistics:\n");
	CiStat[0] = zinum;
	for(i = 0; i <= MAX_ZYPHRASE_LEN; i++)
	{
		fprintf(stderr, "Ci Len=%d Num=%d\n", i+1, CiStat[i]);
	}

#endif

	return 0;
}

int ConvertZhuyinCIN(FILE *fr, hz_input_table *table, char *filename )
{
	FILE *fw; /* , *fp; */

	if ((fw=fopen(filename,"w"))==NULL) 
	{
		fprintf(stderr, "Failed to create output file!\n");
		return -1;
	}

    OldCoding = NewCoding = table->encoding;

	zinum = GetEncodingSpace(NewCoding);
	fprintf(stderr, "EncodingSpace=%d\n", zinum);

	ZiTable = (Zi *)malloc(zinum * sizeof(Zi));
	CiAry = (Ci *)malloc(zinum * sizeof(Ci));
	if (ZiTable == NULL || CiAry == NULL)
	{
		fprintf(stderr, "Out of memory allocating Zi/Ci array!\n");
		return -1;
	}

	memset(ZiTable, 0, zinum * sizeof(Zi));
	memset(CiAry, 0, zinum * sizeof(Ci));
	
	fwrite(table,sizeof(*table),1, fw);
	file_size += sizeof(*table);
	
	if ( LoadFileSection(cmd, arg, /*ZIYINMAP_STR*/SYSPH_MAP_STR, fr, LoadCharPhraseFromFile))
		print_error("%" /*ZIYINMAP_STR*/ SYSPH_MAP_STR " [ <FILE_NAME> | begin ] expected!");	

	do
    {
        cmd_arg(fr, LineBuf, &cmd, &arg);
    }
    while ( LoadFileSection(cmd, arg, SYSPH_MAP_STR, fr, LoadCharPhraseFromFile) == 0);
	fclose(fr);

	SaveYinTable(fw, NULL); /* save the yin->zi map, will update file_size */
	SaveZiCiTable(fw, NULL); /* stdout); // save zi->yin map, will update file_size */
	
	file_size += 4; /* itself */
	fwrite(&file_size, sizeof(int), 1, fw);

    fprintf(stderr, "%s:\tFileSize=%d ZiNum=%d TotalCiNum=%d MaxCiNum=%d\n",
        filename, file_size, zinum, TotalCi, MaxCiNum);

	fclose(fw);

	return 0;
}

