#include <stdio.h>
#include <unistd.h>

#include "ime.h"
#include "chewing_im.h"

#define CHEWING_DATADIR         "/usr/share/chewing"
#define CHEWING_NAME_BIG5       "\267\163\273\305\255\265"
#define CHEWING_UUID            "chewing-9d3878a2-ca6a-4dbb-9e81-62b3774716c3"
#define CHEWING_VERSION		200
#define AUTHOR \
	"Chewing core team <http://chewing.csie.net>\n" \
	"Gavin Tu <Gavin.Tu@sun.com>"
#define COPYRIGHT		"Copyright (c) 2004 Chewing core team"
#define HINTING			"Chewing Input Method"
#define CHEWING_ICONPATH        "chewing.xpm"

ImeResult chewing_Initialize(ImeInfo ime_info);
ImeResult chewing_Destroy(ImeInfo ime_info);
ImeResult chewing_Process_Key_Event(ImeInputContext ic, ImeKey key_event);
ImeResult chewing_Create_Session(ImeInputContext ic);
ImeResult chewing_Destroy_Session(ImeInputContext ic);
ImeResult chewing_FocusOut(ImeInputContext ic);
ImeResult chewing_FocusIn(ImeInputContext ic);

ImmServices imm_services;

ImeMethodsRec chewing_methods = {
    1,				/* version */
    chewing_Initialize,		/* ImeInitialize */
    chewing_Destroy,		/* ImeDestroy  */
    chewing_Process_Key_Event,	/* ImeProcessKeyEvent */
    NULL,			/* ImeProcessAuxEvent  */
    chewing_Create_Session,	/* ImeAttachSession */
    chewing_Destroy_Session,	/* ImeDetachSession */
    chewing_FocusIn,		/* ImeFocusIn  */
    chewing_FocusOut,		/* ImeFocusOut */
    NULL,			/* ImeAttachUser */
    NULL,			/* ImeDetachUser */
    NULL,			/* ImeAttachDesktop */
    NULL,			/* ImeDetachDesktop */
    NULL,			/* ImeGetErrorMessage */
#if 0
    NULL,			/* ImeDoConfig */
#endif
};

#ifdef	WIN32
#define EXPORT extern __declspec(dllexport)
EXPORT
#endif
ImeResult RegisterIME(ImmServices srvs, ImeInfo * ppinfo,
		      ImeMethods * pmthds, int argc, char **argv)
{
    ImeInfoRec *chewing_info = NULL;
    char *base_dir = NULL;
    int i;

    DEBUG_printf("Register Chewing IM: argc: %d\n", argc);
    for (i = 0; i < argc; i++) {
	if (!strcasecmp(argv[i], "-basedir")) {
	    if (argv[i + 1]) {
		base_dir = argv[i + 1];
		DEBUG_printf("       setting base dir to: %s\n",
			     argv[i + 1]);
	    }
	    i++;
	}
    }

    if (access("/usr/lib/libchewing.so", R_OK) != 0) {
	fprintf(stderr, "Error: Chewing: /usr/lib/libchewing.so not found !!\n");
	return (IME_FAIL);
    }

    if (access(CHEWING_DATADIR "/dict.dat", R_OK) != 0) {
	fprintf(stderr, "Error: Chewing: data directory: %s not found !!\n", CHEWING_DATADIR);
	return (IME_FAIL);
    }

    chewing_info = (ImeInfoRec *) calloc(1, sizeof(ImeInfoRec));
    DEBUG_printf("chewing_info: %p\n", chewing_info);
    if (chewing_info == NULL) {
	return (IME_FAIL);
    }

    chewing_info->version = CHEWING_VERSION;
    chewing_info->mt_safe = 0;
    chewing_info->encoding = ENCODE_BIG5;
    chewing_info->uuid = CHEWING_UUID;
    chewing_info->name = CHEWING_NAME_BIG5;
    chewing_info->author = AUTHOR;
    chewing_info->hinting = HINTING;
    chewing_info->copyright = COPYRIGHT;
    chewing_info->icon_file = CHEWING_ICONPATH;
    chewing_info->support_locales = "zh_TW.UTF-8,zh_TW.BIG5";
    chewing_info->pl = NULL;
    chewing_info->specific_data = NULL;

    chewing_Init_Ime_Properties(chewing_info);

    *ppinfo = chewing_info;
    *pmthds = &chewing_methods;

    imm_services = srvs;

    DEBUG_printf("begin leave Register IME\n");
    return (IME_OK);
}

ImeResult chewing_Initialize(ImeInfo chewing_info)
{
    char *prefix = CHEWING_DATADIR;

    DEBUG_printf("chewing_Initialize\n");

    if (chewing_info == NULL)
	return (IME_FAIL);

    ReadTree(prefix);

    if (InitChar(prefix) == 0) {
	fprintf(stderr, "Error: Chewing dictionary file corrupted!\n");
	return (IME_FAIL);
    }

    InitDict(prefix);

    if (ReadHash(prefix) == 0) {
	fprintf(stderr, "Error: Chewing's user phrase library load failed! \n");
	return (IME_FAIL);
    }

    DEBUG_printf("Chewing initialize done\n");

    return (IME_OK);
}

