/* $Id: CIM.c 315 2003-05-06 18:45:02Z clkao $
 *
 * ChewingOSX: Chewing Chinese Input Method for Mac OS X
 *
 * Copyright (C) 2003 by Chia-Liang Kao
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA, or
 * contact the author.
 */

#ifdef	WIN32
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "freebsdsize.h"
#include "SunIM.h"
#include "SunIMMMan.h"
#include "SunIMProt.h"
#include "SunIMMthd.h"
#include "SunIMPriv.h"
#include "SunIMCore.h"
#include "SunIMPub.h"

#include "chewingio.h"
#include "hash.h"
#include "console_chewing.h"
#include "dict.h"
#include "zuin.h"

#include <iconv.h>

#define CHEWING_VERSION "0.01"

/* statistics symbol required by chewing */
int lifetime;

typedef struct chewing_session {
    long		fLastUpdateLength;
    ChewingData		chewing_data;
    ChewingOutput	chewing_output;

    int preedit_start;
    int caret_pos;
    int luc_start;
    int conv_on;
    iconv_t iconv_context;
    int status_start;
    int candidate;
} chewing_session_t;

IMFeedbackList *
create_feedback (iml_session_t *s, int size)
{
    int i;
    IMFeedbackList *feedback, *fbl;
    IMFeedback *fb;
    
    if (s) {
        feedback = (IMFeedbackList *) s->If->m->iml_new(s, sizeof(IMFeedbackList) * size);
        memset(feedback, 0, sizeof(IMFeedbackList) * size);
    } else {
        feedback = (IMFeedbackList *) calloc(1, sizeof(IMFeedbackList) * size);
    }
    for (i = 0; i < size; i++) {
        IMFeedbackList *fbl = &feedback[i];
        fbl->count_feedbacks = 1;
        if (s) {
            fb = fbl->feedbacks = (IMFeedback *) s->If->m->iml_new(s, sizeof(IMFeedback));
            memset(fbl->feedbacks, 0, sizeof(IMFeedback));
        } else {
            fb = fbl->feedbacks = (IMFeedback *) calloc(1, sizeof(IMFeedback));
        }
	IM_FEEDBACK_VALUE(fb)=IMUnderline;

    }
    return feedback;
}

UTFCHAR chewing_string[] = {0x9177, 0x97f3, 0x0};

void
status_draw (iml_session_t *s)
{
    int len, i;
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    memset(p, 0, sizeof(IMText));
    p->encoding = UTF16_CODESET;

    if (!session->conv_on) {
        lp = s->If->m->iml_make_status_done_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
    }
    else {
    
    len = 3;
    p->text.utf_chars = (UTFCHAR *) s->If->m->iml_new(s, sizeof(UTFCHAR) * (len + 1));

    memcpy (p->text.utf_chars, chewing_string, len * sizeof(UTFCHAR));

    p->text.utf_chars[len] = 0;
    p->char_length = len;
    p->feedback = create_feedback(s, p->char_length);

    if (session->status_start == False) {
        lp = s->If->m->iml_make_status_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        session->status_start = True;
    }
    lp = s->If->m->iml_make_status_draw_inst(s, p);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    }
    
    s->If->m->iml_execute(s, &rrv);
}

void
commit (iml_session_t *s)
{
    int len, i, buflen;
    char buf[128], *bufp = buf, *bufutfp;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p;
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    ChewingOutput *op = &session->chewing_output;

    if (!op->nCommitStr)
	return;
    p = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    memset(p, 0, sizeof(IMText));
    p->encoding = UTF16_CODESET;

    memset (buf, 0, 128);
    for (i=0; i<op->nCommitStr; ++i) {
	strcat (buf, op->commitStr[i].s);
    }

    len = strlen (buf) + 0;
    buflen = (op->nCommitStr + 1) * sizeof(UTFCHAR);

    p->text.utf_chars = (UTFCHAR *) s->If->m->iml_new(s, buflen);
    p->char_length = op->nCommitStr;

    bufutfp = (unsigned char *)p->text.utf_chars;
    buflen = iconv (session->iconv_context, (const char **)&bufp, &len, &bufutfp, &buflen);

    lp = s->If->m->iml_make_preedit_erase_inst(s);
    s->If->m->iml_link_inst_tail(&rrv, lp);

    lp = s->If->m->iml_make_commit_inst(s, p);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    s->If->m->iml_execute(s, &rrv);
    
    session->caret_pos = -1;
    op->keystrokeRtn = KEYSTROKE_IGNORE;
    op->nCommitStr = 0;
}

