/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial
portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP OR SUN MICROSYSTEMS, INC. BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/

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

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
#include <pwd.h>
#include <sys/types.h>
#include <canna/jrkanji.h>

#ifndef _WCHAR_T
#define _WCHAR_T
#endif

#ifdef	DEBUG
#define	ENTER	fprintf(stderr, "%s: enter.\n", __FUNCTION__)
#define	LEAVE	fprintf(stderr, "%s: leave.\n", __FUNCTION__)
#else
#define	ENTER
#define	LEAVE
#endif

#include "cannadef.h"
#include "SunIM.h"

static char canna_le_init_filename[] = ".canna";

#define CANNA_COMMIT_STRING_BUFSIZE (8192 * 2)
#define CANNA_CODESET_LOCALE "ja_JP.eucJP"
#define EUC_JP_SS2 0x8E
#define EUC_JP_SS3 0x8F
#define CANNA_NEXT_CHAR(p) 		\
  (((*p) == 0) ? (p) :			\
   (((*p) < 0x80) ? ((p) + 1) :		\
    (((*p) == EUC_JP_SS3) ? ((p) + 3) :	\
     ((p) + 2))))
#define CANNA_GUIDELINE_DELIMITER_P(p)	\
  ((((*p) == ' ') && ((p[1]) == ' ')) || ((*p) == '\t') || 	\
   (((*p) == 0xA1) && ((p[1]) == 0xA1)))

#include <dlfcn.h>
#include "csconv.h"
#define CSC_PATH	"/usr/lib/im/csconv/csconv.so"
#define CSC_OPEN_LOCALE	"csconv_open_locale"
#define CSC_OPEN	"csconv_open"
#define CSC_CONV	"csconv"
#define CSC_CLOSE	"csconv_close"

#define CSC_FAILED -1
#define CSC_UNLOADED 0
#define CSC_UNOPENED 1
#define CSC_LOADED 2

typedef csconv_t	(* csc_open_locale_t)(const char *,
					      const char *, const char *);
typedef csconv_t	(* csc_open_t)(const char *, const char *);
typedef size_t		(* csc_conv_t)(csconv_t, const char **, size_t *,
				       char **, size_t *);
typedef int		(* csc_close_t)(csconv_t);

static void *			csc_handle;	
static csc_open_locale_t	csc_open_locale;
static csc_open_t		csc_open;
static csc_conv_t		csc_conv;
static csc_close_t		csc_close;
static csconv_t                 csconv_cd = NULL;

Bool    if_canna_OpenIF();
Bool    if_canna_CloseIF();
Bool    if_canna_GetIFValue();
Bool    if_canna_SetIFValue();
Bool    if_canna_OpenDesktop();
Bool    if_canna_CloseDesktop();
Bool    if_canna_CreateSC();
Bool    if_canna_DestroySC();
Bool    if_canna_GetSCValue();
Bool    if_canna_SetSCValue();
IMText  *if_canna_ResetSC();
void    if_canna_SetSCFocus();
void    if_canna_UnsetSCFocus();
void    if_canna_SendEvent();

Bool canna_parse_guideline(iml_session_t *s, int *pnum,
			   unsigned char ***psegs,
			   int **pnb,
			   int *pcurrent);
void		canna_status_draw_off(iml_session_t *s);
static int	canna_get_current_mode(iml_session_t *s);
static int      canna_get_current_minor_mode(iml_session_t *s);
int             canna_get_current_candidate_position(iml_session_t *s);
int             canna_get_candidate_count(iml_session_t *s);
static void	canna_change_mode(iml_session_t *s, int id);
static Bool	canna_drop_privilege(const char *username);
void            aux_draw(iml_session_t *s, int count_integers,
			 int *integers, int count_strings,
			 unsigned char **strings);
static Bool     process_keyevent(iml_session_t *s, int ch);

/* IF Method */
if_methods_t canna_methods = {
    if_canna_OpenIF,
    if_canna_CloseIF,
    if_canna_GetIFValue,
    if_canna_SetIFValue,

    if_canna_OpenDesktop,
    if_canna_CloseDesktop,

    if_canna_CreateSC,
    if_canna_DestroySC,
    if_canna_GetSCValue,
    if_canna_SetSCValue,
    if_canna_ResetSC,
    if_canna_SetSCFocus,
    if_canna_UnsetSCFocus,
    if_canna_SendEvent,
};

UTFCHAR lename_string[] = {0x304B, 0x3093, 0x306A, 0x4C, 0x45, 0x0};
UTFCHAR jahrn_string[] = {0x65E5, 0x672C, 0x8A9E, 0x0};
UTFCHAR lookup_choice_title[] = {0x5019, 0x88DC, 0x9078, 0x629E, 0x0};
char *class_names[] = {"com.OpenI18N.leif.CannaLE.menu"};

static IMLEName lename = {
    "CannaLE", lename_string	/* LE id, HRN */
};

static IMLocale locales[] = {
    {"ja", jahrn_string},	/* locale id, HRN */
    {NULL, NULL},
};

/*
  CSConv:
*/

static void
dlopen_csconv()
{
    csc_handle = dlopen(CSC_PATH, RTLD_LAZY);
    if (NULL == csc_handle) {
	csc_handle = (void *)(-1);
	return;
    }

    csc_open_locale = (csc_open_locale_t)dlsym(csc_handle, CSC_OPEN_LOCALE);
    csc_open = (csc_open_t)dlsym(csc_handle, CSC_OPEN);
    csc_conv = (csc_conv_t)dlsym(csc_handle, CSC_CONV);
    csc_close = (csc_close_t)dlsym(csc_handle, CSC_CLOSE);

    if ((NULL == csc_open_locale) || (NULL == csc_open) ||
	(NULL == csc_conv) || (NULL == csc_close)) {
	dlclose(csc_handle);
	csc_handle = (void *)(-1);
	return;
    }
}

static int
csconv_status()
{
    if ((csc_handle == (void *)(-1))
	|| (csconv_cd == (csconv_t)(-1)))
	return CSC_FAILED;
    else if (csc_handle == NULL)
	return CSC_UNLOADED;
    else if (csconv_cd == NULL)
	return CSC_UNOPENED;

    return CSC_LOADED;
}

static void
csconv_open_conv()
{
    /* Open UTF-16 => Canna Multibyte Format */
    if (csconv_status() == CSC_UNOPENED)
	csconv_cd = (csc_open_locale)(CANNA_CODESET_LOCALE,
				      "UTF-16",
				      "MultiByte");
    if (csconv_cd <= 0)
	fprintf(stderr, "CannaLE: Cannot Open csconv - %s\n",
		CANNA_CODESET_LOCALE);
}

static void
setup_csconv()
{
    if (csconv_status() == CSC_UNLOADED)
	dlopen_csconv();
    if (csconv_status() == CSC_UNOPENED)
	csconv_open_conv();

    return;
}

/*
  Desktop data:
 */

typedef struct
{
    int drop_priv;
} CannaLEDesktop;

/*
  Session data:
*/

static int canna_context_id_counter = 1;

typedef struct
{
    int context_id;
    jrKanjiStatusWithValue ksv;
    int conversion_start;
    int aux_start;
    int old_minor_mode;
    int update_state;
    int force_not_update_state;
} CannaLESession;

CannaLESession*
canna_session_data(iml_session_t *s)
{
    return (CannaLESession*)(s->specific_data);
}

CannaLEDesktop *
canna_desktop_data(iml_session_t *s)
{
    return (CannaLEDesktop *)(s->desktop->specific_data);
}

int
canna_session_context(iml_session_t *s)
{
    return ((CannaLESession*)(s->specific_data))->context_id;
}

