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

#include 	"defs.h"
#include 	"vc.h"
#include    "vga.h"
#include 	"hzinput.h"
#include 	"pinyin.h"
#include	"zhuyin.h"
#include	"term.h"
#include 	"font.h"
#include	"getcap.h"
#include	"errors.h"
#include 	"encfilter.h"

 int IsHanziInput;   /* 0 for ascii, 1 for hanzi input */
 int IsFullChar;     /* 0 for half char, 1 for full char */
 int IsChinesePunct; /* 0 for english punctuation, 1 for Chinese punct */

 int current_method = -1; /* no one yet */
 hz_input_table *input_table[NR_INPUTMETHOD],*cur_table = NULL;

 int IsAssocEnable = 1; 

 int IsAssociateMode;  /* currently it's in AssociateMode */

 int ZhiyinKeyboardMode;
 int ZhuyinKeyboardMode;

int ConfigInputMethod(const char *str)
{
    char *p, *lx, *lxend;
    int i = 0, defmethod;

	p = strtok((char *)str, "\n");
    while(p && i < NR_INPUTMETHOD) 
	{
     	p = skip_space(p);	
		defmethod = 0;
		if (*p && *p != '#')  /* comment line doesn't occupy one input slot */
		{
            if (strncasecmp(p, EMPTY_SLOT_STRING, sizeof(EMPTY_SLOT_STRING)-1)) /* not beginning with "EMPTY" */
			{
				if (*p == '*') /* indicate it's the default input method */
				{
					p++;
					defmethod = 1;
				}

				lx = to_space(p);
				if (*lx && *lx != '#') 
					*lx++ = '\0'; /* terminate the input method filename and advance lx */
				else lx = NULL;
			
       			input_table[i] = LoadInputMethod(p);
       			message("Loading input method %d %s: %s",i,p, input_table[i] ? "Succeeded.\n" : "Failed!\n");
			
				if (input_table[i] && (defmethod || current_method == -1)) current_method = i; 

				if (input_table[i] && lx)
				{
					lx = skip_space(lx);
					lxend = to_space(lx); /* go to next space */
					*lxend = '\0'; /* terminated it */
					if (*lx && *lx != '#') LoadAssociateFile(input_table[i], lx);
				}
			}
   			i++;  /* even if we can't load it, it'll occupy one slot */
		}	
        p = strtok((char *)NULL, "\n");
    }

	if (current_method >= 0) cur_table = input_table[current_method];
	return SUCCESS;
}

static int  ConfigFuzzyPinyin(const char *confstr)
{
    bool value = BoolConf(confstr);
    EnableFuzzyPinyin = value;

    if (value) 
	{
        message("zh/z-ch/c-sh/s fuzzy pinyin enabled.\r\n");
    }
    return SUCCESS;
}

static int ConfigZhiyinKeyboard(const char *confstr)
{
	ZhiyinKeyboardMode = atoi(confstr);

	if ( ZhiyinKeyboardMode < 0) ZhiyinKeyboardMode = 0;

    return SUCCESS;
}

static int ConfigZhuyinKeyboard(const char *confstr)
{
    ZhuyinKeyboardMode = atoi(confstr);

	if ( ZhuyinKeyboardMode < 0) ZhuyinKeyboardMode = 0;

    return SUCCESS;
}

static int ConfigShuangPinMap(const char *confstr)
{	
	if (confstr)
	{
		u_char *p = strtok((char *)confstr, " #\t\r\n");
		LoadShuangPinData(p);  /* it's optional, so failure doesn't hurt */
	}

	return SUCCESS;
}

void InputInit(void)
{
	IsHanziInput = 0;	/* default is ascii input  */
	IsFullChar = 0;
  
    DefineCap("FuzzyPinyin",ConfigFuzzyPinyin,"Off");
    DefineCap("ZhiyinKeyboard", ConfigZhiyinKeyboard, "0");
  	DefineCap("CiyinKeyboard", ConfigZhuyinKeyboard, "0");
	DefineCap("ShuangPinMap", ConfigShuangPinMap, "");
}

void InputCleanup(void)
{
	int i;

	IsHanziInput = 0; 
	ResetInput(0,0); /* do we need this one? */
	
	for (i = 0; i < NR_INPUTMETHOD; i++)
 	{
    	if (input_table[i])
      		UnloadInputMethod(i);
	}

#ifdef CONVTABLE_USE_MMAP
	MunmapConvertTable(); /* clean up the conver table */
#endif
}

