/*
* Neima输入法，支持：
*   (1) 目前提供简体中文GB2312,GBK,GB18030和繁体中文BIG5。
*       具体charset决定于session的属性。
*   (2) 简体中文输入法使用自绘制的Canidates-UI，反映新的输入法
*       接口的性质:
*       a) 三种简体内码输入成为一种输入法
*          但是三种繁体内码各自成为一种输入法
*       b) 简体输入法使用自绘制的candidate界面
*       c) 使用用户配置决定是否显示(utf-8, unicode)提示
*/
#include <stdio.h>

#include "ime.h"
#include "ime_buffer.h"

/**************************************************************************
* IME Methods according to the IME.h
***************************************************************************/
ImeResult neima_Initialize(ImeInfo ime_info);
ImeResult neima_Destroy(ImeInfo ime_info);
ImeResult neima_Create_Session(ImeInputContext ic);
ImeResult neima_Destroy_Session(ImeInputContext ic);
ImeResult neima_Process_Key_Event(ImeInputContext ic, ImeKey key_event);
ImeResult neima_FocusIn(ImeInputContext ic);
ImeResult neima_FocusOut(ImeInputContext ic);
ImeResult neima_Create_User(ImeInputContext ic);
ImeResult neima_Destroy_User(ImeInputContext ic);
ImeResult neima_Create_Desktop(ImeInputContext ic);
ImeResult neima_Destroy_Desktop(ImeInputContext ic);

ImeMethodsRec neima_methods = {
    100,                              /* version */
    neima_Initialize,                 /* ImeInitialize */
    neima_Destroy,                    /* ImeDestroy  */
    neima_Process_Key_Event,          /* ImeProcessKeyEvent */
    NULL,                             /* ImeProcessAuxEvent  */
    neima_Create_Session,             /* ImeAttachSession */
    neima_Destroy_Session,            /* ImeDetachSession */
    neima_FocusIn,                    /* ImeFocusIn  */
    neima_FocusOut,                   /* ImeFocusOut */
    neima_Create_User,                /* ImeAttachUser */
    neima_Destroy_User,               /* ImeDetachUser */
    neima_Create_Desktop,             /* ImeAttachDesktop */
    neima_Destroy_Desktop,            /* ImeDeta chDesktop */
    NULL,                             /* ImeGetErrorMsg */
};

ImmServices imm_services = NULL;

// Typedefine for data saved in user scope
// do not used bit mask, just for demostration
typedef struct _TUserPreference {
    int    show_utf8_value;
    int    show_unicode_value;
} TUserPreference;

// Typedefine for data saved in desktop scope
enum {
    LANG_NONE           = 0,
    LANG_ZHCN           = ENCODE_GB18030,
    LANG_ZHTW_BIG5      = ENCODE_BIG5,
    LANG_ZHTW_BIG5HKSCS = ENCODE_BIG5HKSCS,
    LANG_ZHTW_EUCTW     = ENCODE_EUCTW
};

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

    DEBUG_printf("    ====>Register NeiMa IM: argc: %d\n", argc);
    if (argc == 1) { // IMM should not load it twice for same parameter
        if (strcmp(argv[0], "zh_CN") == 0) {
            lang = LANG_ZHCN;
        } else if (strcmp(argv[0], "BIG5") == 0) {
            lang = LANG_ZHTW_BIG5;
        } else if (strcmp(argv[0], "BIG5HKSCS") == 0) {
            lang = LANG_ZHTW_BIG5HKSCS;
        } else if (strcmp(argv[0], "EUCTW") == 0) {
            lang = LANG_ZHTW_EUCTW;
        }
    }
    if (argc != 1 || lang == LANG_NONE){
        DEBUG_printf("        ====>Please using argument [zh_CN|BIG5|BIG5HKSCS|EUCTW]\n");
        return IME_FAIL;
    }

    ime_info = (ImeInfoRec *)malloc(sizeof(ImeInfoRec)); //suppose success

    ime_info->version             = 100;
    ime_info->mt_safe             = 0;
    ime_info->encoding            = lang;
    ime_info->uuid                = "neima-1d76e189-9a54-4a24-8cf7-5d611f3d555f";
    ime_info->author              = "Phill Zhang <Phill.Zhang@sun.com>";
    ime_info->copyright           = "Copyright (c) 2004 Sun Microsystems";;
    ime_info->icon_file           = "neima.xpm";
    ime_info->pl                  = NULL;
    ime_info->specific_data       = (void*)lang;
    ime_info->hinting             = NULL;

    switch (lang) {
    case LANG_ZHCN:
        ime_info->name       = "����";
        ime_info->support_locales = "zh,zh_CN,zh_CN.GB2312,zh_CN.GBK,zh_CN.UTF-8,zh_CN.GB18030";
        break;
    case LANG_ZHTW_BIG5:
        ime_info->name       = "BIG5���X";
        ime_info->support_locales = "zh_TW,zh_TW.BIG5,zh_TW.UTF-8";
        break;
    case LANG_ZHTW_BIG5HKSCS:
        ime_info->name       = "BIG5HKSCS���X";
        ime_info->support_locales = "zh_HK,zh_HK.BIG5HKSCS,zh_HK.UTF-8";
        break;
    case LANG_ZHTW_EUCTW:
        ime_info->name       = "EUC";
        ime_info->support_locales = "zh_TW.EUCTW,zh_TW.UTF-8";
        break;
    }
    *ppinfo = ime_info;
    *pmthds = &neima_methods;
    imm_services = srvs;

    return (IME_OK);
}