ImeResult chewing_Destroy(ImeInfo chewing_info)
{
    DEBUG_printf("chewing_Destroy\n");

    if (chewing_info != NULL) {
	chewing_Destroy_Ime_Properties(chewing_info);
	free((char *) chewing_info);
    }

    return (IME_OK);
}

ImeResult chewing_Create_Session(ImeInputContext ic)
{
    int i;
    ImmResult imm_result;
    ImeInfoRec *chewing_info = NULL;
    chewing_session_t *chewing_session;

    chewing_session = (chewing_session_t *) imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
    DEBUG_printf ("chewing_Create_Session ======= begin get ime_session_data: %p\n",
		  chewing_session);
    if (chewing_session == NULL) {
	int i;
	ChewingConf cf;
	ConfigData config;
	char default_selectionKeys[] = "1234567890";

	DEBUG_printf("chewing_Create_Session ======= begin calloc for chewing_session\n");
	chewing_session = (chewing_session_t *) calloc(1, sizeof(chewing_session_t));
	if (chewing_session == NULL)
	    return (IME_FAIL);

	/* init chewing core */
	chewing_session->keyboard_type = KB_DEFAULT;

	cf.kb_type = KB_DEFAULT;
	InitChewing(&chewing_session->chewing_data, &cf);

	config.selectAreaLen = 40;
	config.maxChiSymbolLen = 16;

	for (i = 0; i < 10; i++)
	    config.selKey[i] = default_selectionKeys[i];

	config.bAddPhraseForward = 1;

	SetConfig(&chewing_session->chewing_data, &config);

	MakeOutput(&chewing_session->chewing_output,
		   &chewing_session->chewing_data);

	imm_result = imm_services->ImmSetData(ic, IME_SCOPE_SESSION, chewing_session);
	if (imm_result == IMM_FAIL) {
	    free((char *) chewing_session);
	    return (IME_FAIL);
	}
    }

    return (IME_OK);
}

ImeResult chewing_Destroy_Session(ImeInputContext ic)
{
    chewing_session_t *chewing_session;

    chewing_session = (chewing_session_t *) imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
    DEBUG_printf (" ====>chewing_Destroy_Session ======= begin get ime_session_data: %p\n",
		  chewing_session);

    if (chewing_session != NULL) {
	free((char *) chewing_session);
    }

    imm_services->ImmSetData(ic, IME_SCOPE_SESSION, NULL);
    return (IME_OK);
}

ImeResult chewing_FocusIn(ImeInputContext ic)
{
    DEBUG_printf("codetable: call chewing_FocusIn()\n");
    return (IME_OK);
}

ImeResult chewing_FocusOut(ImeInputContext ic)
{
    DEBUG_printf("codetable: call chewing_FocusOut()\n");
    return (IME_OK);
}

ImmResult chewing_session_beep(ImeInputContext ic, chewing_session_t * chewing_session)
{
    return (imm_services->ImmBeep(ic, ImeBeepWarning));
}

ImmResult chewing_session_commit(ImeInputContext ic, chewing_session_t * chewing_session)
{
    int i;
    char buf[128];

    ChewingOutput *op = &chewing_session->chewing_output;

    if (!op->nCommitStr)
	return;

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

    imm_services->ImmCommit(ic, buf);

    op->nCommitStr = 0;

    return (IME_OK);
}

ImmResult chewing_session_show_preedit(ImeInputContext ic, chewing_session_t * chewing_session)
{
    ImePreeditRec ime_preedit;

    int i, caret_pos;
    int len = 0;
    char buf[128];

    ChewingOutput *op = &chewing_session->chewing_output;

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

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

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

    len = strlen(buf);
    if (len == 0) {
	return (imm_services->ImmHidePreedit(ic));
    }

    imm_services->ImmShowPreedit(ic);

    caret_pos = op->chiSymbolCursor;
    if (caret_pos < 0 || caret_pos > 100)
	caret_pos = 0;

    memset(&ime_preedit, 0, sizeof(ImePreeditRec));
    ime_preedit.caret = caret_pos;
    ime_preedit.preedit.text = buf;

    return (imm_services->ImmUpdatePreedit(ic, &ime_preedit));
}