jrKanjiStatusWithValue*
canna_session_status(iml_session_t *s)
{
    return &((CannaLESession*)(s->specific_data))->ksv;
}

/*
  AUX Object:
*/

IMObjectDescriptorStruct *objects = NULL;

void
init_objects()
{
    IMObjectDescriptorStruct *l;
    objects = (IMObjectDescriptorStruct *) calloc(2, sizeof (IMObjectDescriptorStruct));

    l = objects;

    l->leid = "CannaLE";
    l->type = IM_DOWNLOADINGOBJECT_BINGUI_TYPE;
    l->name = (UTFCHAR *)"menu";
    l->name_length = 10;
    l->domain = "com.OpenI18N.leif";
    l->scope = "CannaLE";
    l->path = "./locale/ja/CannaLE/aux.so";
    l->signature = "";
    l->basepath = NULL;
    l->encoding = NULL;
}

void
free_objects()
{
    IMObjectDescriptorStruct *l = objects;

    free(objects);
    objects = NULL;
}

/*
  Feedback operations:
*/

typedef enum {
    Canna_Feedback_Input,
    Canna_Feedback_Strong,
    Canna_Feedback_Normal,
}Canna_Feedback_Types;

IMFeedbackList *
create_feedback(iml_session_t *s, int size)
{
    int i;
    IMFeedbackList *feedback;
    IMFeedback *fb;
    
    if (!s) return NULL;

    feedback = ((IMFeedbackList *)
		s->If->m->iml_new(s, sizeof(IMFeedbackList) * size));
    for (i = 0; i < size; i++) {
	IMFeedbackList *fbl = &feedback[i];
	fbl->count_feedbacks = 1;
	fb = ((IMFeedback *) s->If->m->iml_new(s, sizeof(IMFeedback) * 4));
	fbl->feedbacks = fb;
	memset(fbl->feedbacks, 0, sizeof(IMFeedback) * 4);
    }
    return feedback;
}

IMFeedbackList *
create_feedback2(iml_session_t *s, int size)
{
    int i;
    IMFeedbackList *feedback;
    IMFeedback *fb;
    
    if (!s) return NULL;

    feedback = ((IMFeedbackList *)
		s->If->m->iml_new2(s, sizeof(IMFeedbackList) * size));
    for (i = 0; i < size; i++) {
	IMFeedbackList *fbl = &feedback[i];
	fbl->count_feedbacks = 1;
	fb = ((IMFeedback *) s->If->m->iml_new2(s, sizeof(IMFeedback) * 4));
	fbl->feedbacks = fb;
	memset(fbl->feedbacks, 0, sizeof(IMFeedback) * 4);
    }
    return feedback;
}

int
get_feedback(IMFeedbackList *fbl)
{
    /* returns IM_DECORATION_FEEDBACK */
    IMFeedback *fb = &fbl->feedbacks[0];
    return IM_FEEDBACK_VALUE(fb);
}

void
set_feedback_private(
    IMFeedbackList * fbl,
    int normalfeedback,
    int fg,
    int bg,
    int underline
)
{
    int count = 0;
    IMFeedback *fb;
    
    fb = &fbl->feedbacks[count];
    IM_FEEDBACK_TYPE(fb) = IM_DECORATION_FEEDBACK;
    IM_FEEDBACK_VALUE(fb) = normalfeedback;
    count++;
    
#ifdef	USE_COLOR_FEEDBACK

    if (fg != -1) {
	fb = &fbl->feedbacks[count];
	IM_FEEDBACK_TYPE(fb) = IM_FOREGROUND_RGB_FEEDBACK;
	IM_FEEDBACK_VALUE(fb) = fg;
	count++;
    }
    if (bg != -1) {
	fb = &fbl->feedbacks[count];
	IM_FEEDBACK_TYPE(fb) = IM_BACKGROUND_RGB_FEEDBACK;
	IM_FEEDBACK_VALUE(fb) = bg;
	count++;
    }
    if (underline != -1) {
	fb = &fbl->feedbacks[count];
	IM_FEEDBACK_TYPE(fb) = IM_UNDERLINE_RGB_FEEDBACK;
	IM_FEEDBACK_VALUE(fb) = underline;
	count++;
    }

#endif
    IM_FEEDBACK_COUNT(fbl) = count;
}

void
set_canna_feedback_1(IMFeedbackList *fbl, int feedback_type)
{

    switch(feedback_type) {
    case Canna_Feedback_Input:
	set_feedback_private(fbl,
			     IMUnderline,
			     IM_RGB_COLOR(0, 0, 255),	  /* FG: blue */
			     IM_RGB_COLOR(255, 255, 255), /* BG: white */
			     1);                          /* Underline */
	break;
    case Canna_Feedback_Strong:
	set_feedback_private(fbl,
			     IMReverse,
			     IM_RGB_COLOR(255, 255, 255), /* FG: white */
			     IM_RGB_COLOR(0, 0, 255),     /* BG: blue */
			     -1);
	break;
    case Canna_Feedback_Normal:
    default:
	set_feedback_private(fbl, IMNormal, -1, -1, -1);
	break;
    }
    return;
	
}

void
set_canna_feedback(IMFeedbackList *fbl, int feedback_type,
		   int st, int end)
{
    for (;st < end;st++)
	set_canna_feedback_1((fbl + st), feedback_type);
    return;
}


IMFeedbackList *
create_canna_feedback(iml_session_t *s,
		      int size,
		      int normalfeedback,
		      int fg,
		      int bg)
{
    int i;
    IMFeedbackList *feedback;

    feedback = (IMFeedbackList *) create_feedback(s, size);
    for (i = 0; i < size; i++) {
	IMFeedbackList *fbl = &feedback[i];
	set_feedback_private(fbl, normalfeedback, fg, bg, -1);
    }

    return feedback;
}

/*
  IMText operations:
*/

IMText*
create_IMText(iml_session_t *s, int len)
{
    IMText *p;
    
    if (!s) return NULL;

    p = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    memset(p, 0, sizeof(IMText));
    p->encoding = UTF16_CODESET;
    p->text.utf_chars = ((UTFCHAR *)
			 s->If->m->iml_new(s, sizeof(UTFCHAR) * (len + 1)));
    p->char_length = len;
    p->feedback = create_feedback(s, len);

    return p;
}

IMText*
UTFCHAR_to_IMText(iml_session_t *s, UTFCHAR *p)
{
    IMText *pit;
    UTFCHAR *p2 = p;
    int len;

    for (len = 0;*p2;p2++) len++;
    pit = create_IMText(s, len);
    if (!pit) return NULL;
    memcpy(pit->text.utf_chars, p, (len + 1) * sizeof(UTFCHAR));

    return pit;
}

/*
  String conversion:
*/

size_t 
UTFCHAR_buffer_size(size_t canna_str_len)
{
    return canna_str_len * sizeof (UTFCHAR);
}

UTFCHAR*
canna_string_to_UTFCHAR(unsigned char *str)
{
    size_t ret, clen, ulen;
    UTFCHAR *p, *ustr;

    clen = strlen(str);
    ulen = UTFCHAR_buffer_size(clen + 1);
    ustr = p = (UTFCHAR*) malloc(ulen);

    ret = csc_conv(csconv_cd,
		   (const char **)&str, &clen,
		   (char **) &p, &ulen);
    if (ret != clen) return NULL;
    *p = 0;
    return ustr;
}