static int currentKeyboard = KB_DEFAULT;

IMText *
make_preedit_imtext (iml_session_t *s)
{
    int len, i, zuinlen = 0, buflen;
    char buf[128], *bufp = buf, *bufutfp;
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    ChewingOutput *op = &session->chewing_output;
    IMText *p = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    memset(p, 0, sizeof(IMText));
    memset (buf, 0, 128);

    for(i=0;i<op->chiSymbolCursor;++i)
	strcat (buf, op->chiSymbolBuf[i].s);

    len = strlen (buf);

    for(i=0;i<ZUIN_SIZE;++i) {
	strcat (buf, op->zuinBuf[i].s);
    }

    zuinlen = (strlen (buf) - len) / 2;
    for(i=op->chiSymbolCursor; i < op->chiSymbolBufLen; ++i) 
	strcat (buf, op->chiSymbolBuf[i].s);

    len = strlen (buf);

    p->encoding = UTF16_CODESET;

    p->char_length = op->chiSymbolBufLen + zuinlen;
    buflen = (p->char_length + 1) * sizeof(UTFCHAR);
    p->text.utf_chars = (UTFCHAR *) s->If->m->iml_new(s, buflen);

    bufutfp = (char *)p->text.utf_chars;
    buflen = iconv (session->iconv_context, (const char **)&bufp, &len, &bufutfp, &buflen);

    printf("drawing preedit (buflen = %d, len = %d)\n", buflen, len);
    session->caret_pos = op->chiSymbolCursor;

    p->feedback = create_feedback(s, p->char_length);
    /*
    for(i = 0; i < session->caret_pos; i ++) {
	set_feedback(&session->preedit_feedback[i], IMReverse);
    }
    for(i = session->caret_pos; i < p->char_length; i ++) {
	set_feedback(&session->preedit_feedback[i], IMUnderline);
    }
    for (i = 0; i < p->char_length; i++) {
        set_feedback(&p->feedback[i], get_feedback(&session->preedit_feedback[i]));
    }
    */
    return p;
}

void
preedit_draw(iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    IMText *p = make_preedit_imtext(s);
    
    if (session->preedit_start == False) {
        lp = s->If->m->iml_make_preedit_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        session->preedit_start = True;
    }
    if(p->char_length == 0) {
	lp = s->If->m->iml_make_preedit_erase_inst(s);
	s->If->m->iml_link_inst_tail(&rrv, lp);
	s->If->m->iml_execute(s, &rrv);
	return;
    }
    lp = s->If->m->iml_make_preedit_draw_inst(s, p);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    
    if(session->caret_pos != -1) {
	lp = s->If->m->iml_make_preedit_caret_inst(s, session->caret_pos); 
	s->If->m->iml_link_inst_tail(&rrv, lp);
    }
    s->If->m->iml_execute(s, &rrv);
}