ImmResult chewing_session_handle_candidates(ImeInputContext ic, chewing_session_t * chewing_session)
{
    ImmResult imm_result;
    ImeCandidatesRec ime_candidates;

    ChewingOutput *op = &chewing_session->chewing_output;
    ChoiceInfo *pci = op->pci;
    int i, fc, num_candidates;
    int page_state = 0;

    if (pci == NULL || pci->nTotalChoice <= 0) {
	return (imm_services->ImmHideCandidates(ic));
    }

    fc = pci->pageNo * pci->nChoicePerPage;
    if (pci->pageNo == 0)
	page_state |= ImeCandidatesFirstPage;  /* disable PageUp icon */

    num_candidates = pci->nTotalChoice - fc;
    if (num_candidates > pci->nChoicePerPage) {
	num_candidates = pci->nChoicePerPage;
    } else {
	page_state |= ImeCandidatesLastPage;    /* disable PageDown icon */
    }

    memset(&ime_candidates, 0, sizeof(ImeCandidatesRec));

    ime_candidates.title = NULL;
    ime_candidates.numbers = NULL;
    ime_candidates.focus = 0;
    ime_candidates.page_state = page_state;
    ime_candidates.count = num_candidates;
    ime_candidates.candidates = (ImeTextRec *) calloc(num_candidates, sizeof(ImeTextRec));
    if (ime_candidates.candidates == NULL)
	return (IMM_FAIL);

    for (i = 0; i < num_candidates; i++) {
	ime_candidates.candidates[i].text = pci->totalChoiceStr[fc + i];
    }

    imm_result = imm_services->ImmUpdateCandidates(ic, &ime_candidates);

    imm_services->ImmShowCandidates(ic);

    free ((char *)ime_candidates.candidates);
    return (imm_result);
}

/* process key input event */
/* return value:  IME_UNUSED_KEY:  if IME not use this key, return this key to systerm directly */
/*                IME_OK:      if IME has used this key */
ImeResult chewing_Process_Key_Event(ImeInputContext ic, ImeKey key_event)
{
    chewing_session_t *chewing_session = NULL;
    ChewingOutput *op;
    ChewingData *pgdata;

    long kcode, kstate;
    unsigned short kchar;

    DEBUG_printf("chewing_Process_Key_Event: ic: %p\n", ic);
    chewing_session = (chewing_session_t *) imm_services->ImmGetData(ic, IME_SCOPE_SESSION); 
    if (chewing_session == NULL)
	return (IME_UNUSED_KEY);

    kcode = key_event->keycode;
    kchar = key_event->keychar;
    kstate = key_event->modifier;

    DEBUG_printf ("  ====> Chewing processing key (0X%X - 0X%X - 0X%X) ...\n",
	 	  kcode, kchar, kstate);

    chewing_Set_Ime_Properties(ic, chewing_session);

    op = &chewing_session->chewing_output;
    pgdata = &chewing_session->chewing_data;

    op->keystrokeRtn = KEYSTROKE_IGNORE;
    pgdata->zuinData.kbtype = chewing_session->keyboard_type;

    if (kstate == 0) {
        switch (kcode) {
        case IME_VK_PAGE_UP:
        case IME_VK_LEFT:
	    OnKeyLeft(pgdata, op);
	    break;

        case IME_VK_PAGE_DOWN:
        case IME_VK_RIGHT:
	    OnKeyRight(pgdata, op);
	    break;

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

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

        case IME_VK_SPACE:
	    OnKeySpace(pgdata, op);
	    break;

        case IME_VK_CAPS_LOCK:
	    OnKeyCapslock(pgdata, op);
	    break;

        case IME_VK_ENTER:
	    OnKeyEnter(pgdata, op);
	    break;

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

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

        case IME_VK_DELETE:
	    OnKeyDel(pgdata, op);
	    break;

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

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

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

        default:
	    if (kchar)
	        OnKeyDefault(pgdata, kchar, op);
	    break;
        }
    } else if (kstate == IME_SHIFT_MASK) {
        switch (kcode) {
        case IME_VK_PAGE_UP:
        case IME_VK_LEFT:
	    OnKeyLeft(pgdata, op);
	    break;

        case IME_VK_PAGE_DOWN:
        case IME_VK_RIGHT:
	    OnKeyRight(pgdata, op);
	    break;

        default:
	    if (kchar)
	        OnKeyDefault(pgdata, kchar, op);
	    break;
        }
    } else if (kstate == IME_CTRL_MASK) {
        /* Handle user-phrase addition */
	if (kchar <= '9' && kchar >= '0')
	    OnKeyCtrlNum (pgdata, kchar, op);
    }

    DEBUG_printf("  ---->return bitmask 0x%x\n", op->keystrokeRtn);
    if (op->keystrokeRtn & KEYSTROKE_COMMIT) {
	DEBUG_printf("    ---->commiting...\n");
	chewing_session_commit(ic, chewing_session);
    }

    DEBUG_printf("    ---->show preedit...\n");
    chewing_session_show_preedit(ic, chewing_session);

    DEBUG_printf("    ---->handle candidates...\n");
    chewing_session_handle_candidates(ic, chewing_session);

    if (op->keystrokeRtn & KEYSTROKE_BELL)
	chewing_session_beep(ic, chewing_session);

    if (op->keystrokeRtn & KEYSTROKE_ABSORB)
	return (IME_OK);

    if (op->keystrokeRtn & KEYSTROKE_IGNORE)
	return (IME_UNUSED_KEY);

    return (IME_OK);
}
