/*
 * Copyright (C) 2003   Choe Hwanjin <krisna@kldp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <wchar.h>
#include <ctype.h>
#include <X11/Xmd.h>

#define CARD32BIT CARD32

#include <SunIM.h>

#include "composer.h"
#include "hangul.h"

#include "keyboard.h"

#define HANGUL_CONFIG_FILENAME (IM_LEIFDIR "/hangul/hangul.conf")
#define CANDIDATE_TABLE_FILENAME (IM_LEIFDIR "/hangul/tables/candidate.txt")

typedef struct _CandidateItem  CandidateItem;
typedef struct _CandidateTable CandidateTable;

struct _CandidateItem {
    UTFCHAR ch;
    UTFCHAR *comment;
    int len;
    struct _CandidateItem *next;
    struct _CandidateItem *next_key;
};

struct _CandidateTable {
    int size;
    struct _CandidateItem ***data;
};

typedef struct _HangulLEConfig {
    const UTFCHAR *keyboard;
    Bool (*composer)(iml_session_t *, IMKeyEventStruct *);
} HangulLEConfig;

static HangulLEConfig config;
static CandidateTable candidate_table;

static Bool hangul_composer_2(iml_session_t *s, IMKeyEventStruct *key);
static Bool hangul_composer_3(iml_session_t *s, IMKeyEventStruct *key);

static int output_mode = 0;

static Bool
hangul_is_empty(Session *hsession)
{
    return (hsession->choseong[0]  == 0 &&	\
	    hsession->jungseong[0] == 0 &&	\
	    hsession->jongseong[0] == 0 );
}

static void
hangul_push (Session *hsession, UTFCHAR ch)
{
    hsession->stack[++hsession->stack_index] = ch;
}

static UTFCHAR
hangul_peek (Session *hsession)
{
    if (hsession->stack_index < 0)
	return 0;
    return hsession->stack[hsession->stack_index];
}

static UTFCHAR
hangul_pop (Session *hsession)
{
    if (hsession->stack_index < 0)
	return 0;
    return hsession->stack[hsession->stack_index--];
}

static Bool
hangul_add_choseong (Session *hsession, UTFCHAR ch)
{
    if (hsession->lindex >= 3)
        return False;
    hsession->lindex++;
    hsession->choseong[hsession->lindex] = ch;
    return True;
}

static Bool
hangul_add_jungseong (Session *hsession, UTFCHAR ch)
{
    if (hsession->vindex >= 3)
        return False;
    hsession->vindex++;
    hsession->jungseong[hsession->vindex] = ch;
    return True;
}

static Bool
hangul_add_jongseong (Session *hsession, UTFCHAR ch)
{
    if (hsession->tindex >= 3)
        return False;
    hsession->tindex++;
    hsession->jongseong[hsession->tindex] = ch;
    return True;
}

static Bool
hangul_sub_choseong (Session *hsession)
{
    hsession->choseong[hsession->lindex] = 0;
    if (hsession->lindex <= 0)
        return False;
    hsession->lindex--;
    return True;
}

static Bool
hangul_sub_jungseong (Session *hsession)
{
    hsession->jungseong[hsession->vindex] = 0;
    if (hsession->vindex <= 0)
        return False;
    hsession->vindex--;
    return True;
}

static Bool
hangul_sub_jongseong (Session *hsession)
{
    hsession->jongseong[hsession->tindex] = 0;
    if (hsession->tindex <= 0)
        return False;
    hsession->tindex--;
    return True;
}

static IMFeedbackList*
feedbacklist_new(iml_session_t *s, int len)
{
    int i;
    IMFeedbackList *feedbacklist;
    IMFeedback *feedback;

    feedbacklist = (IMFeedbackList*)
			s->If->m->iml_new(s, sizeof(IMFeedbackList) * len);
    for (i = 0; i < len; i++) {
    	feedbacklist[i].count_feedbacks = 1;
	feedback = s->If->m->iml_new(s,
		    sizeof(IMFeedback) * feedbacklist[i].count_feedbacks);
    	feedbacklist[i].feedbacks = feedback;
	IM_FEEDBACK_TYPE(feedback) = IM_DECORATION_FEEDBACK;
	IM_FEEDBACK_VALUE(feedback) = IMNormal;
    }

    return feedbacklist;
}

static void
feedback_set_properties(iml_session_t *s,
			IMFeedbackList *feedbacks, int nproperties, ...)
{
    int i;
    va_list args;
    IMFeedback *feedback;

    if (feedbacks->count_feedbacks < nproperties) {
	/* realloc */
	feedbacks->count_feedbacks = nproperties;
	feedbacks->feedbacks = (IMFeedback*)s->If->m->iml_new(s,
		sizeof(IMFeedback) * feedbacks->count_feedbacks);
	memset(feedbacks->feedbacks, 0,
		sizeof(IMFeedback) * feedbacks->count_feedbacks);
    }

    va_start(args, nproperties);
    for (i = 0; i < nproperties; i++) {
	feedback = &feedbacks->feedbacks[i];
	IM_FEEDBACK_TYPE(feedback) = va_arg(args, int);
	IM_FEEDBACK_VALUE(feedback) = va_arg(args, int);
    }
    va_end(args);
}

static IMFeedbackList*
feedbacklist_new_underline(iml_session_t *s, int len)
{
    int i;
    IMFeedbackList *feedbacklist;

    feedbacklist = feedbacklist_new(s, len);
    for (i = 0; i < len; i++) {
	feedback_set_properties(s, &feedbacklist[i], 1,
				IM_DECORATION_FEEDBACK, IMUnderline);
    }

    return feedbacklist;
}

static IMFeedbackList*
feedbacklist_new_reverse(iml_session_t *s, int len)
{
    int i;
    IMFeedbackList *feedbacklist;

    feedbacklist = feedbacklist_new(s, len);
    for (i = 0; i < len; i++) {
	feedback_set_properties(s, &feedbacklist[i], 1,
				IM_DECORATION_FEEDBACK, IMReverse);
    }

    return feedbacklist;
}