IMText*
canna_string_to_IMText(iml_session_t *s,
		       int nseg, int *nb,
		       unsigned char **strs,
		       int *feedback_type)
{
    UTFCHAR *ustr = NULL, *p;
    IMText *pit;
    char *from;
    size_t ret, from_size, ulen, clen;
    int i, uidx;
    int *idices = NULL;

    clen = 0;
    for (i = 0;i < nseg;i++) {
	clen += nb[i];
    }
    ulen = UTFCHAR_buffer_size(clen + 1);
    ustr = p = (UTFCHAR*) malloc(sizeof(UTFCHAR) * ulen);
    idices = (int*) malloc(sizeof(int) * (nseg + 1));

    for (i = 0;i < nseg;i++) {
	from = strs[i];
	from_size = nb[i];
	idices[i] = p - ustr;
	ret = csc_conv(csconv_cd,
		       (const char **)&from, &from_size,
		       (char **) &p, &ulen);
    }
    *p = 0;
    uidx = p - ustr;
    idices[nseg] = uidx;
    pit = create_IMText(s, uidx);
    if (pit == NULL)
	goto ensure;
    memcpy(pit->text.utf_chars, ustr, (p - ustr + 1) * sizeof(UTFCHAR));

    if (feedback_type) {
	/* set feedback */
	for (i = 0;i < nseg;i++) {
	    set_canna_feedback(pit->feedback, feedback_type[i],
			       idices[i], idices[i + 1]);
	}
    }
ensure:
    if (ustr != NULL)
	free(ustr);
    if (idices != NULL)
	free(idices);

    return pit;
}

/*
  LEIF operations.
*/

static void
send_commit(iml_session_t *s, IMText *p, int executep)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;

    lp = s->If->m->iml_make_commit_inst(s, p);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    if (executep) {
	s->If->m->iml_execute(s, &rrv);
    }
    return;
}

/*
  Canna operations.
*/

static char*
canna_init_filename(char *user)
{
    char *buf;
    int ipsize;
    struct passwd *pw;

    if (!user)
	return NULL;
    setpwent();
    if ((pw = getpwnam(user)) == NULL) {
	endpwent();
	return NULL;
    }
    ipsize = strlen(pw->pw_dir);
    buf = (char*) malloc((ipsize + 2) * sizeof(char)
			 + sizeof(canna_le_init_filename));
    if (ipsize < 1) return NULL;
    strcpy(buf, pw->pw_dir);
    buf[ipsize] = '/';
    buf[ipsize + 1] = '\0';
    strcat(buf, canna_le_init_filename);

    endpwent();

    /* check whether the file is readable. */
    if (access(buf, R_OK) != 0) {
	free(buf);
	return NULL;
    }

    return buf;
}

Bool
canna_init(iml_session_t *s, char *user)
{
    char **warning = NULL;
    char *init_filename;

    init_filename = canna_init_filename(user);
    if (init_filename) {
	jrKanjiControl(canna_session_context(s), KC_SETINITFILENAME, init_filename);
	free(init_filename);
    }
    jrKanjiControl(canna_session_context(s), KC_INITIALIZE, (char *) &warning);

    if (warning) {
	char          **p;

	for (p = warning; *p; p++)
	    fprintf(stderr, "CannaLE: %s\n", *p);

	return False;
    }

    jrKanjiControl(canna_session_context(s), KC_SETAPPNAME, (char *) "CannaLE");

    /* set user info */
    if (user) {
	jrUserInfoStruct info;

	memset(&info, 0, sizeof (info));
	info.uname = user;
	jrKanjiControl(canna_session_context(s), KC_SETUSERINFO, (char *)&info);
    }

    return True;
}

IMText*
canna_commit_string(iml_session_t *s, char *buf)
{
    IMText *p;
    int len = strlen(buf);
    
    p = canna_string_to_IMText(s, 1, &len, (unsigned char**) &buf, NULL);
    return p;
}

IMText*
canna_kakutei(iml_session_t *s)
{
    jrKanjiStatusWithValue *pksv;

    pksv = canna_session_status(s);
    jrKanjiControl(canna_session_context(s), KC_KAKUTEI, (char*) pksv);
    return canna_commit_string(s, pksv->buffer);
}