void InputSwitchEncoding(int encoding)
{
	int i;

	if (encoding <= 0 || encoding >= CODE_NUM) return; /* invalid entry */

	IsHanziInput = 0; 
	ResetInput(0,0); 
	
	for (i = 0; i < NR_INPUTMETHOD; i++)
 	{
    	if (!input_table[i]) continue;
		
		switch(input_table[i]->InputType)
		{
		case INPUT_TYPE_INTCODE: /* always use lInfo.sysCoding */
			input_table[i]->encoding = encoding;
			strcpy(input_table[i]->cname, IntCode_CNameStr[encoding]);
			break;
		case INPUT_TYPE_TABLE:  /* use its own encoding ? */
			if (input_table[i]->encoding == CODE_GB && encoding == CODE_GBK)
				input_table[i]->encoding = CODE_GBK;
		 	break;
		case INPUT_TYPE_PINYIN:
		case INPUT_TYPE_ZHUYIN:  /* always use lInfo.sysCoding */
			input_table[i]->encoding = encoding;
			cur_table = input_table[i];  /* necessary for ChangeKeyboardMap */
			strcpy(cur_table->cname, cur_table->InputType == INPUT_TYPE_PINYIN ? 
					Pinyin_CNameStr[encoding] : Zhuyin_CNameStr[encoding]);
			cur_table->ChangeKeyboardMap(0); /* same one if possible */
			break;
		}
	}
}

void ToggleHalfFull(void)
{
	IsFullChar ^= 1;
	RefreshInputArea(FALSE); /* do not need to clear it */
}

void ToggleAssociateMode()
{
	IsAssocEnable ^= 1;
	if (IsAssociateMode)  /* IsAssocEnable should be 1 now */
	{
		ResetInput(1,1);
	}
}

void ToggleChinesePunct()
{
	int i;

	IsChinesePunct ^= 1;
	RefreshInputArea(FALSE); /* do not need to clear it */

	if (IsChinesePunct)
	{
		for(i = 0; Punct3Str[i].ch[0]; i++)
			Punct3Str[i].count = 0; /* output outmost one */
		for(i = 0; Punct2Str[i].ch; i++)
			Punct2Str[i].index = 0; /* output first one */
	}
}

void ToggleInputMethod(void)
{
	if (!IsHanziInput)  /* not HZinput now */
	{
		IsChinesePunct = 1; /* force Chinese ,. */
		SetCurrentInputMethod(current_method);
	}
	else
	{
   		IsHanziInput = 0; 
		IsChinesePunct = 0; /* force English ,. */
		ResetInput(0,0);
		RefreshInputArea(TRUE);
	}
}

int SetCurrentInputMethod(int curMethod)
{
	if (curMethod >= 0 && input_table[curMethod])
	{
		if (!input_table[curMethod]->FontMapped)
		{
			if (MmapFont(input_table[curMethod]->encoding, NULL)) return -1; /* failed to map font */
			input_table[curMethod]->FontMapped = 1;
		}
		
		if (input_table[curMethod]->item == NULL && MmapInputTable(input_table[curMethod]))
			return -1; /* failed to map it */
		
    	if (current_method != curMethod || !IsHanziInput) 
		{
			ResetInput(0,0);
			switch(input_table[curMethod]->InputType)
			{
			case INPUT_TYPE_PINYIN:
				ResetPinyinInput((PinyinModule *)input_table[curMethod]->PrivateData);
				break;
			case INPUT_TYPE_ZHUYIN:
				ResetZhuyinInput((ZhuyinModule *)input_table[curMethod]->PrivateData);
				break;
			}
		}

    	current_method = curMethod;
    	cur_table = input_table[curMethod];

		IsHanziInput = 1;
   		RefreshInputArea(TRUE);
		return 0;
  	}
	return -1;
}

void SwitchInputMethod(void)
{
	int method = current_method;
	
	if (!IsHanziInput) /* just turn on */
	{
		SetCurrentInputMethod(current_method);
	}
	else  /* swith to next one  */
    {
		if (method < 0) method = -1; /* start from 0 */
		do 
		{
			method = (method+1) % 10;  /*[0-8]9 -> [1-9]0 */
		}
   		while ( (!input_table[method] || SetCurrentInputMethod(method)) && method != current_method) ;
	}
}

void ChangePinyinZhuyinKeyboardMap(void)
{
	if (IsHanziInput && cur_table && cur_table->ChangeKeyboardMap)
	{
		ResetInput(0, 0); /* clear associate candidates if it's available */
		cur_table->ChangeKeyboardMap(1);
	}
}

