/*
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 <canna/jrkanji.h>

#ifndef _WCHAR_T
#define _WCHAR_T
#endif

#include "SunIM.h"

static char canna_le_init_filename[] = "../locale/ja/CannaLE/canna.init";

#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) == '\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;

static char *current_locale = 0;

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();

/* 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};

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

static IMLocale locales[] = {
    {"ja", jahrn_string},	/* locale id, HRN */
    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()
{
    int st;

    if (csconv_status() == CSC_UNLOADED)
	dlopen_csconv();
    if (csconv_status() == CSC_UNOPENED)
	csconv_open_conv();

    return;
}

/*
  Desktop data:
 */

#if 0
typedef struct
{
} CannaLEDesktop;
#endif

/*
  Session data:
*/

static int canna_context_id_counter = 1;

typedef struct
{
    int context_id;
    jrKanjiStatusWithValue ksv;
    int status_start;
    int preedit_start;
    int luc_start;
    Bool conv_on;
} CannaLESession;

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

int
canna_session_context(iml_session_t *s)
{
    printf("canna_session_context: %d\n", ((CannaLESession*)(s->specific_data))->context_id);
    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()
{
}

/*
  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, *fbl;
    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, *fbl;
    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, *fbl;
    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)
{
    int i;
    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:
*/

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

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

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

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

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

    clen = 0;
    for (i = 0;i < nseg;i++) {
	clen += nb[i];
    }
    ulen = UTFCHAR_buffer_size(clen + 1);
    ustr = p = (UTFCHAR*) alloca(ulen);
    idices = (int*) alloca(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) return NULL;
    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]);
	}
    }
    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 *if_path)
{
    char *buf;
    int ipsize;
    FILE *f;

    ipsize = strlen(if_path);
    buf = (char*) malloc((ipsize + 2) * sizeof(char)
			 + sizeof(canna_le_init_filename));
    if (ipsize < 1) return NULL;
    strcpy(buf, if_path);
    buf[ipsize] = '/';
    buf[ipsize + 1] = '\0';
    strcat(buf, canna_le_init_filename);

    /* check whether the file is readable. */
    f = fopen(buf, "r");
    if (!f) {
	free(buf);
	return NULL;
    }
    fclose(f);

    return buf;
}