void
canna_status_draw(iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p;
    CannaLESession *pcls = canna_session_data(s);
    unsigned char *str;
    int len;

    jrKanjiStatusWithValue *pksv;
    pksv = canna_session_status(s);

    if (pcls->conversion_start == False) {
	canna_status_draw_off(s);
	return;
    }
    len = jrKanjiControl(canna_session_context(s), KC_QUERYMAXMODESTR, 0);
    str = (unsigned char *) malloc(sizeof (unsigned char) * (len + 1));
    jrKanjiControl(canna_session_context(s), KC_QUERYMODE, str);

    {
	/* Create IMText with feedback. */
	int ft1;
	ft1 = Canna_Feedback_Normal;
	p = canna_string_to_IMText(s, 1, &len, &str, &ft1);
    }
    free(str);

    if (!IS_REGION_ACTIVE(s, STATUS)) {
        lp = s->If->m->iml_make_status_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
    }
    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
canna_preedit_draw(iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p;
    jrKanjiStatus *pks = canna_session_status(s)->ks;

    /* When KanjiStatus is uninited, return immediately. */
    if (!pks->echoStr) return;

    if (!IS_REGION_ACTIVE(s, PREEDIT)) {
        lp = s->If->m->iml_make_preedit_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
    }

    {
	/* Create IMText with feedbacks.  */
	int nb[3], fts[3];
	unsigned char *strs[3];
	nb[0] = pks->revPos;
	nb[1] = pks->revLen;
	nb[2] = pks->length - nb[0] - nb[1];
	fts[0] = Canna_Feedback_Input;
	fts[1] = Canna_Feedback_Strong;
	fts[2] = Canna_Feedback_Input;
	strs[0] = pks->echoStr;
	strs[1] = strs[0] + pks->revPos;
	strs[2] = strs[1] + pks->revLen;
	p = canna_string_to_IMText(s, 3, nb, strs, fts);
    }

    lp = s->If->m->iml_make_preedit_draw_inst(s, p);
    s->If->m->iml_link_inst_tail(&rrv, lp);
    s->If->m->iml_execute(s, &rrv);
}

void
canna_aux_start(iml_session_t *s)
{
    CannaLESession *pcls = canna_session_data(s);
    /*
     * IS_REGION_ACTIVE seems not working for AUX
    if (!IS_REGION_ACTIVE(s, AUX)) {
    */
    if (pcls->aux_start == False) {
	IMAuxStartCallbackStruct *aux;
	iml_inst *lp;

#ifdef DEBUG
	printf("DEBUG: %s activating.\n", __FUNCTION__);
#endif

	aux = (IMAuxStartCallbackStruct *)s->If->m->iml_new(s, sizeof (IMAuxStartCallbackStruct));
	memset(aux, 0, sizeof (IMAuxStartCallbackStruct));
	aux->aux_name = class_names[0];
	lp = s->If->m->iml_make_aux_start_inst(s, aux);
	s->If->m->iml_execute(s, &lp);

	pcls->aux_start = True;
    }
}

void
canna_aux_draw(iml_session_t *s, int auxmode, void *data, ...)
{
    va_list args;
    int i, j, num = 0, *segs = NULL, cid, int_data[INT_END], mode, mmode;
    unsigned char **ps = NULL, **pstr = NULL;
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    CannaLESession *pcls = canna_session_data(s);

    ENTER;

    va_start(args, data);

    int_data[INT_AUXMODE] = auxmode;
    switch (auxmode) {
	case AUX_UPDATE_STATE:
	    mode = canna_get_current_mode(s);
	    mmode = canna_get_current_minor_mode(s);
#ifdef DEBUG
	    printf("*** mode = %d, mmode = %d, old_minor = %d\n", mode, mmode, pcls->old_minor_mode);
#endif
	    if (pcls->force_not_update_state == 1) {
		pcls->force_not_update_state = 0;
		goto ensure;
	    }
	    if (mode != CANNA_MODE_ExtendMode &&
		mode != CANNA_MODE_HexMode &&
		mode != CANNA_MODE_KigoMode) {
		int_data[INT_STATE] = mode;
		if (!pks->gline.line)
		    goto ensure;
		if (!canna_parse_guideline(s, &num, &ps, &segs, &cid))
		    goto ensure;
		if (num == 0 && pks->gline.line != NULL && pks->gline.length > 0) {
		    num = 1;
		    pstr = (unsigned char **)malloc(sizeof (char *) * (num + 1));
		    pstr[0] = (char *)malloc(sizeof (char *) * (pks->gline.length + 1));
		    for (i = 0, j = 0; i < pks->gline.length; i++) {
			if (pks->gline.line[i] == 0)
			    continue;
			pstr[0][j++] = pks->gline.line[i];
		    }
		    pstr[0][j] = 0;
		} else {
		    num = 0;
		}
		pcls->old_minor_mode = mmode;
	    } else {
		if (mmode != CANNA_MODE_ExtendMode &&
		    mmode != CANNA_MODE_TourokuMode &&
		    mmode != CANNA_MODE_DeleteDicMode &&
		    mmode != CANNA_MODE_TourokuHinshiMode &&
		    mmode == pcls->old_minor_mode) {
		    /* don't send this event */
		    pcls->old_minor_mode = mmode;
		    if (pcls->update_state == 0)
			goto ensure;
		    pcls->update_state = 0;
		} else {
		    if (((mmode == CANNA_MODE_EmptyMode ||
			 mmode == CANNA_MODE_TankouhoMode) &&
			 pcls->old_minor_mode == CANNA_MODE_TourokuMode) ||
			(mmode == CANNA_MODE_EmptyMode &&
			 pcls->old_minor_mode == CANNA_MODE_DeleteDicMode)) {
			/* don't hide the aux window */
			mmode = pcls->old_minor_mode;
		    } else if (mmode == CANNA_MODE_IchiranMode &&
			       pcls->old_minor_mode == CANNA_MODE_TourokuMode) {
			/* don't update the aux window either */
			goto ensure;
		    } else {
			pcls->old_minor_mode = mmode;
		    }
		    pcls->update_state = 0;
		}
		int_data[INT_STATE] = mmode;
		int_data[INT_CURPOS] = canna_get_current_candidate_position(s);
		int_data[INT_CANDCOUNT] = canna_get_candidate_count(s);

		/*
		 * ensure that current cursor position is the beginning of
		 * items in the server after storing current position.
		 */
		/*
		 * CAUTION!! don't send invalid key for the current state.
		 * it causes the status string breakage.
		 */
		if (mmode != CANNA_MODE_TourokuMode &&
		    int_data[INT_CANDCOUNT] > 0) {
		    char buf[CANNA_COMMIT_STRING_BUFSIZE + 1];
		    int size = CANNA_COMMIT_STRING_BUFSIZE;

		    jrKanjiString(canna_session_context(s),
				  CANNA_KEY_Home, buf, size, pks);
		}
		int_data[INT_BEGINPOS] = canna_get_current_candidate_position(s);

#ifdef DEBUG
		printf("*** pos %d(%d)/%d(%d)\n",
		       int_data[INT_BEGINPOS],
		       canna_get_current_candidate_position(s),
		       int_data[INT_CURPOS],
		       canna_get_candidate_count(s),
		       int_data[INT_CANDCOUNT]);
#endif

		switch (mmode) {
		    case CANNA_MODE_HexMode:
		    case CANNA_MODE_ChangingServerMode:
		    case CANNA_MODE_TourokuMode:
		    message:
			if (!pks->gline.line)
			    goto ensure;

			/* DEBUG --> */
			if (!canna_parse_guideline(s, &num, &ps, &segs, &cid))
			    goto ensure;
			/* <-- DEBUG */

			num = 1;
			pstr = (unsigned char **)malloc(sizeof (char *) * (num + 1));
			pstr[0] = (char *)malloc(sizeof (char *) * (pks->gline.length + 1));
			/*
			 * sigh, need to skip null characters and copies the readable characters to
			 * the buffer.
			 */
			for (i = 0, j = 0; i < pks->gline.length; i++) {
			    if (pks->gline.line[i] == 0)
				continue;
			    pstr[0][j++] = pks->gline.line[i];
			}
			pstr[0][j] = 0;
			break;
		    case CANNA_MODE_TourokuHinshiMode:
		    case CANNA_MODE_DeleteDicMode:
		    case CANNA_MODE_TourokuDicMode:
			if (int_data[INT_CANDCOUNT] == 0)
			    goto message;
		    case CANNA_MODE_ExtendMode:
		    case CANNA_MODE_MountDicMode:
			if (!pks->gline.line)
			    goto ensure;
			if (!canna_parse_guideline(s, &num, &ps, &segs, &cid))
			    goto ensure;
			num *= 2;
			pstr = (unsigned char **)malloc(sizeof (char *) * (num + 1));
			for (i = 0; i < num; i++) {
			    pstr[i] = (char *)malloc(sizeof (char) * (segs[i] + 1));
			    strncpy(pstr[i], ps[i], segs[i]);
			    pstr[i][segs[i]] = 0;
			}
			break;
		    default:
			num = 0;
			break;
		}
	    }
	    break;
	case AUX_UPDATE_KEY:
	    {
		IMKeyListEvent *kev = (IMKeyListEvent *)data;
		IMKeyEventStruct *k = (IMKeyEventStruct *)kev->keylist;
		int ch = va_arg(args, int);

		int_data[INT_STATE] = canna_get_current_minor_mode(s);
		int_data[INT_KEYCODE] = k->keyCode;
		int_data[INT_KEYMOD] = k->modifier;
		int_data[INT_TRANSLATEDCODE] = ch;
		int_data[INT_CANDCOUNT] = canna_get_candidate_count(s);
	    }
	    break;
	case AUX_FORCE_UPDATE_STATE:
	    pcls->update_state = 1;
	    canna_aux_draw(s, AUX_UPDATE_STATE, NULL);
	    goto ensure;
	case AUX_FORCE_NOT_UPDATE_STATE:
	    pcls->force_not_update_state = 1;
	    goto ensure;
	case AUX_FOCUS_CHANGE:
	    int_data[INT_STATE] = (int)data;
	    if (int_data[INT_STATE] == 0) {
		/* going to unfocus */
		canna_aux_draw(s, AUX_FORCE_SAVE_STATE, NULL);
	    } else {
		/* going to focus */
		canna_aux_draw(s, AUX_FORCE_UPDATE_STATE, NULL);
	    }
	    break;
	case AUX_FORCE_SAVE_STATE:
	    int_data[INT_STATE] = pcls->old_minor_mode;
	    break;
    }

    aux_draw(s, INT_END, int_data, num, pstr);

ensure:
    for (i = 0; i < num; i++) {
	if (pstr[i] != NULL)
	    free(pstr[i]);
    }
    if (pstr != NULL)
	free(pstr);
    if (ps != NULL)
	free(ps);
    if (segs != NULL)
	free(segs);
    va_end(args);

    LEAVE;
}

void
aux_draw(iml_session_t  *s,
	 int             count_integers,
	 int            *integers,
	 int             count_strings,
	 unsigned char **strings)
{
    iml_inst *lp;
    IMAuxDrawCallbackStruct *auxdraw;
    int i;
    size_t len = 7;
    IMText *Its, *It;
    CannaLESession *pcls = canna_session_data(s);

    /*
     * IS_REGION_ACTIVE seems to not working for AUX
    if (!IS_REGION_ACTIVE(s, AUX)) {
    */
    if (pcls->aux_start == False) {
	/* canna_aux_start() should be called before calling aux_draw() */
	return;
    }

    auxdraw = (IMAuxDrawCallbackStruct *)s->If->m->iml_new(s, sizeof (IMAuxDrawCallbackStruct));
    memset(auxdraw, 0, sizeof (IMAuxDrawCallbackStruct));
    auxdraw->aux_name = class_names[0];
    auxdraw->count_integer_values = count_integers;
    if (count_integers) {
	auxdraw->integer_values = (int *)s->If->m->iml_new(s, sizeof (int) * count_integers);
	for (i = 0; i < count_integers; i++) {
	    auxdraw->integer_values[i] = integers[i];
	}
    }

    auxdraw->count_string_values = count_strings;
    if (count_strings > 0) {
	auxdraw->string_values = Its = (IMText *)s->If->m->iml_new(s, sizeof (IMText) * count_strings);

	for (i = 0, It = Its; i < count_strings; i++, It++) {
	    memset(It, 0, sizeof (IMText));
	    It->encoding = UTF16_CODESET;
	    len = UTFCHAR_buffer_size(strlen(strings[i]) + 1);
	    It->text.utf_chars = canna_string_to_UTFCHAR(strings[i]);
	    It->char_length = len;

#ifdef DEBUG
	    printf("*** '%s'\n", strings[i]);
	    printf("*** '%s' (%d)\n", It->text.utf_chars, len);
#endif
	}
    }

    lp = s->If->m->iml_make_aux_draw_inst(s, auxdraw);
    s->If->m->iml_execute(s, &lp);
}

void
canna_status_draw_off(iml_session_t *s)
{
    IMText *p;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    unsigned char *str = "";
    int len = strlen(str);
    int ft1 = Canna_Feedback_Normal;

    p = canna_string_to_IMText(s, 1, &len, &str, &ft1);
    if (!IS_REGION_ACTIVE(s, STATUS)) {
	lp = s->If->m->iml_make_status_start_inst(s);
	s->If->m->iml_link_inst_tail(&rrv, lp);
    }
    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, &lp);
}