void InputAreaOutput(int encoding, int x, u_char *string, int fg, int bg)
{

	while (*string)
	{
		if (iskanji( encoding, string[0], string[1])) /* depends on lInfo.sysCoding	  */
     	{
        	pVideoInfo->input_wput(encoding, x,*string,*(string+1),fg,bg);
         	x += 2;
         	string += 2;
     	}
     	else pVideoInfo->input_sput(x++,*string++,fg,bg);
  	}
}

static void GetCurrentTime(char *buffer, int len)
{
    static char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
	static char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
	time_t curTime;
	struct tm *pTM;

	if (dispInfo.gxdim < 640) /* 800) */
	{
		snprintf(buffer, len, "CCE Ver " VERSION);
	}
	else
	{
		time(&curTime);
		pTM = localtime(&curTime);
#if 0  /* including seconds */
		sprintf(buffer, "CCE Ver " VERSION "    %s %s %d %02d:%02d:%02d", 
			day[pTM->tm_wday], month[pTM->tm_mon], pTM->tm_mday, pTM->tm_hour, pTM->tm_min, pTM->tm_sec);	
#else
    	snprintf(buffer, len, "CCE Ver " VERSION "   %s %s %d %02d:%02d" /* :%02d" */,
        	day[pTM->tm_wday], month[pTM->tm_mon], pTM->tm_mday, pTM->tm_hour, pTM->tm_min /*, pTM->tm_sec*/ );
#endif
	}
}

void RefreshInputArea(int ClearInput)
{
	char buf[256], timebuf[55];

	if (ClearInput)
    	pVideoInfo->clear(CLEAR_INPUT, INPUT_BGCOLOR);  /* 1 blue */

  	if (!IsHanziInput)
  	{

		GetCurrentTime(timebuf, sizeof(timebuf));

#if defined(__QNX__)
		snprintf(buf, sizeof(buf), "[ENGNUM%s]%s",
			"     ", CurCoding[lInfo.sysCoding]); /* 5 spaces */
#else
		snprintf(buf, sizeof(buf), EngNumStr[lInfo.sysCoding], 
			"     ", CurCoding[lInfo.sysCoding]); /* 5 spaces */
#endif

    	InputAreaOutput(lInfo.sysCoding, 0, buf, INPUT_FGCOLOR,INPUT_BGCOLOR);

		pVideoInfo->input_draw_icon(7, HalfFullIcons[IsFullChar], 16, 16, INPUT_FGCOLOR, INPUT_BGCOLOR); /* half/full width */
		pVideoInfo->input_draw_icon(9, ChEngPunctIcons[IsChinesePunct], 16, 16, INPUT_FGCOLOR, INPUT_BGCOLOR); /* chinese/eng punct */
	
		InputAreaOutput(lInfo.sysCoding, (dispInfo.tx_avail-strlen(timebuf))/2+1, timebuf, INPUT_FGCOLOR, INPUT_BGCOLOR);

#if defined(__QNX__)
		snprintf(buf, sizeof(buf), "[Term %d][Window %d]",
			VtNum, curCon ? curCon->window : 0);
#else
		snprintf(buf, sizeof(buf), WindowStr[lInfo.sysCoding],
			VtNum, curCon ? curCon->window : 0);
#endif

    	InputAreaOutput(lInfo.sysCoding, dispInfo.tx_avail-WINDOW_TERMINAL_STR_WIDTH, buf, INPUT_FGCOLOR, INPUT_BGCOLOR);
  	}
  	else
  	{
		int len;

		len = strlen(cur_table->cname);
#if defined(__QNX__)
		snprintf(buf, sizeof(buf), " [%s%s] ", 
			cur_table->cname, dispInfo.show_terminal ? "     " : "" );
#else
		snprintf(buf, sizeof(buf), InpNameStr[cur_table->encoding], 
			cur_table->cname, dispInfo.show_terminal ? "     " : "" );
#endif

    	InputAreaOutput(cur_table->encoding, 0, buf, INPUT_FGCOLOR,INPUT_BGCOLOR); 

		if (dispInfo.show_terminal)
		{
			pVideoInfo->input_draw_icon(len+3, HalfFullIcons[IsFullChar], 16, 16, INPUT_FGCOLOR, INPUT_BGCOLOR); /* half/full width */
			pVideoInfo->input_draw_icon(len+5, ChEngPunctIcons[IsChinesePunct], 16, 16, INPUT_FGCOLOR, INPUT_BGCOLOR); /* chinese/eng punct */
		}

#if 1 /* def SUPPORT_ASSOCIATE */
    	if (IsAssociateMode)
    	{
        	DisplayAssociateInput();
			DisplaySelection(); /* Associate Input is using the table input's engine */
    	}
		else
#endif
		
		if (cur_table->RefreshInput) (*cur_table->RefreshInput)();
		else
		{
        	DisplayInput(); 
			DisplaySelection();
		}
		
		if (dispInfo.show_terminal)
		{
#if defined(__QNX__)
			snprintf(buf, sizeof(buf), "[Term %d][Window %d]",
				VtNum, curCon ? curCon->window : 0);
#else
        	snprintf(buf, sizeof(buf), WindowStr[cur_table->encoding],VtNum, 
					curCon ? curCon->window :0);
#endif
        	InputAreaOutput(cur_table->encoding, dispInfo.tx_avail-WINDOW_TERMINAL_STR_WIDTH, buf, INPUT_FGCOLOR, INPUT_BGCOLOR);
		}
	}
}

