#include <stdio.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ctim.h"
#include "ctim_messages.h"

#define NAME_UTF8	"Codetable"
#define AUTHOR		"Ervin Yan <Ervin.Yan@sun.com>"
#define COPYRIGHT	"Copyright (c) 2004 Sun Microsystems"
#define HINTING		"Codetable Input Method"

ImeResult ctim_Initialize(ImeInfo ime_info);
ImeResult ctim_Destroy(ImeInfo ime_info);
ImeResult ctim_Process_Key_Event(ImeInputContext ic, ImeKey key_event);
ImeResult ctim_Create_Session(ImeInputContext ic);
ImeResult ctim_Destroy_Session(ImeInputContext ic);
ImeResult ctim_FocusOut(ImeInputContext ic);
ImeResult ctim_FocusIn(ImeInputContext ic);

ImmServices imm_services;

typedef struct _codetable_im_data_t {
        char *file_name;
        CodeTableStruct *ctHeader;
} codetable_im_data_t;

ImeMethodsRec ctim_methods = {
        1,                                /* version */
        ctim_Initialize,                  /* ImeInitialize */
        ctim_Destroy,                     /* ImeDestroy  */
        ctim_Process_Key_Event,           /* ImeProcessKeyEvent */
        NULL,                             /* ImeProcessAuxEvent  */
        ctim_Create_Session,              /* ImeAttachSession */
        ctim_Destroy_Session,             /* ImeDetachSession */
        ctim_FocusIn,                     /* ImeFocusIn  */
        ctim_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 *ctim_info = NULL;
        char *codetable_file_name = NULL;
        char *base_dir = NULL;
        char file_path[256];
        CodeTableStruct ctHeader;
        codetable_im_data_t *ctim_data = NULL;

        int version_num, i, ret;

        DEBUG_printf("Register Codetable 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++;
                } else if (!strcasecmp(argv[i], "-codetable")) {
                        if (argv[i+1]) {
                                codetable_file_name = argv[i+1];
                                DEBUG_printf("       setting codetable file to: %s\n", argv[i+1]);
                        }
                        i++;
                }
        }

        if (!codetable_file_name || !*codetable_file_name)
                return (IME_FAIL);

        if (base_dir == NULL)
                base_dir = LE_BASE_DIR;

        if (base_dir && *base_dir && *codetable_file_name != '/') {
                snprintf(file_path, 256, "%s/%s", base_dir, codetable_file_name);
        }

        DEBUG_printf("file_path: %s\n", file_path);
        ret = LoadCodeTableHeader(file_path, &ctHeader);
        if (ret == -1)
                return (IME_FAIL);

        ctim_data = (codetable_im_data_t *)calloc(1, sizeof(codetable_im_data_t));
        if (ctim_data == NULL)
                return (IME_FAIL);

        ctim_info = (ImeInfoRec *)calloc(1, sizeof(ImeInfoRec));
        if (ctim_info == NULL)  {
                free ((char *)ctim_data);
                return (IME_FAIL);
        }

        version_num = 1;
        if (*(ctHeader.Version))
                version_num = atoi(ctHeader.Version);
        version_num += CODETABLE_VERSION * 100;

        ctim_info->version           = version_num;
        ctim_info->encoding          = ctHeader.Encode;
        ctim_info->name              = (char *)strdup(ctHeader.Cname);
        ctim_info->uuid              = (char *)strdup(ctHeader.UUID);

        if (*ctHeader.Author)
                ctim_info->author    = (char *)strdup(ctHeader.Author);
        else
                ctim_info->author    = (char *)strdup(AUTHOR);
        if (*ctHeader.Copyright)
                ctim_info->copyright = (char *)strdup(ctHeader.Copyright);
        else
                ctim_info->copyright = (char *)strdup(COPYRIGHT);
        if (*ctHeader.Hinting)
                ctim_info->hinting   = (char *)strdup(ctHeader.Hinting);
        else
                ctim_info->hinting   = (char *)strdup(HINTING);

        ctim_info->icon_file         = (char *)strdup(ctHeader.IconPath);

        ctim_info->support_locales = CTIM_SUPPORT_LOCALES;

        ctim_data->file_name = (char *)strdup(file_path);
        ctim_data->ctHeader  = NULL;

        ctim_info->specific_data     = (void *)ctim_data;

        *ppinfo = ctim_info;
        *pmthds = &ctim_methods;

        imm_services = srvs;

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