void
draw_candidate (iml_session_t *s)
{
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    ChewingOutput *op = &session->chewing_output;
    ChewingData *pgdata = &session->chewing_data;
    IMLookupDrawCallbackStruct *draw;
    ChoiceInfo *pci = op->pci;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    int i, no = pci->pageNo * pci->nChoicePerPage;

    if (!session->candidate) {
	IMLookupStartCallbackStruct *start = (IMLookupStartCallbackStruct *) s->If->m->iml_new(s, sizeof(IMLookupStartCallbackStruct));
        memset(start, 0, sizeof(IMLookupStartCallbackStruct));
        start->whoIsMaster = IMIsMaster;
        start->IMPreference = (LayoutInfo *) s->If->m->iml_new(s, sizeof(LayoutInfo));
        memset(start->IMPreference, 0, sizeof(LayoutInfo));
        start->IMPreference->choice_per_window = pci->nChoicePerPage;
        start->IMPreference->ncolumns = pci->nChoicePerPage;
        start->IMPreference->nrows = 1;
        start->IMPreference->drawUpDirection = DrawUpHorizontally;
        start->IMPreference->whoOwnsLabel = IMOwnsLabel;
        start->CBPreference = NULL;
        lp = s->If->m->iml_make_lookup_start_inst(s, start);
	s->If->m->iml_link_inst_tail(&rrv, lp);
	session->candidate = 1;
    }

    for(i=0;i<pci->nChoicePerPage; no++,i++) {
	if( no >= pci->nTotalChoice )
	    break;
    }

    draw = (IMLookupDrawCallbackStruct *) s->If->m->iml_new(s, sizeof(IMLookupDrawCallbackStruct));
    memset(draw, 0, sizeof(IMLookupDrawCallbackStruct));
    draw->index_of_current_candidate = draw->index_of_first_candidate = pci->pageNo * pci->nChoicePerPage;
    draw->index_of_last_candidate = no;
    draw->n_choices = i;
    
    /* XXX: make this shit a macro of a function */
    draw->title = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    memset(draw->title, 0, sizeof(IMText));
    draw->title->encoding = UTF16_CODESET;
    draw->title->char_length = 2;
    draw->title->text.utf_chars = (UTFCHAR *) s->If->m->iml_new(s,
        sizeof(IMText) * (draw->title->char_length + 1));
    memcpy (draw->title->text.utf_chars, chewing_string, (draw->title->char_length + 1) * sizeof(UTFCHAR));

    draw->title->feedback = create_feedback(s, draw->title->char_length);
    
    draw->choices = (IMChoiceObject *) s->If->m->iml_new(s, draw->n_choices * sizeof(IMChoiceObject));
    
    for (i = draw->max_len = 0; i < draw->n_choices; i++) {
        IMText *vt = (IMText *)s->If->m->iml_new(s, sizeof(IMText));
        IMText *lt = (IMText *)s->If->m->iml_new(s, sizeof(IMText));
	char *bufp = pci->totalChoiceStr[i+draw->index_of_first_candidate], *bufutfp;
	int buflen, len;
        draw->choices[i].value = vt;
	vt->encoding = UTF16_CODESET;

	len = strlen (bufp);
	vt->char_length = len / 2;
	buflen = (vt->char_length + 1) * sizeof(UTFCHAR);
	vt->text.utf_chars = (UTFCHAR *)s->If->m->iml_new(s, buflen);
	bufutfp = (char *)vt->text.utf_chars;
	buflen = iconv (session->iconv_context, (const char **)&bufp, &len, &bufutfp, &buflen);
	vt->feedback = create_feedback(0, vt->char_length);

	if (len > draw->max_len)
	    draw->max_len = len;

        draw->choices[i].label = lt;
	lt->encoding = UTF16_CODESET;
	lt->char_length = 1;
	lt->text.utf_chars = (UTFCHAR *)s->If->m->iml_new(s, sizeof(UTFCHAR)*2);
	lt->text.utf_chars[0] = (UTFCHAR) i+'1';
	lt->feedback = create_feedback(0, lt->char_length);
    }
    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    s->If->m->iml_execute(s, &rrv);

    status_draw(s);
}

void
close_candidate (iml_session_t *s)
{
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    iml_inst *lp;

    lp = s->If->m->iml_make_lookup_done_inst(s);
    s->If->m->iml_execute(s, &lp);
    session->candidate = 0;
}

void
handle_candidate (iml_session_t *s)
{
    chewing_session_t *session = (chewing_session_t *)s->specific_data;

    if (session->chewing_output.pci->nPage)
	draw_candidate(s);
    else {
	if (session->candidate) {
	    close_candidate (s);
	}
    }
}

void
chewing_conversion_on (iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    chewing_session_t *session_data = (chewing_session_t *) s->specific_data;
        session_data->conv_on = True;
    
    lp = s->If->m->iml_make_start_conversion_inst(s);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    
    if (session_data->preedit_start == False) {
        lp = s->If->m->iml_make_preedit_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        session_data->preedit_start = True;
    }
    lp = s->If->m->iml_execute(s, &rrv);
    status_draw(s);
}

void
chewing_conversion_off (iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    chewing_session_t *session = (chewing_session_t *) s->specific_data;
    ChewingData *pgdata = &session->chewing_data;
    ChewingOutput *op = &session->chewing_output;

    session->conv_on = False;

    /* if preedit exists, commit the string */
    OnKeyEnter(pgdata, op);
    commit(s);
    
    if (session->luc_start == True) {
        lp = s->If->m->iml_make_lookup_done_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        session->luc_start = False;
    }
    if (session->preedit_start == True) {
        lp = s->If->m->iml_make_preedit_erase_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        lp = s->If->m->iml_make_preedit_done_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        session->preedit_start = False;
        session->caret_pos = -1;
    }
    lp = s->If->m->iml_make_end_conversion_inst(s);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    
    s->If->m->iml_execute(s, &rrv);
    close_candidate (s);
    
    status_draw(s);
}