static
int get_ime_language(ImeInputContext ic)
{
    ImeInfo ime_info = imm_services->ImmGetImeInfo(ic);
    return (ime_info)?((int)ime_info->specific_data):(LANG_NONE);
}

static
ImHandle get_candidate_aux(ImeInputContext ic)
{
    return (ImHandle)imm_services->ImmGetData(ic, IME_SCOPE_DESKTOP);
}

ImeResult neima_Initialize(ImeInfo ime_info)
{
    DEBUG_printf("    ====>neima_Initialize for language %d\n", ime_info->specific_data);

    return (IME_OK);
}

ImeResult neima_Destroy(ImeInfo ime_info)
{
    DEBUG_printf("    ====>neima_Destroy\n");

    if (ime_info != NULL)
        free(ime_info);

    return (IME_OK);
}

static
ImeResult my_update_candidates(ImeInputContext ic, ImeBufferRec *ime_buffer)
{
    int sz = 0;
    ImeEventRec         event;

    ime_buffer->candidates.horizental = 0;

    event.any_event.ic = ic;
    event.any_event.peer = get_candidate_aux(ic);
    if (ime_buffer) sz = ime_buffer->candidates.count;
    if ((get_ime_language(ic) == LANG_ZHCN) && (event.any_event.peer != 0)) { //make sure the aux start successfully
        if (sz == 0) {
            event.type = IME_EVENT_EXPOSE;
            event.any_event.param = 0;    //hide candidates
            imm_services->ImmSendUiMessage(&event);
        } else {
            event.type = IME_EVENT_EXPOSE;
            event.any_event.param = IMUI_STATE_SHOW;    //show candidates
            imm_services->ImmSendUiMessage(&event);
            event.type = IME_EVENT_CANDI_DATA;
            event.any_event.param = ENCODE_GB18030;
            event.candidate_data_event.candidates = &(ime_buffer->candidates);
            imm_services->ImmSendUiMessage(&event);
        }
    } else {
        if (sz == 0)
            imm_services->ImmHideCandidates(ic);
        else {
            imm_services->ImmShowCandidates(ic);
            imm_services->ImmUpdateCandidates(ic, &ime_buffer->candidates);
        }
    }

    return IME_OK;
}

//infact 9/16/5/1/5
#define MAX_PREEDIT_BYTES               16
#define MAX_CANDIDATES_NUM              16
#define MAX_CANDIDATE_BYTES             16
#define MAX_NUMBERING_BYTES             4
#define MAX_COMMIT_BYTES                16

ImeResult neima_Create_Session(ImeInputContext ic)
{
    int i;
    ImmResult imm_result;
    ImeBufferRec *ime_buffer = NULL;

    DEBUG_printf("    ====>neima_Create_Session ======= begin calloc for ime_buffer\n");
    ime_buffer = alloc_ime_buffer(MAX_PREEDIT_BYTES, MAX_CANDIDATES_NUM, MAX_CANDIDATE_BYTES, MAX_NUMBERING_BYTES, MAX_COMMIT_BYTES);
    if (ime_buffer == NULL)
        return (IME_FAIL);

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

    return (IME_OK);
}

ImeResult neima_Destroy_Session(ImeInputContext ic)
{
    ImeBufferRec *ime_buffer = (ImeBufferRec *)imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
    DEBUG_printf("    ====>neima_Destroy_Session ======= begin get ime_session_data: 0x%x\n", ime_buffer);

    free_ime_buffer(ime_buffer);

    return (IME_OK);
}

ImeResult neima_Create_User(ImeInputContext ic)
{
    int  sz;
    unsigned char* buf = NULL;
    TUserPreference* pup = NULL;

    if (pup = (TUserPreference*)malloc(sizeof(TUserPreference))) {
        memset(pup, 0, sizeof(TUserPreference));
        buf = (unsigned char*)imm_services->ImmLoadUserProfile(ic, "preference", &sz);
        if (buf) {
            if (sscanf(buf, "%d", &sz) == 1) {
                pup->show_utf8_value = (sz & 1);
                pup->show_unicode_value = (sz & 2);
            }
            imm_services->ImmFreeUserProfile(buf);
        }
        imm_services->ImmSetData(ic, IME_SCOPE_USER, pup);
        return IME_OK;
    }
    return IME_FAIL;
}