ImeResult ctim_Initialize(ImeInfo ctim_info)
{
        codetable_im_data_t *ctim_data;
        CodeTableStruct *ctHeader;
        int ret;

        DEBUG_printf("ctim_Initialize\n");

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

        ctim_data = (codetable_im_data_t *)ctim_info->specific_data;
        if (ctim_data == NULL ||
            ctim_data->file_name == NULL)
                return (IME_FAIL);

        if (ctim_data->ctHeader != NULL)
                return (IME_OK);

        ctHeader = (CodeTableStruct *)calloc(1, sizeof(CodeTableStruct));
        if (ctHeader == NULL)
                return (IME_FAIL);

        ret = LoadCodeTable(ctim_data->file_name, ctHeader);
        if (ret == -1) {
                UnloadCodeTable(ctHeader);
                free((char *)ctHeader);
                return(IME_FAIL);
        }

        ctim_data->ctHeader = ctHeader;

	ctim_Init_Ime_Properties(ctim_info, ctHeader);

        return (IME_OK);
}

ImeResult ctim_Destroy(ImeInfo ctim_info)
{
        codetable_im_data_t *ctim_data;

        DEBUG_printf("ctim_Destroy\n");

        if (ctim_info != NULL) {
                if (ctim_info->uuid != NULL)
                        free ((char *)ctim_info->uuid);
                if (ctim_info->icon_file != NULL)
                        free ((char *)ctim_info->icon_file);
                if (ctim_info->name != NULL)
                        free ((char *)ctim_info->name);
                if (ctim_info->author != NULL)
                        free ((char *)ctim_info->author);
                if (ctim_info->copyright != NULL)
                        free ((char *)ctim_info->copyright);
                if (ctim_info->hinting != NULL)
                        free ((char *)ctim_info->hinting);

		ctim_Destroy_Ime_Properties(ctim_info);

                ctim_data = (codetable_im_data_t *)ctim_info->specific_data;
                if (ctim_data) {
                        if (ctim_data->file_name)
                                free ((char *)ctim_data->file_name);
                        if (ctim_data->ctHeader) {
                                UnloadCodeTable(ctim_data->ctHeader);
                                free ((char *)ctim_data->ctHeader);
                        }
                        free ((char *)ctim_data);
                }

                free ((char *)ctim_info);
        }

        return (IME_OK);
}

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

        ime_buffer = (ImeBufferRec *)imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
        DEBUG_printf("ctim_Create_Session ======= begin get ime_session_data: 0x%x\n", ime_buffer);
        if (ime_buffer == NULL) {
                ime_buffer = (ImeBufferRec *)calloc(1, sizeof(ImeBufferRec));
                if (ime_buffer == NULL)
                        return (IME_FAIL);

                for (i = 0; i < MAX_CANDIDATES_NUM; i++) {
                        ime_buffer->candidates[i] = ime_buffer->candidates_buf[i];
                        ime_buffer->comments[i] = ime_buffer->comments_buf[i];
                        ime_buffer->lookups[i] = ime_buffer->lookups_buf[i];
                }

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

        return (IME_OK);
}

ImeResult ctim_Destroy_Session(ImeInputContext ic)
{
        ImeBufferRec *ime_buffer = NULL;

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

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

        imm_services->ImmSetData(ic, IME_SCOPE_SESSION, NULL);

        return (IME_OK);
}

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


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


ImmResult ctim_beep(ImeInputContext ic)
{
	return (imm_services->ImmBeep(ic, ImeBeepWarning));
}

ImmResult ctim_commit(ImeInputContext ic, int encoding, char *commit_buf, int commit_len)
{
        if (commit_len <= 0)
                return (IMM_FAIL);

        if (commit_buf == NULL)
                return (IMM_FAIL);

        return (imm_services->ImmCommit(ic, commit_buf));
}