/* IF method implementation */

Bool
if_chewing_OpenIF (iml_if_t *If)
{
    /* fix the data installation path and put it here */
    char *prefix = LIBCHEWING_DATADIR;
    char home[128];

    ReadTree(prefix);
    InitChar(prefix); 
    InitDict(prefix) ; 
    strcpy (home, getenv ("HOME"));
    strcat (home, "/.chewing");
    ReadHash(home);

    
    return True;
}

Bool
if_chewing_CloseIF (iml_if_t *If)
{
    return True;
}

Bool
if_chewing_GetIFValues (iml_if_t *If, IMArgList args, int num_args)
{
    return True;
}

Bool
if_chewing_SetIFValues (iml_if_t *If, IMArgList args, int num_args)
{
    return True;
}

Bool
if_chewing_OpenDesktop (iml_desktop_t *desktop, IMArgList args, int num_args)
{
    return True;
}

Bool
if_chewing_CloseDesktop(iml_desktop_t *desktop)
{
    return True;
}

Bool
if_chewing_CreateSC (iml_session_t *s, IMArgList args, int num_args)
{
    int i;
    iml_desktop_t *desktop = s->desktop;
    chewing_session_t *session = calloc (sizeof (chewing_session_t), 1);
    ChewingConf		cf ;
    ConfigData		config;

    /* init chewing core */
    cf.kb_type = KB_DEFAULT;
    InitChewing(&session->chewing_data, &cf) ;

    config.selectAreaLen = 40;
    config.maxChiSymbolLen = 16;
    for(i=0; i<9; i++)
	config.selKey[i] = i + '1';
    config.selKey[9] = '0';
    SetConfig(&session->chewing_data, &config);

    session->iconv_context = iconv_open ("UTF-16LE", "BIG5");
    session->preedit_start = False;

    s->specific_data = (void*) session;

    printf("created session data %p\n", session);
    
    return True;
}

Bool
if_chewing_DestroySC (iml_session_t *s)
{
    return True;
}

Bool
if_chewing_GetSCValues(iml_session_t *s, IMArgList args, int num_args)
{
    int i;
    IMArg *p = args;
    
    for (i = 0; i < num_args; i++, p++) {
        switch (p->id) {
            default:
                break;
            }
    }
    return True;
}

Bool
if_chewing_SetSCValues (iml_session_t *s, IMArgList args, int num_args)
{
    int i;
    IMArg *p = args;
    for (i = 0; i < num_args; i++, p++) {
        switch (p->id) {
            case SC_TRIGGER_ON_NOTIFY:
                chewing_conversion_on(s);
                break;
            case SC_TRIGGER_OFF_NOTIFY:
                chewing_conversion_off(s);
                break;
            case SC_REALIZE:
		if(s->desktop->session_count == 1){
			printf("	SC_REALIZE: just after OpenDesktop\n");
		} else {
			printf("	SC_REALIZE: received\n");
		}
                break;
            default:
                break;
            }
    }
    return True;
}

IMText *
if_chewing_ResetSC (iml_session_t *s)
{
    return (IMText *) NULL;
}

void
if_chewing_SetSCFocus (iml_session_t *s)
{
    status_draw(s);
}

void
if_chewing_UnsetSCFocus (iml_session_t *s)
{
}

int reentrace;