ImeResult neima_Destroy_User(ImeInputContext ic)
{
    int  pref = 0;
    char valstr[32];
    TUserPreference* pup = (TUserPreference*)imm_services->ImmGetData(ic, IME_SCOPE_USER);
    if (pup) {
        if (pup->show_utf8_value) pref |= 1;
        if (pup->show_unicode_value) pref |= 2;
        snprintf(valstr, 32, "%d\n", pref);
        return ImmSaveUserProfile(ic, "preference", valstr, strlen(valstr));
    }
    return IME_OK;
}

ImeResult neima_Create_Desktop(ImeInputContext ic)
{
    static const char *aux_name = "com.sun.iiim.cle.aux.neima";

    DEBUG_printf("    ====>neima: call neima_Create_Desktop()\n");

    if (get_ime_language(ic) == LANG_ZHCN) {
        ImHandle candi_aux = imm_services->ImmStartUI(ic, aux_name);
        imm_services->ImmSetData(ic, IME_SCOPE_DESKTOP, (void*)candi_aux);
    }
}

ImeResult neima_Destroy_Desktop(ImeInputContext ic)
{
    DEBUG_printf("    ====>neima: call neima_Destroy_Desktop()\n");

    if (get_ime_language(ic) == LANG_ZHCN) {
        ImHandle candi_aux = (ImHandle)imm_services->ImmGetData(ic, IME_SCOPE_DESKTOP);
        imm_services->ImmCloseUI(ic, candi_aux);
    }
}

ImeResult neima_FocusIn(ImeInputContext ic)
{
    DEBUG_printf("    ====>neima: call neima_FocusIn()\n");

    ImeBufferRec *ime_buffer = NULL;
    if (get_ime_language(ic) == LANG_ZHCN) {
        ime_buffer = (ImeBufferRec *)imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
        if (ime_buffer && ime_buffer->candidates.count)
            return my_update_candidates(ic, ime_buffer);
    }

    return IME_OK;
}

ImeResult neima_FocusOut(ImeInputContext ic)
{
    ImeEventRec event;

    DEBUG_printf("    ====>neima: call neima_FocusOut()\n");

    if (get_ime_language(ic) == LANG_ZHCN) {
        event.type              = IME_EVENT_EXPOSE;
        event.any_event.peer    = get_candidate_aux(ic);
        if (event.any_event.peer) {
            event.any_event.ic      = ic;
            event.any_event.param   = 0;    //hide it
            imm_services->ImmSendUiMessage(&event);
        }
    }
    return(IME_OK);
}

/* 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 neima_Process_Key_Event(ImeInputContext ic, ImeKey key_event)
{
    unsigned char   key;
    int             lang, ret, encoding = 0;
    ImeBufferRec   *ime_buffer = NULL;

    DEBUG_printf("    ====>neima_Process_Key_Event: ic: 0x%x\n", ic);
    ime_buffer = (ImeBufferRec *)imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
    if (ime_buffer == NULL){
        DEBUG_printf("      ====>neima: ime_buffer is null.\n");
        return (IME_UNUSED_KEY);
    }

    ime_buffer->return_status = 0;

    lang = get_ime_language(ic);
    if (lang == LANG_ZHCN) {
        DEBUG_printf("      ====>neima: language is zh_CN.\n");
        encoding = imm_services->ImmGetSessionEncoding(ic);
        if (encoding != ENCODE_GB2312 && encoding != ENCODE_GBK)
            encoding = lang;
    } else
        encoding = lang;

    key = imm_services->ImmPrefilterKey(key_event);
    if (key == IME_FILTERED_KEY_UNUSED)
	return (IME_UNUSED_KEY);

    ret = neima_filter(encoding, key, ime_buffer);
    if (ret == IME_UNUSED_KEY) {
        DEBUG_printf("      ====>neima: key is not used.\n");
	return (IME_UNUSED_KEY);
    }

    DEBUG_printf("      ====>neima: key is used.\n");
    if (ime_buffer->return_status & IME_PREEDIT_AREA) {
        if (ime_buffer->preedit.preedit.text[0] != '\0') {
            imm_services->ImmShowPreedit(ic);
            imm_services->ImmUpdatePreedit(ic, &ime_buffer->preedit);
        } else {
            imm_services->ImmHidePreedit(ic);
        }
    }

    if (ime_buffer->return_status & IME_LOOKUP_AREA)
        my_update_candidates(ic, ime_buffer);

    if (ime_buffer->return_status & IME_COMMIT)
        imm_services->ImmCommit(ic, ime_buffer->commit_buf);

    return (IME_OK);
}