void
canna_preedit_done(iml_session_t *s)
{
    if (IS_REGION_ACTIVE(s, PREEDIT)) {
	iml_inst *lp;
        lp = s->If->m->iml_make_preedit_done_inst(s);
	s->If->m->iml_execute(s, &lp);
    }
}

void
canna_aux_done(iml_session_t *s)
{
    CannaLESession *pcls = canna_session_data(s);

    /* hide aux window */
    canna_aux_draw(s, AUX_UPDATE_STATE, NULL);
    /*
     * IS_REGION_ACTIVE seems to not working for AUX
    if (IS_REGION_ACTIVE(s, AUX)) {
    */
    if (pcls->aux_start == True) {
	iml_inst *lp;
	IMAuxDoneCallbackStruct *aux;

#ifdef DEBUG
	printf("DEBUG: %s is deactivating\n", __FUNCTION__);
#endif

	aux = (IMAuxDoneCallbackStruct *)s->If->m->iml_new(s, sizeof (IMAuxDoneCallbackStruct));
	memset(aux, 0, sizeof (IMAuxDoneCallbackStruct));
	aux->aux_name = class_names[0];
	lp = s->If->m->iml_make_aux_done_inst(s, aux);
	s->If->m->iml_execute(s, &lp);

	pcls->aux_start = False;
    }
}

/*
  Caution!!!
  This part assumes the structure of guidline given by canna UI
  library.  It parses guideline string without any protocol on it.
  Therefore, it may not work with the future version of canna
  UI library!
*/
Bool
canna_parse_guideline(iml_session_t *s, int *pnum,
		      unsigned char ***psegs,
		      int **pnb,
		      int *pcurrent)
{
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    unsigned char *str = pks->gline.line;
    unsigned char *p, *st;
    int i, idx, tot, delimiterp;

#ifdef DEBUG
    char linestr[1024];

    printf("*** length = %d\n", pks->gline.length);
    printf("*** revPos = %d\n", pks->gline.revPos);
    printf("*** revLen = %d\n", pks->gline.revLen);
    memcpy(linestr, pks->gline.line, (pks->gline.length > 1024 ? 1024 : pks->gline.length));
    printf("*** line = '%s'\n", linestr);
    printf("*** line(Hex) = '");
    for (i = 0; i < pks->gline.length; i++) {
	printf("%02X:", linestr[i] & 0xFF);
    }
    printf("\n");
#endif
    tot = 0;
    for (p = str, st = NULL;*p;p = CANNA_NEXT_CHAR(p)) {
	delimiterp = CANNA_GUIDELINE_DELIMITER_P(p);
	if (st && delimiterp) {
	    tot++;
	    st = NULL;
	}else if (!st && !delimiterp) {
	    st = p;
	}
    }
    *pnum = tot;
    *pcurrent = 0;
    *psegs = (unsigned char**) malloc(sizeof(unsigned char*) * tot * 2);
    *pnb = (int*) malloc(sizeof(int) * tot * 2);
    for (p = str, i = 0, idx = 0, st = NULL;
	 (idx < tot);p = CANNA_NEXT_CHAR(p)) {
	delimiterp = CANNA_GUIDELINE_DELIMITER_P(p);
	if (st && delimiterp) {
	    /* the size of the value */
	    (*pnb)[i] = (p - st);
	    i++;
	    idx++;
	    st = NULL;
	}else if (!st && !delimiterp) {
	    /* label */
	    (*psegs)[i] = st = p;
	    (*pnb)[i] = CANNA_NEXT_CHAR(p) - p;
	    i++;
	    if (pks->gline.revPos == (p - str))
		*pcurrent = idx;
	    /* value */
	    (*psegs)[i] = st = CANNA_NEXT_CHAR(p);
	}
    }
#ifdef DEBUG
    for (i = 0;i < (tot * 2);i++) {
	memcpy(linestr, (*psegs)[i], (*pnb)[i]);
	linestr[(*pnb)[i]] = '\0';
	fprintf(stderr, "Seg(%d):'%s'\n", i, linestr);
    }
#endif
    return True;
}

int
canna_get_current_candidate_position(iml_session_t *s)
{
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    unsigned char *str = pks->gline.line;
    unsigned char *p, linestr[1024];
    int i;

    if ((p = rindex(str, '/')) == NULL) {
	return 0;
    }
    i = 0;
    while (*p--) {
	if (*p >= '0' && *p <= '9') {
	    i++;
	} else {
	    p++;
	    break;
	}
    }
    strncpy(linestr, p, i);
    linestr[i] = 0;

    return atoi(linestr);
}

int
canna_get_candidate_count(iml_session_t *s)
{
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    unsigned char *str = pks->gline.line;
    unsigned char *p, linestr[1024];
    int i;

    if ((p = rindex(str, '/')) == NULL) {
	return 0;
    }
    i = 0;
    while (*p++) {
	if (*p >= '0' && *p <= '9') {
	    linestr[i] = *p;
	    i++;
	} else {
	    break;
	}
    }
    linestr[i] = 0;

    return atoi(linestr);
}