ImmResult ctim_update_preedit(ImeInputContext ic, int encoding,
			      char *preedit_buf, int preedit_len, int caret_pos)
{
        ImePreeditRec ime_preedit;
        memset(&ime_preedit, 0, sizeof(ImePreeditRec));

        if (preedit_len == 0) {
                return (imm_services->ImmHidePreedit(ic));
        }

        imm_services->ImmShowPreedit(ic);

        ime_preedit.cl_start = ime_preedit.caret = caret_pos;
        ime_preedit.preedit.text     = preedit_buf;

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

ImmResult ctim_update_candidates(ImeInputContext ic, int encoding, int label_type, char **candidates, int num_candidates, int page_state)
{
        int i;
        ImmResult imm_result;
        ImeCandidatesRec       ime_candidates;

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

        if (num_candidates == 0 || candidates == NULL) {
                return (imm_services->ImmHideCandidates(ic));
        }

        imm_services->ImmShowCandidates(ic);

        ime_candidates.title = NULL;
        ime_candidates.focus = 0;
	ime_candidates.page_state = page_state;

        ime_candidates.numbers = NULL;
        switch (label_type) {
        case NUMBER0_MODE:
                ime_candidates.numbers = "0123456789abcdefghijklmnopqrstuvwxyz";
                break;
        case NUMBER_MODE:
                ime_candidates.numbers = "1234567890abcdefghijklmnopqrstuvwxyz";
                break;
        case LOWER_MODE:
                ime_candidates.numbers = "abcdefghijklmnopqrstuvwxyz";
                break;
        case UPPER_MODE:
                ime_candidates.numbers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                break;
        }

        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 = candidates[i];
        }

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

	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 ctim_Process_Key_Event(ImeInputContext ic, ImeKey key_event)
{
        ImeInfoRec *ctim_info = NULL;
        ImeBufferRec *ime_buffer = NULL;
        CodeTableStruct *ctHeader;

        codetable_im_data_t *ctim_data;
        unsigned char key;
        int ret;

        DEBUG_printf("ctim_Process_Key_Event: ic: 0x%x\n", ic);
        ime_buffer = (ImeBufferRec *)imm_services->ImmGetData(ic, IME_SCOPE_SESSION);
        if (ime_buffer == NULL)
                return (IME_UNUSED_KEY);

        ctim_info = (ImeInfo)imm_services->ImmGetImeInfo(ic);
        if (ctim_info == NULL || ctim_info->specific_data == NULL)
                return (IME_UNUSED_KEY);

        ctim_data = (codetable_im_data_t *)ctim_info->specific_data;
	ctHeader = ctim_data->ctHeader;
        if (ctHeader == NULL)
                return (IME_UNUSED_KEY);

        if (imm_services->ImmPrefilterKey == NULL) {
                DEBUG_printf("ctim_Process_Key_Event: imm_services->ImmPrefilterKey is NULL\n");
                return (IME_UNUSED_KEY);
        }

        key = imm_services->ImmPrefilterKey(key_event);
        DEBUG_printf("ctim_Process_Key_Event: imm_services->ImmPrefilterKey return: 0x%x\n", key);
        if (key == IME_FILTERED_KEY_UNUSED)
                return (IME_UNUSED_KEY);

        ctim_Set_Ime_Properties(ic, ctHeader);

	key = tolower(key);
        ret = ctim_filter(ctHeader, key, ime_buffer);

        if (ime_buffer->return_status & IME_PREEDIT_AREA) {
                ctim_update_preedit(ic, ime_buffer->encoding, ime_buffer->preedit_buf,
				    ime_buffer->preedit_len, ime_buffer->preedit_caretpos);
        }

        if (ime_buffer->return_status & IME_LOOKUP_AREA) {
                ctim_update_candidates(ic, ime_buffer->encoding, ctHeader->nSelectKeyMode, ime_buffer->lookups, ime_buffer->num_candidates, ime_buffer->page_state);
        }

        if (ime_buffer->return_status & IME_COMMIT) {
                ctim_commit(ic, ime_buffer->encoding, ime_buffer->commit_buf, ime_buffer->commit_len);
        }

        if (ime_buffer->return_status & IME_BEEP) {
                ctim_beep(ic);
        }

        if (ret == IME_UNUSED_KEY)
                return (IME_UNUSED_KEY);

        return (IME_OK);
}