static IMText *
imtext_new(iml_session_t *s, UTFCHAR *str, int len, IMFeedbackList *feedbacks)
{
    IMText *text;

    text = (IMText *) s->If->m->iml_new(s, sizeof(IMText));
    text->encoding = UTF16_CODESET;
    text->char_length = len;
    text->text.utf_chars = (UTFCHAR*)s->If->m->iml_new(s,
				sizeof(UTFCHAR) * (len + 1));
    memcpy(text->text.utf_chars, str, sizeof(UTFCHAR) * len);
    text->text.utf_chars[len] = '\0';
    text->feedback = feedbacks;
    text->count_annotations = 0;
    text->annotations = NULL;

    return text;
}

static UTFCHAR
get_preedit_char(iml_session_t *s)
{
    UTFCHAR ch;
    Session *hsession = (Session*) s->specific_data;

    if (hangul_is_empty(hsession))
	return 0;

    /* use hangul syllables   (U+AC00 - U+D7AF) */
    ch = hangul_jamo_to_syllable (hsession->choseong[0],
				  hsession->jungseong[0],
				  hsession->jongseong[0]);
     
    return ch;
}


#define OUTPUT_MODE_JAMO_EXT  1
#define OUTPUT_MODE_JAMO  1

static int
get_preedit_string(iml_session_t *s, UTFCHAR *buf)
{
    int i;
    int n = 0;
    Session *hsession = (Session*) s->specific_data;

    if (hangul_is_empty(hsession))
	return 0;

    if (output_mode & OUTPUT_MODE_JAMO_EXT) {
	  /* we use conjoining jamo, U+1100 - U+11FF */
	if (hsession->choseong[0] == 0) {
	    buf[n++] = HCF;
	} else {
	    for (i = 0; i <= hsession->lindex; i++)
		buf[n++] = hsession->choseong[i];
	}

	if (hsession->jungseong[0] == 0)
	    buf[n++] = HJF;
	else {
	    for (i = 0; i <= hsession->vindex; i++)
		buf[n++] = hsession->jungseong[i];
	}

	if (hsession->jongseong[0] != 0) {
	    for (i = 0; i <= hsession->tindex; i++)
		buf[n++] = hsession->jongseong[i];
	}
    } else if (output_mode & OUTPUT_MODE_JAMO) {
	/* we use conjoining jamo, U+1100 - U+11FF */
	if (hsession->choseong[0] == 0)
	    buf[n++] = HCF;
	else
	    buf[n++] = hsession->choseong[0];
	 
	if (hsession->jungseong[0] == 0)
	    buf[n++] = HJF;
	else
	    buf[n++] = hsession->jungseong[0];
	 
	if (hsession->jongseong[0] != 0)
	    buf[n++] = hsession->jongseong[0];
    } else {
	/* use hangul syllables   (U+AC00 - U+D7AF)
	 * and compatibility jamo (U+3130 - U+318F) */
	UTFCHAR ch;
	ch = hangul_jamo_to_syllable (hsession->choseong[0],
				      hsession->jungseong[0],
				      hsession->jongseong[0]);
	 
	if (ch) {
	    buf[n++] = ch;
	} else {
	    if (hsession->choseong[0]) {
		ch = hangul_choseong_to_cjamo (hsession->choseong[0]);
		buf[n++] = ch;
	    }
	    if (hsession->jungseong[0]) {
		ch = hangul_jungseong_to_cjamo (hsession->jungseong[0]);
		buf[n++] = ch;
	    }
	    if (hsession->jongseong[0]) {
		ch = hangul_jongseong_to_cjamo (hsession->jongseong[0]);
		buf[n++] = ch;
	    }
	}
    }

    return n;
}

static void
hangul_preedit_update(iml_session_t *s)
{
    iml_inst *lp;
    int len;
    UTFCHAR buf[12];
    Session *hsession = (Session*) s->specific_data;

    len = get_preedit_string(s, buf);
    if (len == 0) {
	lp = s->If->m->iml_make_preedit_erase_inst(s);
    	s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
    } else {
    	IMText *text;

	text = imtext_new(s, buf, len, feedbacklist_new_reverse(s, len));
	lp = s->If->m->iml_make_preedit_draw_inst(s, text);
    	s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
    }
}