void
canna_start_lookup_choice(iml_session_t *s,
			  iml_inst **prrv, int num)
{
    if (!IS_REGION_ACTIVE(s, LOOKUP)) {
	iml_inst *lp;
	IMLookupStartCallbackStruct *start;
        start = ((IMLookupStartCallbackStruct *)
		 s->If->m->iml_new(s, 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 = num;
        start->IMPreference->ncolumns = 1;
        start->IMPreference->nrows = num;
        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(prrv, lp);
    }
}

void
canna_show_lookup_choice(iml_session_t *s)
{
    int num;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMLookupDrawCallbackStruct *draw;
    jrKanjiStatus *pks = canna_session_status(s)->ks;

    /* When KanjiStatus is uninited, return immediately. */
    if (!pks->gline.line) return;

    draw = ((IMLookupDrawCallbackStruct *)
	    s->If->m->iml_new(s, sizeof(IMLookupDrawCallbackStruct)));
    memset(draw, 0, sizeof(IMLookupDrawCallbackStruct));
    draw->title = UTFCHAR_to_IMText(s, lookup_choice_title);

    /* set choices */
    {
	int i, cid;
	unsigned char **ps;
	int *segs;
        IMText *pvt;
        IMText *plt;
	int max_len = 0;
	
	if (!canna_parse_guideline(s, &num, &ps, &segs, &cid))
	    return;

	if (num <= 0) {
	    free(ps);
	    free(segs);
	    return;
	}
	draw->index_of_first_candidate = 0;
	draw->index_of_last_candidate = num - 1;
	draw->n_choices = num;
	draw->choices = ((IMChoiceObject *)
			 s->If->m->iml_new(s, num * sizeof(IMChoiceObject)));
	memset(draw->choices, 0, num * sizeof(IMChoiceObject));
	draw->index_of_current_candidate = cid;

	for (cid = 0, i = 0;i < num;i++) {
	    plt = draw->choices[i].label
		= canna_string_to_IMText(s, 1, &segs[cid], &ps[cid], NULL);
	    cid++;
	    pvt = draw->choices[i].value
		= canna_string_to_IMText(s, 1, &segs[cid], &ps[cid], NULL);
	    cid++;
	    if (max_len < pvt->char_length)
		max_len = pvt->char_length;
	    if (max_len < plt->char_length)
		max_len = plt->char_length;
	}
	free(ps);
	free(segs);
	draw->max_len = max_len;
#if 0
	fprintf(stderr, "draw->index_of_first_candidate=%x\n",
		draw->index_of_first_candidate);
	fprintf(stderr, "draw->index_of_last_candidate=%x\n",
		draw->index_of_last_candidate);
	fprintf(stderr, "draw->n_choices=%x\n",
		draw->n_choices);
	fprintf(stderr, "draw->choices=%x\n",
		draw->choices);
	fprintf(stderr, "draw->choices->label=%x\n",
		draw->choices->label);
	fprintf(stderr, "draw->max_len=%x\n", max_len);
	fprintf(stderr, "draw->index_of_current_candidate=%x\n",
		draw->index_of_current_candidate);
#endif
    }
    canna_start_lookup_choice(s, &rrv, num);
    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);
}

void
canna_lookup_choice_done(iml_session_t *s)
{
    if (IS_REGION_ACTIVE(s, LOOKUP)) {
	iml_inst *lp;
        lp = s->If->m->iml_make_lookup_done_inst(s);
	s->If->m->iml_execute(s, &lp);
    }
}

void
canna_make_conversion_off(iml_session_t *s)
{
    CannaLESession *pcls = canna_session_data(s);

    if (pcls->conversion_start == True) {
	pcls->conversion_start = False;
	canna_change_mode(s, CANNA_MODE_AlphaMode);
	canna_status_draw(s);
	canna_status_draw_off(s);
	canna_lookup_choice_done(s);
	canna_preedit_done(s);
	canna_aux_done(s);
    }
}

void
canna_make_conversion_on(iml_session_t  *s)
{
    CannaLESession *pcls = canna_session_data(s);

    if (pcls->conversion_start == False) {
	pcls->conversion_start = True;
	if (canna_get_current_mode(s) == CANNA_MODE_AlphaMode) {
	    canna_change_mode(s, CANNA_MODE_HenkanMode);
	}
	canna_status_draw(s);
	canna_aux_start(s);
    }
}

int
canna_translate_keyevent(IMKeyListEvent *kev)
{
    IMKeyEventStruct *k = (IMKeyEventStruct *) kev->keylist;
    fprintf(stderr, "iml_session_t() keycode=%x,keychar=%x, state=%x\n",
	    k->keyCode, k->keyChar, k->modifier);

    switch(k->keyCode) {
    case IM_VK_UP:
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Up;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Up;
	}
	return  CANNA_KEY_Up;

    case IM_VK_DOWN:
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Down;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Down;
	}
	return  CANNA_KEY_Down;

    case IM_VK_LEFT:
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Left;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Left;
	}
	return  CANNA_KEY_Left;

    case IM_VK_RIGHT:
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Right;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Right;
	}
	return  CANNA_KEY_Right;

    case IM_VK_INSERT:
	return CANNA_KEY_Insert;

    case IM_VK_PAGE_UP:
	return CANNA_KEY_Rolldown;

    case IM_VK_PAGE_DOWN:
	return CANNA_KEY_Rollup;

    case IM_VK_HOME:
	return CANNA_KEY_Home;

    case IM_VK_HELP:
	return CANNA_KEY_Help;

    case IM_VK_F1:
    case IM_VK_F2:
    case IM_VK_F3:
    case IM_VK_F4:
    case IM_VK_F5:
    case IM_VK_F6:
    case IM_VK_F7:
    case IM_VK_F8:
    case IM_VK_F9:
    case IM_VK_F10:
	return CANNA_KEY_F1 + k->keyCode - IM_VK_F1;

    case IM_VK_CONVERT: /* XFER */
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Xfer;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Xfer;
	}
	return CANNA_KEY_Xfer;

    case IM_VK_NONCONVERT: /* NFER */
	if (k->modifier & IM_CTRL_MASK) {
	    return CANNA_KEY_Cntrl_Nfer;
	} else if (k->modifier & IM_SHIFT_MASK) {
	    return CANNA_KEY_Shift_Nfer;
	}
	return CANNA_KEY_Nfer;

    case IM_VK_ENTER:
	return 0x0D;

    case IM_VK_BACK_SPACE:
	return 0x08;

    case IM_VK_DELETE:
	return 0x04;

    case IM_VK_CLEAR:
	return 0x0B;

    case IM_VK_PAUSE:
	return 0x13;

    case IM_VK_SCROLL_LOCK:
	return 0x14;

    case IM_VK_ESCAPE:
	return 0x1B;

    default:
	if (k->modifier & IM_CTRL_MASK) {
	    if (k->keyCode >= IM_VK_A && k->keyCode <= IM_VK_CLOSE_BRACKET) {
		return 0x01 + k->keyCode - IM_VK_A;
	    }
	    if (k->keyCode == IM_VK_CIRCUMFLEX) {
		return 0x1E;
	    }
	    if (k->keyCode == IM_VK_SLASH) {
		return 0x1F;
	    }
	    /* no process any Ctrl characters here */
	    break;
	}
	if ((k->keyChar > 0)
	    && (k->keyChar < 0xFFFF)) {
	    /* Should we translate it to EUC? */
	    return k->keyChar;
	}
    }

    fprintf(stderr, "translation failed:keycode=%x,keychar=%x, state=%x\n",
	    k->keyCode, k->keyChar, k->modifier);
    return 0;
}