void ClearSelectionArea(void)
{
    int i, pos = cur_table->SelAreaX;
    
    for(i = pos; i < pos + cur_table->SelAreaWidth ; i++)
		pVideoInfo->input_sput(i,' ',INPUT_BGCOLOR,INPUT_BGCOLOR);
}

void ClearInputArea(void)
{
    int i, pos = cur_table->InputAreaX;
    
    for(i = pos; i < pos + cur_table->InputAreaWidth ; i++)
		pVideoInfo->input_sput(i,' ',INPUT_BGCOLOR,INPUT_BGCOLOR);
}

void OutputCJKStr(int tty_fd, unsigned char *buf, int count)
{
#ifdef SUPPORT_ENCODING_FILTER
	if (UseEncodingFilter)
    {
     	DoEncodingFilter(CONVERT_TO_FILTER, buf, count);  /* ignore the unconvertible bytes */
		if (EncodingFilterLen > 0) write(tty_fd, EncodingFilterBuff, EncodingFilterLen);
    }
    else
#endif
		write(tty_fd, buf, count);
}

void OutputEngChar(int tty_fd, unsigned char c)
{
	int key;
	char *p = NULL;
	int i, index;
	
#ifdef DEBUG
	fprintf(stderr, "Now in hzinput.c's OutputEngChar, tty_fd=%d, c=0x%x IsFullChar=%d\n", tty_fd, c, IsFullChar);
#endif

	if (IsChinesePunct)
	{
		for(i = 0; Punct3Str[i].ch[0]; i++)
		{
			if (c == Punct3Str[i].ch[0]) index = 0;         /* '<' =>  << < */
			else if (c == Punct3Str[i].ch[1]) index = 1;    /* '>' =>  >> > */
			else continue;

			p = Punct3Str[i].punct[index][Punct3Str[i].count > 0 ][lInfo.sysCoding];
			if (p) /* not NULL */
			{
				OutputCJKStr(tty_fd, p, strlen(p));
				if (index == 1 && Punct3Str[i].count > 0) Punct3Str[i].count--;
				else if (index == 0 && Punct3Str[i].count < 255) Punct3Str[i].count++; 
				return;
			}
		}

		for(i = 0; Punct2Str[i].ch; i++)
			if (c == Punct2Str[i].ch)
			{
				p = Punct2Str[i].punct[ Punct2Str[i].index ][ lInfo.sysCoding ]; 
				if (p) /* not NULL */
				{
					OutputCJKStr(tty_fd, p, strlen(p));
					Punct2Str[i].index ^= 1;  /* alternate */
					return;
				}
			}

		for(i = 0; Punct1Str[i].ch; i++)
			if (c == Punct1Str[i].ch)
			{
				p = Punct1Str[i].punct[ lInfo.sysCoding ];
				if (p)
				{
					OutputCJKStr(tty_fd, p, strlen(p));
					return;
				}
			}
	}
	
	if (IsFullChar && FullCharMap[lInfo.sysCoding]  /* Mapping not NULL */
			&& c >= ' ' && c < 0x7F) /* 0x7F is DEL, not printable ASCII */
  	{
       	key = (c - ' ') << 1;
       	OutputCJKStr(tty_fd, FullCharMap[lInfo.sysCoding]+key, 2);
  	}
  	else write(tty_fd,&c,1);
}

void HZFilter(int tty_fd, unsigned char key)
{
	int handled = 0;

	if (IsHanziInput)
	{
		if (IsAssociateMode)
			handled = Associate_HZFilter(tty_fd, key);
	
		if (!handled) handled =	cur_table->HZFilter(tty_fd, key); /* will call the correct filter function */
	}

	if (!handled) OutputEngChar(tty_fd, key);
}