static void
hangul_commit_utfchar(iml_session_t *s, UTFCHAR ch)
{
    iml_inst *lp;
    IMText *text;
    Session *hsession = (Session*) s->specific_data;

    text = imtext_new(s, &ch, 1, feedbacklist_new(s, 1));

    lp = s->If->m->iml_make_commit_inst(s, text);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_commit_utfstr(iml_session_t *s, UTFCHAR *str, int len)
{
    iml_inst *lp;
    IMText *text;
    Session *hsession = (Session*) s->specific_data;

    text = imtext_new(s, str, len, feedbacklist_new(s, len));

    lp = s->If->m->iml_make_commit_inst(s, text);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_hsession_clear(Session *hsession)
{
    hsession->stack_index = -1;
    hsession->stack[0] = 0;
    hsession->stack[1] = 0;
    hsession->stack[2] = 0;
    hsession->stack[3] = 0;
    hsession->stack[4] = 0;
    hsession->stack[5] = 0;
    hsession->stack[6] = 0;
    hsession->stack[7] = 0;
    hsession->stack[8] = 0;
    hsession->stack[9] = 0;
    hsession->stack[10] = 0;
    hsession->stack[11] = 0;

    hsession->lindex = 0;
    hsession->choseong[0] = 0;
    hsession->choseong[1] = 0;
    hsession->choseong[2] = 0;
    hsession->choseong[3] = 0;

    hsession->vindex = 0;
    hsession->jungseong[0] = 0;
    hsession->jungseong[1] = 0;
    hsession->jungseong[2] = 0;
    hsession->jungseong[3] = 0;

    hsession->tindex = 0;
    hsession->jongseong[0] = 0;
    hsession->jongseong[1] = 0;
    hsession->jongseong[2] = 0;
    hsession->jongseong[3] = 0;
}

static int
hangul_get_commit_string(iml_session_t *s, UTFCHAR *buf, int len)
{
    int i;
    int n = 0;
    Session *hsession = (Session*) s->specific_data;

    if (output_mode & OUTPUT_MODE_JAMO_EXT) {
	  /* we use conjoining jamo, U+1100 - U+11FF */
	if (hsession->choseong[0] == 0) {
	    buf[n++] = HCF;
	} else {
	    for (i = 0; i <= hsession->lindex; i++)
		buf[n++] = hsession->choseong[i];
	}

	if (hsession->jungseong[0] == 0)
	    buf[n++] = HJF;
	else {
	    for (i = 0; i <= hsession->vindex; i++)
		buf[n++] = hsession->jungseong[i];
	}

	if (hsession->jongseong[0] != 0) {
	    for (i = 0; i <= hsession->tindex; i++)
		buf[n++] = hsession->jongseong[i];
	}
    } else if (output_mode & OUTPUT_MODE_JAMO) {
	/* we use conjoining jamo, U+1100 - U+11FF */
	if (hsession->choseong[0] == 0)
	    buf[n++] = HCF;
	else
	    buf[n++] = hsession->choseong[0];
	 
	if (hsession->jungseong[0] == 0)
	    buf[n++] = HJF;
	else
	    buf[n++] = hsession->jungseong[0];
	 
	if (hsession->jongseong[0] != 0)
	    buf[n++] = hsession->jongseong[0];
    } else {
	/* use hangul syllables   (U+AC00 - U+D7AF)
	 * and compatibility jamo (U+3130 - U+318F) */
	UTFCHAR ch;
	ch = hangul_jamo_to_syllable (hsession->choseong[0],
				      hsession->jungseong[0],
				      hsession->jongseong[0]);
	 
	if (ch) {
	    buf[n++] = ch;
	} else {
	    if (hsession->choseong[0]) {
		ch = hangul_choseong_to_cjamo (hsession->choseong[0]);
		buf[n++] = ch;
	    }
	    if (hsession->jungseong[0]) {
		ch = hangul_jungseong_to_cjamo (hsession->jungseong[0]);
		buf[n++] = ch;
	    }
	    if (hsession->jongseong[0]) {
		ch = hangul_jongseong_to_cjamo (hsession->jongseong[0]);
		buf[n++] = ch;
	    }
	}
    }

    return n;
}

static int
hangul_commit(iml_session_t *s)
{
    int len = 0;
    UTFCHAR buf[12];
    Session *hsession = (Session*) s->specific_data;

    if (hangul_is_empty(hsession))
	return 0;

    len = hangul_get_commit_string(s, buf, sizeof(buf));

    hangul_hsession_clear(hsession);
    hangul_preedit_update(s);
    hangul_commit_utfstr(s, buf, len);

    return len;
}

static const HangulCombination compose_table_default[] = {
  { 0x11001100, 0x1101 }, /* choseong  kiyeok + kiyeok	= ssangkiyeok	*/
  { 0x11031103, 0x1104 }, /* choseong  tikeut + tikeut	= ssangtikeut	*/
  { 0x11071107, 0x1108 }, /* choseong  pieup  + pieup	= ssangpieup	*/
  { 0x11091109, 0x110a }, /* choseong  sios   + sios	= ssangsios	*/
  { 0x110c110c, 0x110d }, /* choseong  cieuc  + cieuc	= ssangcieuc	*/
  { 0x11691161, 0x116a }, /* jungseong o      + a	= wa		*/
  { 0x11691162, 0x116b }, /* jungseong o      + ae	= wae		*/
  { 0x11691175, 0x116c }, /* jungseong o      + i	= oe		*/
  { 0x116e1165, 0x116f }, /* jungseong u      + eo	= weo		*/
  { 0x116e1166, 0x1170 }, /* jungseong u      + e	= we		*/
  { 0x116e1175, 0x1171 }, /* jungseong u      + i	= wi		*/
  { 0x11731175, 0x1174 }, /* jungseong eu     + i	= yi		*/
  { 0x11a811a8, 0x11a9 }, /* jongseong kiyeok + kiyeok	= ssangekiyeok	*/
  { 0x11a811ba, 0x11aa }, /* jongseong kiyeok + sios	= kiyeok-sois	*/
  { 0x11ab11bd, 0x11ac }, /* jongseong nieun  + cieuc	= nieun-cieuc	*/
  { 0x11ab11c2, 0x11ad }, /* jongseong nieun  + hieuh	= nieun-hieuh	*/
  { 0x11af11a8, 0x11b0 }, /* jongseong rieul  + kiyeok	= rieul-kiyeok	*/
  { 0x11af11b7, 0x11b1 }, /* jongseong rieul  + mieum	= rieul-mieum	*/
  { 0x11af11b8, 0x11b2 }, /* jongseong rieul  + pieup	= rieul-pieup	*/
  { 0x11af11ba, 0x11b3 }, /* jongseong rieul  + sios	= rieul-sios	*/
  { 0x11af11c0, 0x11b4 }, /* jongseong rieul  + thieuth = rieul-thieuth	*/
  { 0x11af11c1, 0x11b5 }, /* jongseong rieul  + phieuph = rieul-phieuph	*/
  { 0x11af11c2, 0x11b6 }, /* jongseong rieul  + hieuh	= rieul-hieuh	*/
  { 0x11b811ba, 0x11b9 }, /* jongseong pieup  + sios	= pieup-sios	*/
  { 0x11ba11ba, 0x11bb }, /* jongseong sios   + sios	= ssangsios	*/
};

static UTFCHAR
hangul_compose (Session *hsession, UTFCHAR first, UTFCHAR last)
{
    int min, max, mid;
    uint32_t key;
 
    /* make key */
    key = first << 16 | last;

    /* binary search in table */
    min = 0;
    max = hsession->compose_table_size - 1;

    while (max >= min) {
	mid = (min + max) / 2;
	if (hsession->compose_table[mid].key < key)
	    min = mid + 1;
	else if (hsession->compose_table[mid].key > key)
	    max = mid - 1;
	else
	    return hsession->compose_table[mid].code;
    }

    return 0;
}

static UTFCHAR
hangul_key_mapping(Session *hsession, IMKeyEventStruct *key)
{
    int keyChar = key->keyChar;

    if (keyChar >= '!' && keyChar <= '~') {
	if (key->modifier & IM_SHIFT_MASK) {
	    if (keyChar >= 'a' && keyChar <= 'z')
		keyChar -= ('a' - 'A');
	} else {
	    if (keyChar >= 'A' && keyChar <= 'Z')
		keyChar += ('a' - 'A');
	}
	return hsession->keyboard[keyChar - '!'];
    }
    return 0;
}

static Bool
hangul_is_candidate_key(IMKeyEventStruct *key)
{
    if (key->keyCode == IM_VK_F9 || key->keyCode == IM_VK_HANJA)
	return True;
    return False;
}

static Bool
hangul_is_backspace(IMKeyEventStruct *key)
{
    if (key->keyCode == IM_VK_BACK_SPACE)
	return True;
    return False;
}

static Bool
hangul_process_nonhangul(iml_session_t *s, UTFCHAR ch, IMKeyEventStruct *key)
{
    int len = 0;
    UTFCHAR buf[16] = { 0, };
    Session *hsession = (Session*) s->specific_data;

    if (!hangul_is_empty(hsession)) {
	len = hangul_get_commit_string(s, buf, sizeof(buf));
	hangul_hsession_clear(hsession);
	hangul_preedit_update(s);
    }

    if (ch != 0) {	/* for sebeol(3 set) keymap */
	buf[len++] = ch;
	hangul_commit_utfstr(s, buf, len);
	return True;
    }

    hangul_commit_utfstr(s, buf, len);

    return False;
}

static Bool
hangul_composer_2(iml_session_t *s, IMKeyEventStruct *key)
{
    UTFCHAR ch;
    UTFCHAR comp_ch;
    UTFCHAR jong_ch;
    Session *hsession = (Session*) s->specific_data;

    ch = hangul_key_mapping(hsession, key);

    if (hsession->jongseong[0]) {
	if (hangul_is_choseong (ch)) {
	    jong_ch = hangul_choseong_to_jongseong (ch);
	    comp_ch = hangul_compose (hsession, 
			       hsession->jongseong[0], jong_ch);
	    if (hangul_is_jongseong (comp_ch)) {
		hsession->jongseong[0] = comp_ch;
		hangul_push (hsession, comp_ch);
	    } else {
		hangul_commit (s);
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
	    }
	    goto done;
	}
	if (hangul_is_jungseong (ch)) {
	    UTFCHAR pop, peek;
	    pop = hangul_pop (hsession);
	    peek = hangul_peek (hsession);
	    if (hangul_is_jungseong (peek)) {
		hsession->jongseong[0] = 0;
		hangul_commit (s);
		hsession->choseong[0] = hangul_jongseong_to_choseong (pop);
		hsession->jungseong[0] = ch;
		hangul_push (hsession, hsession->choseong[0]);
		hangul_push (hsession, ch);
	    } else {
		wchar_t choseong, jongseong; 
		hangul_jongseong_dicompose (hsession->jongseong[0],
					 &jongseong, &choseong);
		hsession->jongseong[0] = jongseong;
		hangul_commit (s);
		hsession->choseong[0] = choseong;
		hsession->jungseong[0] = ch;
		hangul_push (hsession, choseong);
		hangul_push (hsession, ch);
	    }
	    goto done;
	}
    } else if (hsession->jungseong[0]) {
	if (hangul_is_choseong (ch)) {
	    if (hsession->choseong[0]) {
		jong_ch = hangul_choseong_to_jongseong (ch);
		if (hangul_is_jongseong (jong_ch)) {
		    hsession->jongseong[0] = jong_ch;
		    hangul_push (hsession, jong_ch);
		} else {
		    hangul_commit (s);
		    hsession->choseong[0] = ch;
		    hangul_push (hsession, ch);
		}
	    } else {
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
	    }
	    goto done;
	}
	if (hangul_is_jungseong (ch)) {
	    comp_ch = hangul_compose (hsession,
			       hsession->jungseong[0], ch);
	    if (hangul_is_jungseong (comp_ch)) {
		hsession->jungseong[0] = comp_ch;
		hangul_push (hsession, comp_ch);
	    } else {
		hangul_commit (s);
		hsession->jungseong[0] = ch;
		hangul_push (hsession, ch);
	    }
	    goto done;
	}
    } else if (hsession->choseong[0]) {
	if (hangul_is_choseong (ch)) {
	    comp_ch = hangul_compose (hsession,
			       hsession->choseong[0], ch);
	    if (hangul_is_choseong (comp_ch)) {
		hsession->choseong[0] = comp_ch;
		hangul_push (hsession, comp_ch);
	    } else {
		hangul_commit (s);
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
	    }
	    goto done;
	}
	if (hangul_is_jungseong (ch)) {
	    hsession->jungseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
    } else {
	if (hangul_is_choseong (ch)) {
	    hsession->choseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
	if (hangul_is_jungseong (ch)) {
	    hsession->jungseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
    }

    /* treat backspace */
    if (hangul_is_backspace (key)) {
	ch = hangul_pop (hsession);
	if (ch != 0) {
	    if (hangul_is_choseong (ch)) {
		ch = hangul_peek (hsession);
		hsession->choseong[0] = hangul_is_choseong (ch) ? ch : 0;
		goto done;
	    }
	    if (hangul_is_jungseong (ch)) {
		ch = hangul_peek (hsession);
		hsession->jungseong[0] = hangul_is_jungseong (ch) ? ch : 0;
		goto done;
	    }
	    if (hangul_is_jongseong (ch)) {
		ch = hangul_peek (hsession);
		hsession->jongseong[0] = hangul_is_jongseong (ch) ? ch : 0;
		goto done;
	    }
	}
    }

    return hangul_process_nonhangul (s, ch, key); /* english */

done:
    hangul_preedit_update (s);
    return True;
}

static Bool
hangul_composer_3 (iml_session_t *s, IMKeyEventStruct *key)
{
    UTFCHAR ch;
    Session *hsession = (Session*) s->specific_data;

    ch = hangul_key_mapping (hsession, key);

    if (output_mode & OUTPUT_MODE_JAMO_EXT) {
	if (hsession->jongseong[0]) {
	    if (hangul_is_choseong (ch)) {
		hangul_commit (s);
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jungseong (ch)) {
		hangul_commit (s);
		hsession->jungseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jongseong (ch)) {
		if (!hangul_add_jongseong (hsession, ch)) {
		    hangul_commit (s);
		    hsession->jongseong[0] = ch;
		}
		hangul_push (hsession, ch);
		goto done;
	    }
	} else if (hsession->jungseong[0]) {
	    if (hangul_is_choseong (ch)) {
		hangul_commit (s);
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jungseong (ch)) {
		if (!hangul_add_jungseong (hsession, ch)) {
		    hangul_commit (s);
		    hsession->jungseong[0] = ch;
		}
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jongseong (ch)) {
		hsession->jongseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	} else if (hsession->choseong[0]) {
	    if (hangul_is_choseong (ch)) {
		if (!hangul_add_choseong (hsession, ch)) {
		    hangul_commit (s);
		    hsession->choseong[0] = ch;
		}
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jungseong (ch)) {
		hsession->jungseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jongseong (ch)) {
		hangul_commit (s);
		hsession->jongseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	} else {
	    if (hangul_is_choseong (ch)) {
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jungseong (ch)) {
		hsession->jungseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jongseong (ch)) {
		hsession->jongseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	}

	/* treat backspace */
	if (hangul_is_backspace (key)) {
	    ch = hangul_pop (hsession);
	    if (ch != 0) {
		if (hangul_is_choseong (ch)) {
		    hangul_sub_choseong (hsession);
		    goto done;
		}
		if (hangul_is_jungseong (ch)) {
		    hangul_sub_jungseong (hsession);
		    goto done;
		}
		if (hangul_is_jongseong (ch)) {
		    hangul_sub_jongseong (hsession);
		    goto done;
		}
	    }
	}
    } else {
	/* choseong */
	if (hangul_is_choseong (ch)) {
	    if (hsession->choseong[0] == 0) {
		hsession->choseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_choseong (hangul_peek (hsession))) {
		UTFCHAR choseong = hangul_compose (hsession,
						   hsession->choseong[0], ch);
		if (choseong) {
		    hsession->choseong[0] = choseong;
		    hangul_push (hsession, choseong);
		    goto done;
		}
	    }
	    hangul_commit (s);
	    hsession->choseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
	/* junseong */
	if (hangul_is_jungseong (ch)) {
	    if (hsession->jungseong[0] == 0) {
		hsession->jungseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jungseong (hangul_peek (hsession))) {
		UTFCHAR jungseong = hangul_compose (hsession,
						    hsession->jungseong[0],
						    ch);
		if (jungseong) {
		    hsession->jungseong[0] = jungseong;
		    hangul_push (hsession, jungseong);
		    goto done;
		}
	    }
	    hangul_commit (s);
	    hsession->jungseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
	/* jongseong */
	if (hangul_is_jongseong (ch)) {
	    if (hsession->jongseong[0] == 0) {
		hsession->jongseong[0] = ch;
		hangul_push (hsession, ch);
		goto done;
	    }
	    if (hangul_is_jongseong (hangul_peek (hsession))) {
		UTFCHAR jongseong = hangul_compose (hsession,
						    hsession->jongseong[0],
						    ch);
		if (jongseong) {
		    hsession->jongseong[0] = jongseong;
		    hangul_push (hsession, jongseong);
		    goto done;
		}
	    }
	    hangul_commit (s);
	    hsession->jongseong[0] = ch;
	    hangul_push (hsession, ch);
	    goto done;
	}
	/* treat backspace */
	if (hangul_is_backspace (key)) {
	    ch = hangul_pop (hsession);
	    if (ch != 0) {
		if (hangul_is_choseong (ch)) {
		    ch = hangul_peek (hsession);
		    hsession->choseong[0] = hangul_is_choseong (ch) ? ch : 0;
		    goto done;
		}
		if (hangul_is_jungseong (ch)) {
		    ch = hangul_peek (hsession);
		    hsession->jungseong[0] = hangul_is_jungseong (ch) ? ch : 0;
		    goto done;
		}
		if (hangul_is_jongseong (ch)) {
		    ch = hangul_peek (hsession);
		    hsession->jongseong[0] = hangul_is_jongseong (ch) ? ch : 0;
		    goto done;
		}
	    }
	}
    }

    return hangul_process_nonhangul (s, ch, key);

done:
    hangul_preedit_update(s);
    return True;
}

/* candidate routine */
const UTFCHAR UTFCHAR_EOF = ~0;

Bool
utfchar_is_little_endian(void)
{
    union {
	unsigned char mb[sizeof(UTFCHAR)];
	UTFCHAR       ch;
    } test;

    test.ch = 0xFEFF;
    return test.mb[0] == 0xFF;
}

inline UTFCHAR
utfchar_byteswap(UTFCHAR ch)
{
    return (ch << 8 & 0xFF00) | (ch >> 8 & 0xFF);
}

UTFCHAR
utfchar_getc(FILE *stream, Bool need_swap)
{
    UTFCHAR ch;

    if (fread(&ch, sizeof(ch), 1, stream) == 1) {
	if (need_swap)
	    ch = utfchar_byteswap(ch);
	return ch;
    } else
	return UTFCHAR_EOF;
}

UTFCHAR*
utfchar_gets(UTFCHAR *buf, int size, FILE *stream, Bool need_swap)
{
    int i, len;
    UTFCHAR ch;

    len = size / sizeof(buf[0]) - 1;
    for (i = 0; i < len; i++) {
	ch = utfchar_getc(stream, need_swap);
	if (ch == UTFCHAR_EOF)
	    break;
	if (ch == '\n')
	    break;
	buf[i] = ch;
    }
    buf[i] = 0;

    if (i == 0)
	return NULL;
    else
	return buf;
}

UTFCHAR*
utfchar_strchr(UTFCHAR* str, UTFCHAR c)
{
    while (*str != 0) {
	if (*str == c)
	    return str;
	str++;
    }
    return NULL;
}

int
utfchar_strlen(UTFCHAR* str)
{
    int len = 0;

    while (*str != 0) {
	len++;
	str++;
    }
    return len;
}

UTFCHAR*
utfchar_strdup(UTFCHAR* str)
{
    UTFCHAR *ret;
    int size = (utfchar_strlen(str) + 1) * sizeof(UTFCHAR);

    ret = (UTFCHAR*)malloc(size);
    memcpy(ret, str, size);
    return ret;
}

CandidateItem*
candidate_item_new(UTFCHAR ch, UTFCHAR *comment)
{
    CandidateItem *item;

    item = (CandidateItem*)malloc(sizeof(CandidateItem));
    item->ch = ch;
    if (comment != NULL)
	item->comment = utfchar_strdup(comment);
    else
	item->comment = NULL;

    item->len = 0;
    item->next = NULL;
    item->next_key = NULL;
    return item;
}

void
candidate_item_delete(CandidateItem *item)
{
    if (item) {
	free(item->comment);
	free(item);
    }
}

int
candidate_item_length(CandidateItem *item)
{
    int len = 0;
    while (item != NULL) {
	len++;
	item = item->next;
    }
    return len;
}

int
candidate_item_key_length(CandidateItem *item)
{
    int len = 0;
    while (item != NULL) {
	len++;
	item = item->next_key;
    }
    return len;
}

int
candidate_table_compare(const void *first, const void *last)
{
    return ((CandidateItem***)first)[0][0]->ch - 
	   ((CandidateItem***)last)[0][0]->ch;
}

static inline UTFCHAR*
skip_space(UTFCHAR* p)
{
    while (*p == ' '  || *p == '\t' || *p == '\v' ||
	   *p == '\n' || *p == '\r' || *p == '\f')
	p++;
    return p;
}

Bool
candidate_table_load(CandidateTable *table, const char *filename)
{
    FILE *file;
    int n, i, j;
    Bool need_swap;
    UTFCHAR bom;
    UTFCHAR *p;
    UTFCHAR buf[256];
    UTFCHAR ch;
    UTFCHAR key;

    CandidateItem *item;
    CandidateItem *list = NULL;
    CandidateItem *list_last = NULL;
    CandidateItem *items = NULL;
    CandidateItem *items_last = NULL;

    file = fopen(filename, "r");
    if (file == NULL) {
	printf("Failed to open candidate file: %s\n", filename);
	return False;
    }

    /* check byte order */
    bom = utfchar_getc(file, False);
    if (bom == 0xFEFF)
	need_swap = False;
    else if (bom == 0xFFFE)
	need_swap = True;
    else {
	/* candidate table file should be BE */
	need_swap = utfchar_is_little_endian();
	rewind(file);
    }

    while (!feof(file)) {
	p = utfchar_gets(buf, sizeof(buf), file, need_swap);
	if (p == NULL)
	    break;

	p = skip_space(p);
	if (*p == 0 || *p == ';' || *p == '#')
	    continue;

	if (*p == '[') {
	    p++;
	    key = *p;
	    item = candidate_item_new(key, NULL);
	    items = item;
	    items_last = item;
	    if (list == NULL) {
		list = items;
		list_last = items;
	    } else {
		list_last->next_key = items;
		list_last = items;
	    }
	} else {
	    ch = *p;
	    p = utfchar_strchr(p, '=');
	    if (p != NULL) {
		p = skip_space(p + 1);
		item = candidate_item_new(ch, p);
	    } else {
		item = candidate_item_new(ch, NULL);
	    }
	    if (items_last != NULL) {
		items_last->next = item;
		items_last = item;
	    }
	}
    }
    fclose(file);

    table->size = candidate_item_key_length(list);
    table->data = (CandidateItem***)
		  malloc(sizeof(CandidateItem**) * table->size);

    items = list;
    for (i = 0; i < table->size; i++) {
	n = candidate_item_length(items);
	items->len = n - 1;
	table->data[i] = (CandidateItem**)
		         malloc(sizeof(CandidateItem*) * (n + 1));
	item = items;
	for (j = 0;  j < n; j++) {
	    table->data[i][j] = item;
	    item = item->next;
	}
	table->data[i][j] = NULL;
	items = items->next_key;
    }

    qsort(table->data, table->size, sizeof(table->data[0]),
	  candidate_table_compare);

    return True;
}

static int
candidate_table_get_index (UTFCHAR ch)
{
    int first, last, mid;

    /* binary search */
    first = 0;
    last = candidate_table.size;
    while (first <= last) {
	mid = (first + last) / 2;

	if (ch == candidate_table.data[mid][0]->ch)
	    return mid;

	if (ch < candidate_table.data[mid][0]->ch)
	    last = mid - 1;
	else
	    first = mid + 1;
    }

    return -1;
}

static IMLookupDrawCallbackStruct*
hangul_lookup_draw_new(iml_session_t *s)
{
    int i;
    int first, last, n, len;
    UTFCHAR label;
    UTFCHAR *buf;
    CandidateItem *item;
    IMText *text;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    first = hsession->candidate - (hsession->candidate % 10);
    last = (first + 9 < hsession->candidate_length - 1) ? 
	    first + 9 : hsession->candidate_length - 1;
    n = last - first + 1;

    draw = (IMLookupDrawCallbackStruct *) s->If->m->iml_new(s,
		        sizeof(IMLookupDrawCallbackStruct));
    draw->index_of_first_candidate = 0;
    draw->index_of_last_candidate = n - 1;
    draw->n_choices = n;
    draw->title = imtext_new(s, (UTFCHAR*)&hsession->candidate_char, 1,
	    			feedbacklist_new(s, 1)); 
    draw->choices = (IMChoiceObject *) s->If->m->iml_new(s,
            draw->n_choices * sizeof(IMChoiceObject));

    for (i = 0; i < n; i++) {
	item = candidate_table.data[hsession->candidate_index][first + i + 1];
	len = utfchar_strlen(item->comment) + 2;
	buf = (UTFCHAR*) s->If->m->iml_new(s, sizeof(UTFCHAR) * len);
	buf[0] = item->ch;
	buf[1] = ' ';
	memcpy(&buf[2], item->comment, (len - 2) * sizeof(UTFCHAR));
	if (first + i == hsession->candidate)
	    text = imtext_new(s, buf, len, feedbacklist_new(s, len));
	else
	    text = imtext_new(s, buf, len, feedbacklist_new_reverse(s, len));
	draw->choices[i].value = text;

	if (i == 9)
	    label = '0';
	else
	    label = '1' + i;
	text = imtext_new(s, &label, 1, feedbacklist_new(s, 1));
	draw->choices[i].label = text;
    }
    draw->max_len = 1;
    draw->index_of_current_candidate = hsession->candidate - first;

    return draw;
}

static void
hangul_lookup_prev(iml_session_t *s)
{
    iml_inst *lp;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    if (hsession->candidate > 0)
	hsession->candidate--;
    draw = hangul_lookup_draw_new(s);

    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_next(iml_session_t *s)
{
    iml_inst *lp;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    if (hsession->candidate < hsession->candidate_length - 1)
	hsession->candidate++;
    draw = hangul_lookup_draw_new(s);

    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_prev_page(iml_session_t *s)
{
    iml_inst *lp;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    hsession->candidate -= 10;
    if (hsession->candidate < 0)
	hsession->candidate = 0;
    draw = hangul_lookup_draw_new(s);

    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_next_page(iml_session_t *s)
{
    iml_inst *lp;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    hsession->candidate += 10;
    if (hsession->candidate >= hsession->candidate_length)
	hsession->candidate = hsession->candidate_length - 1;
    draw = hangul_lookup_draw_new(s);

    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_start(iml_session_t *s, IMKeyEventStruct *key)
{
    iml_inst *lp;
    IMLookupStartCallbackStruct *start;
    IMLookupDrawCallbackStruct *draw;
    Session *hsession = (Session*) s->specific_data;

    /* find candidate */
    hsession->candidate_char = get_preedit_char(s);
    hsession->candidate_index = candidate_table_get_index(hsession->candidate_char);
    if (hsession->candidate_index < 0) {
	/* fail to find candidate */
    	hsession->candidate_char = 0;
    	hsession->candidate_index = 0;
	return;
    }

    hsession->candidate_length = candidate_table.data[hsession->candidate_index][0]->len;
    hsession->candidate = 0;
    printf("hanja: %x (%d)\n",
	    candidate_table.data[hsession->candidate_index][0]->ch,
	    hsession->candidate_length);

    hsession->state = HANGUL_STATE_HANJA;

    /* send Lookup 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));
    start->IMPreference->choice_per_window = 10;
    start->IMPreference->ncolumns = 1;
    start->IMPreference->nrows = 10;
    start->IMPreference->drawUpDirection = DrawUpVertically;
    start->IMPreference->whoOwnsLabel = IMOwnsLabel;

    lp = s->If->m->iml_make_lookup_start_inst(s, start);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);

    draw = hangul_lookup_draw_new(s);

    lp = s->If->m->iml_make_lookup_draw_inst(s, draw);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_done(iml_session_t *s)
{
    iml_inst *lp;
    Session *hsession = (Session*) s->specific_data;

    hsession->candidate_char = 0;
    hsession->candidate_index = 0;
    hsession->candidate_length = 0;
    hsession->candidate = 0;
    hsession->state = HANGUL_STATE_HANGUL;

    lp = s->If->m->iml_make_lookup_done_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

static void
hangul_lookup_commit(iml_session_t *s)
{
    UTFCHAR ch;
    Session *hsession = (Session*) s->specific_data;

    ch = candidate_table.data[hsession->candidate_index][hsession->candidate + 1]->ch;
    hangul_lookup_done(s);
    hangul_hsession_clear(hsession);
    hangul_preedit_update(s);
    hangul_commit_utfchar(s, ch);
    hsession->state = HANGUL_STATE_HANGUL;
}

static void
hangul_lookup_commit_nth(iml_session_t *s, int n)
{
    int first;
    UTFCHAR ch;
    Session *hsession = (Session*) s->specific_data;

    first = hsession->candidate - (hsession->candidate % 10);
    ch = candidate_table.data[hsession->candidate_index][first + n + 1]->ch;
    hangul_lookup_done(s);
    hangul_hsession_clear(hsession);
    hangul_preedit_update(s);
    hangul_commit_utfchar(s, ch);
    hsession->state = HANGUL_STATE_HANGUL;
}

static void
get_striped(char *src, char *dest)
{
    char *p;

    /* ignore white space from start */
    while (isspace(*src))
	src++;

    /* ignore white space at the end */
    for (p = src; !isspace(*p); p++)
	continue;
    *p = 0;

    strcpy(dest, src);
}

static void
get_key_value(char *buf, char *key, char *value)
{
    char *p;
    char *end = NULL;

    /* we silently ignore that has no '=' mark */
    p = strchr(buf, '=');
    if (p == NULL)
	return;

    p = strchr(buf, '#');
    if (p != NULL)
	*p = 0;

    p = strtok_r(buf, "=", &end);
    if (p != NULL)
	get_striped(p, key);

    p = strtok_r(NULL, "=", &end);
    if (p != NULL)
	get_striped(p, value);
}

static void
hangul_load_config(void)
{
    const char *conf_filename = HANGUL_CONFIG_FILENAME;
    FILE *conf_file;
    char key[64];
    char value[512];
    char buf[1024];
    char *p;

    /* default values */
    config.keyboard = keyboard_map_2;
    config.composer = hangul_composer_2;

    conf_file = fopen(conf_filename, "r");
    if (conf_file == NULL) {
	perror(conf_filename);
	return;
    }

    while (!feof(conf_file)) {
	p = fgets(buf, sizeof(buf), conf_file);

	if (p == NULL)
	    break;

	/* check comment */
	if (buf[0] == '#')
	    continue;

	buf[1023] = 0;
	get_key_value(buf, key, value);

	if (strcmp(key, "keyboard") == 0) {
	    if (strcmp(value, "2") == 0) {
		config.keyboard = keyboard_map_2;
		config.composer = hangul_composer_2;
	    } else if (strcmp(value, "32") == 0) {
		config.keyboard = keyboard_map_32;
		config.composer = hangul_composer_3;
	    } else if (strcmp(value, "39") == 0) {
		config.keyboard = keyboard_map_390;
		config.composer = hangul_composer_3;
	    } else if (strcmp(value, "3f") == 0) {
		config.keyboard = keyboard_map_3final;
		config.composer = hangul_composer_3;
	    } else if (strcmp(value, "3s") == 0) {
		config.keyboard = keyboard_map_3sun;
		config.composer = hangul_composer_3;
	    }
	}
    }

    fclose(conf_file);
}

static void
hangul_load_candidate_table(void)
{
    candidate_table_load(&candidate_table, CANDIDATE_TABLE_FILENAME);
}

void
hangul_le_init()
{
    hangul_load_config();
    hangul_load_candidate_table();
}

void
hangul_desktop_init(iml_desktop_t *desktop)
{
    Desktop *desktop_data;

    if (desktop->specific_data != NULL)
	free(desktop->specific_data);

    desktop_data = (Desktop *) calloc(1, sizeof(Desktop));
    desktop_data->state = HANGUL_STATE_ENGLISH;
    desktop->specific_data = desktop_data;
}

void
hangul_desktop_finalize(iml_desktop_t *desktop)
{
    if (desktop->specific_data != NULL) {
	free(desktop->specific_data);
	desktop->specific_data = NULL;
    }
}

void
hangul_session_init(iml_session_t *s)
{
    Session *hsession;

    if (s->specific_data != NULL)
	free(s->specific_data);

    hsession = (Session *)calloc(1, sizeof(Session));
    hsession->state = HANGUL_STATE_ENGLISH;

    hsession->keyboard = config.keyboard;
    hsession->composer = config.composer;
    hsession->compose_table_size = sizeof(compose_table_default) 
				/ sizeof(compose_table_default[0]);
    hsession->compose_table = compose_table_default;

    hsession->stack_index = -1;

    hsession->candidate_char = 0;
    hsession->candidate_index = 0;
    hsession->candidate_length = 0;
    hsession->candidate = 0;

    hsession->rrv = NULL;

    s->specific_data = hsession;
}

void
hangul_session_finalize(iml_session_t *s)
{
    if (s->specific_data != NULL) {
	free(s->specific_data);
	s->specific_data = NULL;
    }
}

IMText *
hangul_session_reset(iml_session_t *s)
{
    int len = 0;
    UTFCHAR buf[12];
    Session *hsession = (Session*) s->specific_data;

    if (hangul_is_empty(hsession))
	return NULL;

    len = hangul_get_commit_string(s, buf, sizeof(buf));
    hangul_hsession_clear(hsession);
    hangul_preedit_update(s);
    return imtext_new(s, buf, len, feedbacklist_new(s, len));
}

static UTFCHAR *
get_status_string(iml_session_t *s)
{
    static UTFCHAR status_str_english[] = { '\0' };
    static UTFCHAR status_str_hangul[] = { 0xd55c, 0xae00 };
    Session *hsession = (Session*) s->specific_data;
    
    switch (hsession->state) {
    case HANGUL_STATE_NONE:
    case HANGUL_STATE_ENGLISH:
	return status_str_english;
    case HANGUL_STATE_HANGUL:
    case HANGUL_STATE_HANJA:
	return status_str_hangul;
    }
    return status_str_english;
}

void
hangul_prep(iml_session_t *s)
{
    Session *hsession = (Session*) s->specific_data;
    hsession->rrv = NULL;
}

void
hangul_exec(iml_session_t *s)
{
    Session *hsession = (Session*) s->specific_data;
    if (hsession->rrv != NULL)
	s->If->m->iml_execute(s, &hsession->rrv);
}

void
hangul_foward_keyevent(iml_session_t *s, IMKeyEventStruct *key)
{
    iml_inst *lp;
    Session *hsession = (Session*) s->specific_data;

    lp = s->If->m->iml_make_keypress_inst(s, key);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

void
hangul_focus(iml_session_t *s)
{
    iml_inst *lp;
    int len;
    IMText *text;
    UTFCHAR *status_str;
    Session *hsession = (Session*) s->specific_data;

    lp = s->If->m->iml_make_status_start_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
    status_str = get_status_string(s);
    len = utfchar_strlen(status_str);
    text = imtext_new(s, status_str, len, feedbacklist_new(s, len));
    lp = s->If->m->iml_make_status_draw_inst(s, text);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

void
hangul_unfocus(iml_session_t *s)
{
    iml_inst *lp;
    Session *hsession = (Session*) s->specific_data;

    lp = s->If->m->iml_make_status_done_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

void
hangul_conversion_on(iml_session_t *s)
{
    iml_inst *lp;
    int len;
    IMText *text;
    UTFCHAR *status_str;
    Session *hsession = (Session*) s->specific_data;

    hsession->state = HANGUL_STATE_HANGUL;
    if (s->desktop && s->desktop->specific_data)
	((Desktop*)s->desktop->specific_data)->state = HANGUL_STATE_HANGUL;

    lp = s->If->m->iml_make_start_conversion_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
    lp = s->If->m->iml_make_preedit_start_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);

    status_str = get_status_string(s);
    len = utfchar_strlen(status_str);
    text = imtext_new(s, status_str, len, feedbacklist_new(s, len));
    lp = s->If->m->iml_make_status_draw_inst(s, text);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

void
hangul_conversion_off(iml_session_t *s)
{
    iml_inst *lp;
    int len;
    IMText *text;
    UTFCHAR *status_str;
    Session *hsession = (Session*) s->specific_data;

    hangul_commit (s);

    hsession->state = HANGUL_STATE_ENGLISH;
    if (s->desktop && s->desktop->specific_data)
	((Desktop*)s->desktop->specific_data)->state = HANGUL_STATE_ENGLISH;

    status_str = get_status_string(s);
    len = utfchar_strlen(status_str);
    text = imtext_new(s, status_str, len, feedbacklist_new(s, len));
    lp = s->If->m->iml_make_status_draw_inst(s, text);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);

    lp = s->If->m->iml_make_preedit_done_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
    lp = s->If->m->iml_make_end_conversion_inst(s);
    s->If->m->iml_link_inst_tail(&hsession->rrv, lp);
}

Bool
hangul_composer(iml_session_t *s, IMKeyEventStruct *key)
{
    Session *hsession = (Session*) s->specific_data;

    if (hsession->state == HANGUL_STATE_HANJA) {
	switch (key->keyCode) {
	case IM_VK_0:
	    hangul_lookup_commit_nth(s, 9);
	    break;
	case IM_VK_1:
	case IM_VK_2:
	case IM_VK_3:
	case IM_VK_4:
	case IM_VK_5:
	case IM_VK_6:
	case IM_VK_7:
	case IM_VK_8:
	case IM_VK_9:
	    hangul_lookup_commit_nth(s, key->keyCode - IM_VK_1);
	    break;
	case IM_VK_ENTER:
	    hangul_lookup_commit(s);
	    break;
	case IM_VK_ESCAPE:
	    hangul_lookup_done(s);
	    break;
	case IM_VK_UP:
	case IM_VK_K:
	    hangul_lookup_prev(s);
	    break;
	case IM_VK_DOWN:
	case IM_VK_J:
	    hangul_lookup_next(s);
	    break;
	case IM_VK_LEFT:
	case IM_VK_H:
	    hangul_lookup_prev_page(s);
	    break;
	case IM_VK_RIGHT:
	case IM_VK_L:
	case IM_VK_SPACE:
	    hangul_lookup_next_page(s);
	    break;
	default:
	    break;
	}
	return True;
    }
    
    if (hangul_is_candidate_key(key)) {
	hangul_lookup_start(s, key);
	return True;
    }

    /* on shift key we silently ignore it */
    if (key->keyCode == IM_VK_SHIFT)
	return False;

    /* ignore modifier on keys */
    if (key->modifier & (IM_CTRL_MASK | IM_ALT_MASK | IM_META_MASK))
	return False;

    if (hsession->composer != NULL)
    	return hsession->composer(s, key);

    return False;
}

/* vim: set ts=8 sw=4 : */