static Bool
process_keyevent(iml_session_t *s, int ch)
{
    int size = CANNA_COMMIT_STRING_BUFSIZE, n;
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    char buf[CANNA_COMMIT_STRING_BUFSIZE + 1];
    int mode;

    n = jrKanjiString(canna_session_context(s),
		      ch, buf, size, pks);
    buf[n] = '\0';

    /* if there is no preedit characters and no related characters,
     * shouldn't process it.
     */
    if (n == 1 && (pks->info & KanjiThroughInfo) && pks->length == 0) {
	pks->info &= ~KanjiThroughInfo;
	return False;
    }
    if (n > 0) {
	IMText *p;

	pks->info &= ~KanjiThroughInfo;
	p = canna_commit_string(s, buf);
	send_commit(s, p, 1);
    }
    if (pks->length >= 0)
	canna_preedit_draw(s);
    if (pks->info & KanjiModeInfo)
	canna_status_draw(s);

    /* always call canna_aux_draw() to hide the aux window even if it's not necessary */
    canna_aux_draw(s, AUX_UPDATE_STATE, NULL);
    mode = canna_get_current_mode(s);
    if (mode == CANNA_MODE_ExtendMode || mode == CANNA_MODE_HexMode) {
	switch (canna_get_current_minor_mode(s)) {
	    case CANNA_MODE_ExtendMode:
	    case CANNA_MODE_HexMode:
	    case CANNA_MODE_TourokuMode:
	    case CANNA_MODE_MountDicMode:
	    case CANNA_MODE_ChangingServerMode:
	    case CANNA_MODE_TankouhoMode:
	    case CANNA_MODE_TourokuHinshiMode:
	    case CANNA_MODE_DeleteDicMode:
	    case CANNA_MODE_TourokuDicMode:
		/* don't use lookup */
		/* enforce to close the lookup window, when the state gets back from the lookup window say */
		canna_lookup_choice_done(s);
		break;
	    default:
		goto use_lookup;
	}
    } else {
use_lookup:
	if (pks->info & KanjiGLineInfo) {
	    if (pks->gline.length > 0)
		canna_show_lookup_choice(s);
	    else
		canna_lookup_choice_done(s);
	}
    }

    return True;
}

static int
canna_swap_keyevent(iml_session_t *s, int ch)
{
    int mode = canna_get_current_minor_mode(s);
    int flag = 0;
    int retval = ch;

    switch (mode) {
	case CANNA_MODE_KigoMode:
	case CANNA_MODE_IchiranMode:
	case CANNA_MODE_BushuMode:
	case CANNA_MODE_RussianMode:
	case CANNA_MODE_GreekMode:
	case CANNA_MODE_LineMode:
	    flag = 1;
	    break;
    }
    if (flag == 1) {
	switch (ch) {
	    case CANNA_KEY_Up:
		retval = CANNA_KEY_Left;
		break;
	    case CANNA_KEY_Shift_Up:
		retval = CANNA_KEY_Shift_Left;
		break;
	    case CANNA_KEY_Cntrl_Up:
		retval = CANNA_KEY_Cntrl_Left;
		break;
	    case CANNA_KEY_Down:
		retval = CANNA_KEY_Right;
		break;
	    case CANNA_KEY_Shift_Down:
		retval = CANNA_KEY_Shift_Right;
		break;
	    case CANNA_KEY_Cntrl_Down:
		retval = CANNA_KEY_Cntrl_Right;
		break;
	    case CANNA_KEY_Left:
		retval = CANNA_KEY_Up;
		break;
	    case CANNA_KEY_Shift_Left:
		retval = CANNA_KEY_Shift_Up;
		break;
	    case CANNA_KEY_Cntrl_Left:
		retval = CANNA_KEY_Cntrl_Up;
		break;
	    case CANNA_KEY_Right:
		retval = CANNA_KEY_Down;
		break;
	    case CANNA_KEY_Shift_Right:
		retval = CANNA_KEY_Shift_Down;
		break;
	    case CANNA_KEY_Cntrl_Right:
		retval = CANNA_KEY_Cntrl_Down;
		break;
	}
    }

    return retval;
}

void
canna_process_keyevent(iml_session_t *s, IMKeyListEvent *kev)
{
    int ch, mode;

    ch = canna_translate_keyevent(kev);
    ch = canna_swap_keyevent(s, ch);

    /* if current mode needs the aux support, don't commit the keyevent to Canna.
       in this case, all of keyevent is handled via aux */
    mode = canna_get_current_mode(s);
    if (mode == CANNA_MODE_ExtendMode || mode == CANNA_MODE_HexMode) {
	switch (canna_get_current_minor_mode(s)) {
	    case CANNA_MODE_ExtendMode:
	    case CANNA_MODE_HexMode:
	    case CANNA_MODE_TourokuMode:
	    case CANNA_MODE_MountDicMode:
	    case CANNA_MODE_ChangingServerMode:
	    case CANNA_MODE_TourokuHinshiMode:
	    case CANNA_MODE_DeleteDicMode:
	    case CANNA_MODE_TourokuDicMode:
		/* process the keyevent via aux */
		canna_aux_draw(s, AUX_UPDATE_KEY, kev, ch);
		break;
	    default:
		goto try_to_process;
	}
    } else {
try_to_process:
	if (ch) {
	    if (!process_keyevent(s, ch))
		goto no_process;
	} else {
no_process:;
	    /* I don't process this keyevent.  Return it. */
	    iml_inst *lp;
	    lp = s->If->m->iml_make_keypress_inst(s, ((IMKeyEventStruct *)
						  kev->keylist));
	    s->If->m->iml_execute(s, &lp);
	}
    }
    return;
}

void
canna_process_auxevent (iml_session_t *s, IMAuxDrawCallbackStruct *aux)
{
#ifdef DEBUG
    printf ("*** aux_name=%s\n", aux->aux_name);
    printf ("*** count_integer_values=%d\n", aux->count_integer_values);
    printf ("*** count_string_values=%d\n", aux->count_string_values);
    if (aux->string_values != NULL)
	printf ("*** string_values = %s\n", aux->string_values->text.utf_chars);
    printf ("*** keycode = %d\n", aux->integer_values[INT_KEYCODE]);
    printf ("*** modifier = %d\n", aux->integer_values[INT_KEYMOD]);
    printf ("*** translated = %d\n", aux->integer_values[INT_TRANSLATEDCODE]);
#endif

    if (aux->count_integer_values > 0) {
	switch(aux->integer_values[INT_AUXMODE]) {
	    case AUX_UPDATE_KEY:
		if (aux->integer_values[INT_TRANSLATEDCODE] > 0)
		    process_keyevent(s, aux->integer_values[INT_TRANSLATEDCODE]);
		break;
	    case AUX_FORCE_UPDATE_STATE:
	    case AUX_FORCE_NOT_UPDATE_STATE:
		canna_aux_draw(s, aux->integer_values[INT_AUXMODE], NULL);
		break;
	    default:
		break;
	}
    }
}

/*
  IF offer.
*/

void
if_GetIfInfo(IMArgList args, int num_args)
{
    int i;
    init_objects();
    for (i = 0; i < num_args; i++, args++) {
        switch (args->id) {
	case IF_VERSION:
	    args->value = (IMArgVal) "1.2";
	    break;
	case IF_METHOD_TABLE:
	    args->value = (IMArgVal) &canna_methods;
	    break;
	case IF_LE_NAME:
	    args->value = (IMArgVal) &lename;
	    break;
	case IF_SUPPORTED_LOCALES:
	    args->value = (IMArgVal) &locales;
	    break;
	case IF_SUPPORTED_OBJECTS:
	    if (!objects)
		init_objects();
	    args->value = (IMArgVal) objects;
	    break;
	case IF_NEED_THREAD_LOCK:
	    args->value = (IMArgVal) True;
	    break;
	default:
	    break;
	}
    }
}

/*
  IFs
*/

Bool
if_canna_OpenIF(iml_if_t *If)
{
    int st;

    st = csconv_status(); 
    if (st == CSC_UNLOADED) {
	setup_csconv();
    } else if (st == CSC_FAILED) {
	return False;
    }

    return True;
}

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

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

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