Bool
canna_init(char *if_path)
{
    char **warning = NULL;
    char *init_filename;        

    init_filename = canna_init_filename(if_path);
    if (init_filename) {
	jrKanjiControl(0, KC_SETINITFILENAME, init_filename);
	free(init_filename);
    }
    jrKanjiControl(0, KC_INITIALIZE, (char *) &warning);

    if (warning) {
	char          **p;

	for (p = warning; *p; p++)
	    fprintf(stderr, "htt: canna.so: %s\n", *p);

	return False;
    }

    jrKanjiControl(0, KC_SETAPPNAME, (char *) "CannaLE");

    canna_context_id_counter = 1;

    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);

    jrKanjiStatusWithValue *pksv;
    pksv = canna_session_status(s);

    /* When KanjiStatus is uninited, return immediately. */
    if (!pksv->ks->mode) return;

    {
	/* Create IMText with feedback. */
	int len;
	unsigned char *str;
	int ft1;
	str = pksv->ks->mode;
	len = strlen(str);
	ft1 = Canna_Feedback_Normal;
	p = canna_string_to_IMText(s, 1, &len, &str, &ft1);
    }

    if (pcls->status_start == False) {
        lp = s->If->m->iml_make_status_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        pcls->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
canna_preedit_draw(iml_session_t *s)
{
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p;
    CannaLESession *pcls = canna_session_data(s);
    jrKanjiStatus *pks = canna_session_status(s)->ks;

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

    if (pcls->preedit_start == False) {
        lp = s->If->m->iml_make_preedit_start_inst(s);
        s->If->m->iml_link_inst_tail(&rrv, lp);
        pcls->preedit_start = True;
    }

    {
	/* 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_preedit_done(iml_session_t *s)
{
    CannaLESession *pcls = canna_session_data(s);
    if (pcls->preedit_start == True) {
	iml_inst *lp;
        lp = s->If->m->iml_make_preedit_done_inst(s);
	s->If->m->iml_execute(s, &lp);
        pcls->preedit_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)
{
#if 1
    char linestr[1024];
#endif
    jrKanjiStatus *pks = canna_session_status(s)->ks;
    unsigned char *str = pks->gline.line;
    unsigned char *p, *st;
    int i, idx, tot, delimiterp;

    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] = CANNA_NEXT_CHAR(p);
	}
    }
#if 1
    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;
}

void
canna_start_lookup_choice(iml_session_t *s,
			  iml_inst **prrv, int num)
{
    CannaLESession *pcls = canna_session_data(s);
    if (pcls->luc_start == False) {
	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);
        pcls->luc_start = True;
    }
}

void
canna_show_lookup_choice(iml_session_t *s)
{
    int num;
    iml_inst *lp;
    iml_inst *rrv = NULL;
    IMText *p;
    IMLookupDrawCallbackStruct *draw;
    CannaLESession *pcls = canna_session_data(s);
    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;

	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)
{
    CannaLESession *pcls = canna_session_data(s);
    if (pcls->luc_start == True) {
	iml_inst *lp;
        lp = s->If->m->iml_make_lookup_done_inst(s);
	s->If->m->iml_execute(s, &lp);
        pcls->luc_start = False;
    }
}

void
canna_make_conversion_off(iml_session_t *s)
{
    IMText *p;
    jrKanjiStatusWithValue *pksv;
    CannaLESession* ses = canna_session_data(s);
    pksv = canna_session_status(s);

    pksv->val = CANNA_MODE_AlphaMode;
    jrKanjiControl(canna_session_context(s),
		   KC_CHANGEMODE, (char*) pksv);
    p = canna_kakutei(s);
    send_commit(s, p, 1);
    canna_status_draw(s);
    canna_lookup_choice_done(s);
    canna_preedit_done(s);

    ses->conv_on = False;
}

void
canna_make_conversion_on(iml_session_t  *s)
{
    CannaLESession* ses = canna_session_data(s);
    jrKanjiStatusWithValue *pksv;
    pksv = canna_session_status(s);

    pksv->val = CANNA_MODE_HenkanMode;
    jrKanjiControl(canna_session_context(s),
		   KC_CHANGEMODE, (char*) pksv);
    canna_status_draw(s);

    ses->conv_on = True;
}

int
canna_translate_keyevent(iml_session_t *s, IMKeyListEvent *kev)
{
    IMKeyEventStruct *k = (IMKeyEventStruct *) kev->keylist;
    printf("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:
	/* CANNA_KEY_PF1 */
	return CANNA_KEY_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 '\b';

    case IM_VK_SPACE:
    if( k->modifier & IM_CTRL_MASK ) {
      CannaLESession *pcls = canna_session_data(s);
      if ( pcls->conv_on == True ) {
        canna_make_conversion_off(s);
      } else
        canna_make_conversion_on(s);
        return -100; /* Non existant value */
      }

    default:
	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;
}

void
canna_process_keyevent(iml_session_t *s, IMKeyListEvent *kev)
{
    int ch;
    CannaLESession* ses = canna_session_data(s);
    ch = canna_translate_keyevent(s, kev);

    if (ch == -100) return;
    if (ses->conv_on == True && ch) {
	int size, n;
	char buf[CANNA_COMMIT_STRING_BUFSIZE + 1];

	jrKanjiStatus *pks;
	pks = canna_session_status(s)->ks;
	size = CANNA_COMMIT_STRING_BUFSIZE;
	n = jrKanjiString(canna_session_context(s),
			  ch, buf, size, pks);
	buf[n] = '\0';
	if (n > 0) {
	    IMText *p;
    	    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);
	if (pks->info & KanjiGLineInfo)	{
	    if (pks->gline.length > 0)
		canna_show_lookup_choice(s);
	    else
		canna_lookup_choice_done(s);
	}
    }else{
	/* 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;
}

/*
  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:
	    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;
    if (canna_init(If->ifpath_name)) {
	if (!jrKanjiControl(0, KC_QUERYCONNECTION, (char *) 0)) {
	    fprintf(stderr,
		"htt: canna.so: Unable to connect with canna server.\n");
	    return False;
	}
    }

    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)
{
    jrKanjiControl(0, KC_FINALIZE, (char *) 0);

    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)
{
    return True;
}

Bool
if_canna_CloseDesktop(iml_desktop_t * desktop)
{
    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));
    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->status_start = False;
    pcls->luc_start = False;
    pcls->preedit_start = False;
    pcls->context_id = canna_context_id_counter++;
    /* Init jrKanjiStatus variable with 0. */
    memset(pks, 0, sizeof(jrKanjiStatus));

    s->specific_data = (void*) pcls;
    return True;
}

Bool
if_canna_DestroySC(iml_session_t *s)
{
    CannaLESession *pcls;
    pcls = canna_session_data(s);
    jrKanjiControl(canna_session_context(s),
		   KC_CLOSEUICONTEXT,
		   (char*) canna_session_status(s));
    free(pcls->ksv.buffer);
    free(pcls->ksv.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);
}

void
if_canna_UnsetSCFocus(iml_session_t *s)
{
    /* do nothing. */
}

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:
	case IM_EventString:
	case IM_EventText:
	default:
	    break;
	}
    }
    return;
}

/* Local Variables: */
/* c-file-style: "bsd" */
/* End: */