void
if_chewing_SendEvent (iml_session_t *s, IMInputEvent *ev)
{
    chewing_session_t *session = (chewing_session_t *)s->specific_data;
    ChewingOutput *op = &session->chewing_output;
    ChewingData *pgdata = &session->chewing_data;
    IMKeyListEvent *keylistevent;
    IMKeyEventStruct *key;

    if (reentrace) {
	printf("reentrance in handleinput!\n");
	return;
    }
    reentrace = 1;

    if (ev->type == IM_EventKeyList) {
        keylistevent = (IMKeyListEvent *) ev;
        key = (IMKeyEventStruct *) keylistevent->keylist;
    }
    else {
	reentrace = 0;
	return;
    }
    if (key->keyCode == IM_VK_CAPS_LOCK) {
	if (pgdata->bChiSym == CHINESE_MODE)
	    OnKeyCapslock(pgdata, op);
    }
    else if (pgdata->bChiSym != CHINESE_MODE)
	OnKeyCapslock(pgdata, op);

    pgdata->zuinData.kbtype = currentKeyboard;
    switch (key->keyCode) {
	case IM_VK_ENTER:
	    OnKeyEnter(pgdata, op);
	    break;

	case IM_VK_ESCAPE:
	    OnKeyEsc( pgdata, op) ;
	    break;

	case IM_VK_BACK_SPACE:
	    OnKeyBackspace(pgdata, op);
	    break;

	case IM_VK_LEFT:
	    OnKeyLeft(pgdata, op);
	    break;

	case IM_VK_RIGHT:
	    OnKeyRight(pgdata, op);
	    break;

	case IM_VK_UP:
	    OnKeyUp(pgdata, op);
	    break ;

	case IM_VK_DOWN:
	    OnKeyDown(pgdata, op);
	    break;

	case IM_VK_HOME:
	    OnKeyHome(pgdata, op);
	    break;

	case IM_VK_END:
	    OnKeyEnd(pgdata, op);
	    break;

	case IM_VK_PAGE_UP:
	case IM_VK_PAGE_DOWN:
	    break;

	case KEY_TAB:
	    OnKeyTab(pgdata, op);
	    break;

	default:
	    /*
	    if (key->modifier & (controlKey|rightControlKey) && (inCharCode > '0' && inCharCode < '9'))
		OnKeyCtrlNum(pgdata, inCharCode, op);

	    else if (inCharCode) {
		if (modifiers & alphaLock && !(modifiers & shiftKey))
		    inCharCode |= 32;
	    */
	    if (key->keyChar)
		OnKeyDefault(pgdata, key->keyChar, op);
	    break;
    }

    /* handle candidate window here */

    handle_candidate (s);

    /* is there any way to bell on the client? */
    /*
    if (op->keystrokeRtn & KEYSTROKE_BELL)
	SysBeep();
    */

    if (op->keystrokeRtn & KEYSTROKE_IGNORE) {
	iml_inst *lp;
	lp = s->If->m->iml_make_keypress_inst(s, key);
	s->If->m->iml_execute(s, &lp);
	reentrace = 0;
	return;
    }

    if (op->keystrokeRtn & KEYSTROKE_COMMIT)
	commit (s);
    preedit_draw (s);
    op->keystrokeRtn = KEYSTROKE_IGNORE;

    reentrace = 0;
    
    return;
}

/* IF Method */
static if_methods_t chewing_methods = {
    if_chewing_OpenIF,
    if_chewing_CloseIF,
    if_chewing_GetIFValues,
    if_chewing_SetIFValues,
    if_chewing_OpenDesktop,
    if_chewing_CloseDesktop,
    if_chewing_CreateSC,
    if_chewing_DestroySC,
    if_chewing_GetSCValues,
    if_chewing_SetSCValues,
    if_chewing_ResetSC,
    if_chewing_SetSCFocus,
    if_chewing_UnsetSCFocus,
    if_chewing_SendEvent
};

/* 酷音輸入法 */
UTFCHAR lename_string[] = {0x9177, 0x97f3, 0x8f38, 0x5165, 0x6cd5, 0x0};
static IMLEName lename = {
    "chewing", (UTFCHAR *) lename_string
};

/* 繁體中文 */
UTFCHAR locale_string[] = {0x7e41, 0x9ad4, 0x4e2d, 0x6587, 0x0};
static IMLocale locales[] = {
    {"zh_TW", (UTFCHAR *) locale_string },
    NULL
};

void 
if_GetIfInfo (IMArgList args, int num_args)
{
    int i;

    for (i = 0; i < num_args; i++, args++) {
        switch (args->id) {
            case IF_VERSION:
                args->value = (IMArgVal) CHEWING_VERSION;
                break;
            case IF_METHOD_TABLE:
                args->value = (IMArgVal) & chewing_methods;
                break;
            case IF_LE_NAME:
                args->value = (IMArgVal) & lename;
                break;
            case IF_SUPPORTED_LOCALES:
                args->value = (IMArgVal) & locales;
                break;
            case IF_SUPPORTED_OBJECTS:
                break;
            case IF_NEED_THREAD_LOCK:
                args->value = (IMArgVal) False;
                break;
            default:
                break;
	}
    }
}