Bool
if_canna_OpenDesktop(iml_desktop_t * desktop,
		     IMArgList args,
		     int num_args)
{
    CannaLEDesktop *d;

    d = (CannaLEDesktop *) malloc(sizeof (CannaLEDesktop));
    memset(d, 0, sizeof(CannaLEDesktop));
    d->drop_priv = canna_drop_privilege(desktop->user_name);
    desktop->specific_data = (void *) d;

    canna_context_id_counter = 1;

    return True;
}

Bool
if_canna_CloseDesktop(iml_desktop_t * desktop)
{
    CannaLEDesktop *d = (CannaLEDesktop *) desktop->specific_data;

    free(d);

    jrKanjiControl(0, KC_FINALIZE, (char *) 0);

    return True;
}

Bool
if_canna_CreateSC(iml_session_t *s, IMArgList args, int num_args)
{
    CannaLESession *pcls = (CannaLESession*) malloc(sizeof(CannaLESession));
    jrKanjiStatus *pks = (jrKanjiStatus*) malloc(sizeof(jrKanjiStatus));
    iml_desktop_t *desktop = s->desktop;
    CannaLEDesktop *d = canna_desktop_data(s);
    unsigned char *buf;

    buf = (unsigned char *) malloc(CANNA_COMMIT_STRING_BUFSIZE);
    if ((!pcls) || (!pks) || (!buf)) return False;
    pcls->ksv.ks = pks;
    pcls->ksv.buffer = buf;
    buf[0] = '\0';
    pcls->ksv.bytes_buffer = CANNA_COMMIT_STRING_BUFSIZE;
    pcls->conversion_start = False;
    pcls->aux_start = False;
    pcls->context_id = canna_context_id_counter++;
    pcls->old_minor_mode = 0;
    pcls->update_state = 0;
    pcls->force_not_update_state = 0;
    /* Init jrKanjiStatus variable with 0. */
    memset(pks, 0, sizeof(jrKanjiStatus));

    s->specific_data = (void*) pcls;

    /* initialize here because of avoid wrong initialization for multiple users */
    if (canna_init(s, d->drop_priv ? desktop->user_name : NULL)) {
	if (!jrKanjiControl(canna_session_context(s), KC_QUERYCONNECTION, (char *) 0)) {
	    fprintf(stderr, "htt: CannaLE: Unable to connect with canna server.\n");
	    return False;
	}
    }

    return True;
}

Bool
if_canna_DestroySC(iml_session_t *s)
{
    CannaLESession *pcls;
    jrKanjiStatusWithValue *pksv = canna_session_status(s);

    canna_aux_done(s);
    pcls = canna_session_data(s);
    jrKanjiControl(canna_session_context(s), KC_CLOSEUICONTEXT, (char *) pksv);
    if (pksv->buffer != NULL)
	free(pksv->buffer);
    if (pksv->ks != NULL)
	free(pksv->ks);
    free(pcls);
    return True;
}

IMText*
if_canna_ResetSC(iml_session_t *s)
{
    iml_inst *lp;
    IMText *p;

    /* erase preedit. */
    lp = s->If->m->iml_make_preedit_erase_inst(s);
    s->If->m->iml_execute(s, &lp);

    /* fix the current string. (kakutei) */
    p = canna_kakutei(s);

    if (p->char_length) return p;
    return (IMText*) NULL;
}

Bool
if_canna_SetSCValue(iml_session_t *s, IMArgList args, int num)
{
    int i;
    IMArg *p = args;

    for (i = 0; i < num; i++, p++) {
        switch (p->id) {
	case SC_TRIGGER_ON_NOTIFY:
	    canna_make_conversion_on(s);
	    break;
	case SC_TRIGGER_OFF_NOTIFY:
	    canna_make_conversion_off(s);
	    break;
	case SC_REALIZE:
	    /* currently do nothing. */
	    break;
	case SC_LOOKUP_LABELTYPE:
	    break;
	default:
	    break;
	}
    }

    return True;
}

Bool
if_canna_GetSCValue(iml_session_t *s, IMArgList args, int num_args)
{
    int i;
    IMArg *p = args;

    /* Canna uses at least LATIN, HIRAGANA, KATAKANA,
       and KANJI scripts.
       That's all to it? */
    static int charsubset[] = {
        67,			/* LATIN */
        47,			/* HIRAGANA */
        48,			/* KATAKANA */
        71,			/* KANJI */
        0
    };

    for (i = 0; i < num_args; i++, p++) {
        switch (p->id) {
            case SC_SUPPORTED_CHARACTER_SUBSETS:
                /* specify CHARACTER_SUBSETS */
                p->value = (IMArgVal) charsubset;
                break;
            default:
                break;
            }
    }
    return True;
}

void
if_canna_SetSCFocus(iml_session_t *s)
{
    canna_status_draw(s);
    canna_aux_draw(s, AUX_FOCUS_CHANGE, ((void *)(int) 1));
}

void
if_canna_UnsetSCFocus(iml_session_t *s)
{
    canna_aux_draw(s, AUX_FOCUS_CHANGE, ((void *)(int) 0));
//    canna_aux_done(s);
}

void
if_canna_SendEvent(iml_session_t *s, IMInputEvent *ev)
{
    if (ev) {
	switch (ev->type) {
	case IM_EventKeyList:
	    canna_process_keyevent(s, (IMKeyListEvent*) ev);
	    break;
	case IM_EventAux:
	    canna_process_auxevent(s, ((IMAuxEvent *) ev)->aux);
	    break;
	case IM_EventString:
	case IM_EventText:
	default:
	    break;
	}
    }
    return;
}

static void
canna_change_mode(iml_session_t *s, int id)
{
    jrKanjiStatusWithValue *pksv;

    pksv = canna_session_status(s);
    pksv->val = id;
    jrKanjiControl(canna_session_context(s),
		   KC_CHANGEMODE, (char*) pksv);
}

static int
canna_get_current_mode(iml_session_t *s)
{
    char mode[4];

    jrKanjiControl(canna_session_context(s), KC_SETMODEINFOSTYLE, (char *)1);
    jrKanjiControl(canna_session_context(s), KC_QUERYMODE, (char *)mode);
    jrKanjiControl(canna_session_context(s), KC_SETMODEINFOSTYLE, (char *)0);

#ifdef DEBUG
    printf("*** major = %d\n", mode[0] - '@');
#endif

    return mode[0] - '@';
}

static int
canna_get_current_minor_mode(iml_session_t *s)
{
    char mode[4];

    jrKanjiControl(canna_session_context(s), KC_SETMODEINFOSTYLE, (char *)2);
    jrKanjiControl(canna_session_context(s), KC_QUERYMODE, (char *)mode);
    jrKanjiControl(canna_session_context(s), KC_SETMODEINFOSTYLE, (char *)0);

#ifdef DEBUG
    printf("*** minor = %d:%d\n", mode[0] - '@', mode[1] - '@');
#endif

    return mode[1] - '@';
}

static Bool
canna_drop_privilege(const char *username)
{
    struct passwd *pw;
    uid_t uid;
    gid_t gid;

    if (!username)
	goto error;
    if ((pw = getpwnam(username)) == NULL) {
	goto error;
    } else {
	uid = pw->pw_uid;
	gid = pw->pw_gid;
	if (uid < 500)
	    goto error;
    }
    if (setregid(gid, gid) < 0) {
	return False;
    }
    if (setreuid (uid, uid) < 0) {
	return False;
    }
    return True;

error:
    if ((pw = getpwnam("nobody")) != NULL) {
	gid = pw->pw_gid;
	uid = pw->pw_uid;
	setregid(gid, gid);
	setreuid(uid, uid);
    } else {
	assert(0);
    }
    return False;
}
