// Anthy 用のplugin
//
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include "anthy.xpm"
#include "jmode.h"
#include "anthyconv.h"


//input mode
#define INPUT_MODE_RAW 0
#define INPUT_MODE_HIRA 1
#define INPUT_MODE_KATA 2
#define INPUT_MODE_WIDE 3

static atom_t A_extend_segment, A_shorten_segment;
static atom_t A_anthy_personality;

static struct anthy_input_config *config;

static void init_key_atom()
{
    A_extend_segment = get_atom_by_name("anthy_extend_segment");
    A_shorten_segment = get_atom_by_name("anthy_shorten_segment");
    A_anthy_personality = get_atom_by_name("AnthyPersonality");

    bind_str_to_atom("C-o", A_extend_segment);
    bind_str_to_atom("S-Right", A_extend_segment);
    bind_str_to_atom("S-Left", A_shorten_segment);
    bind_str_to_atom("C-i", A_shorten_segment);
}

static void
decode_arg(char *dst, char *src)
{
    int c = src[0];
    if (c == '\\') {
	int d = src[1];
	src++;src++;
	switch (d) {
	case '\\':
	    *dst = '\\';
	    dst++;
	    break;
	case 'x':
	case 'X':
	    {
		char buf[10];
		int num;
		strcpy(buf, src);
		sscanf(buf, "%x", &num);
		unsigned char buf2[5];
		buf2[0] = (num>>8) & 255;
		buf2[1] = num & 255;
		buf2[2] = 0;
		sprintf(dst, "%s", (char *)buf2);
		return ;
	    }
	    break;
	}
    } else {
	*dst = c;
	if (!c) {
	    return ;
	}
	dst ++;
	src ++;
    }
    decode_arg(dst, src);
}

static void
do_edit_rk_config(int m, char *from, char *to, char *follow)
{
    char buf[10];
    decode_arg(buf, to);

    anthy_input_edit_rk_config(config, m, from, buf, follow);
}

static void
anthy_conf_hook(char *str)
{
    if (!config) {
	config = anthy_input_create_config();
	anthy_input_clear_rk_config(config, 0);
    }
    if (strlen(str) > 20) {
	return ;
    }
    char buf1[20], buf2[20], buf3[20], buf4[20];
    int c, m;
    c = sscanf(str, "%s %s %s %s", buf1, buf2, buf3, buf4);
    if (c < 3) {
	return ;
    }
    if (buf1[0] == 'h') {
	m = 2;
    } else {
	m = 3;
    }
    if (c == 3) {
	do_edit_rk_config(m, buf2, buf3, 0);
    } else {
	do_edit_rk_config(m, buf2, buf3, buf4);
    }
    anthy_input_change_config(config);
}

void init_preconf_kkconv()
{
    add_conf_hook("anthy", anthy_conf_hook);
}

bool init_conv()
{
    // もし初期化で失敗したら、この関数はfalseを返す。
    AnthyConv *anthy = new AnthyConv();
    init_key_atom();
    if (anthy_input_init()) {
	delete anthy;
	return false;
    }
    if (atom_t a = get_bound_atoms(A_anthy_personality, 0)) {
	anthy_input_set_personality(get_atom_name(a));
    }
    register_kkconv(anthy);
    if (!config) {
	config = anthy_input_create_config();
    }
    return true;
}

AnthyConv::~AnthyConv()
{
    // 終了の処理
}

KKContext *AnthyConv::createContext(XimIC *ic)
{
    // 入力コンテキストを作る。
    AnthyContext *anthy;
    anthy = new AnthyContext(this, ic);
    return anthy;
}

char **AnthyConv::getIcon()
{
    // toolbarに出てくるアイコンを返す。
    return anthy_xpm;
}

void AnthyConv::onPushIcon()
{
    // toolbarの押された時に呼ばれる。
}

char *AnthyConv::getModeName(int num)
{
    switch (num) {
    case INPUT_MODE_RAW: return "a";
    case INPUT_MODE_HIRA: return "あ";
    case INPUT_MODE_KATA: return "ア";
    case INPUT_MODE_WIDE: return "Ａ";
    }
    return NULL;
}

/////////////////////////////////////
//
AnthyContext::AnthyContext(AnthyConv *, XimIC *ic) : KKContext(ic)
{
    mAic = anthy_input_create_context(config);
    mIsOn = false;
    mPedit = NULL;
    mCands = NULL;
}

AnthyContext::~AnthyContext()
{
    // 使わなくなったコンテクストを解放
    anthy_input_free_context(mAic);
    if (mCands) {
	delete mCands;
    }
}

int AnthyContext::getMode()
{
    if (mIsOn) {
	switch (anthy_input_get_selected_map(mAic)) {
	case ANTHY_INPUT_MAP_HIRAGANA:
	    return INPUT_MODE_HIRA;
	case ANTHY_INPUT_MAP_KATAKANA:
	    return INPUT_MODE_KATA;
	}
    }
    return INPUT_MODE_RAW;
}

void AnthyContext::OnUpdatePe(pe_stat *pe)
{
    // preeditが更新される時に呼ばれる。
    pe->clear();
    if (!mIsOn) {
	return ;
    }
    struct anthy_input_segment *seg;
    for (seg = mPedit->segment; seg; seg = seg->next) {
	char *str = "^";
	if (seg->str) {
	    str = seg->str;
	}
	if (seg->flag & ANTHY_INPUT_SF_CURSOR) { 
	    pe->new_segment(PE_REVERSE);
	} else {
	    pe->new_segment(PE_UNDERLINE);
	}
	jstring_t js;
	str_to_jstring(&js, str);
	jstring_t::iterator it;
	for (it = js.begin(); it != js.end(); it++) {
	    pe->push_cchar(*it);
	}
    }
    pe->cands = mCands;
}

int AnthyContext::procRawMode(keyState *e)
{
    if (e->is_bind_to(A_hira_mode)) {
	mIsOn = true;
	return UPDATE_MODE;
    }
    return COMMIT_RAW;
}

void AnthyContext::updateCandidates()
{
    struct anthy_input_segment *seg;
    struct anthy_input_segment *cseg = NULL;
    if (mCands) {
	delete mCands;
	mCands = NULL;
    }
    for (seg = mPedit->segment; seg; seg = seg->next) {
	if (seg->flag & (ANTHY_INPUT_SF_ENUM | ANTHY_INPUT_SF_ENUM_REVERSE)) {
	    cseg = seg;
	}
    }
    if (!cseg) {
	return ;
    }
    mCands = new Candidates;
    mCands->nth = cseg->cand_no;
    int i;
    for (i = 0; i < cseg->nr_cand; i++) {
	seg = anthy_input_get_candidate(mAic, i);
	jstring_t js;
	str_to_jstring(&js, seg->str);
	mCands->cands.push_back(js);
	anthy_input_free_segment(seg);
    }
    seg = anthy_input_get_candidate(mAic, mCands->nth);
    anthy_input_free_segment(seg);
}

int AnthyContext::procCommandKey(keyState *e)
{
    bool pe = true;
    
    if (!mPedit || !mPedit->segment) {
	pe = false;
    }
    if (e->is_bind_to(A_latin_mode) && !pe) {
	mIsOn = false;
	return UPDATE_MODE;
    }
    if (!pe && e->char_code() == 0) {
	return COMMIT_RAW;
    }
    if (e->is_bind_to(A_commit) || e->is_bind_to(A_return)) {
	anthy_input_commit(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_do_conv)) {
	anthy_input_space(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_cancel) ||
	(e->is_bind_to(A_delete_back) &&
	 ((anthy_input_get_state(mAic) == ANTHY_INPUT_ST_CONV) ||
	  (anthy_input_get_state(mAic) == ANTHY_INPUT_ST_CSEG)))) {
	anthy_input_quit(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_go_line_head)) {
	anthy_input_beginning_of_line(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_go_line_end)) {
	anthy_input_end_of_line(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_go_right)) {
	anthy_input_move(mAic, 1);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_go_left)) {
	anthy_input_move(mAic, -1);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_delete_here)) {
	anthy_input_erase_next(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_delete_back)) {
	anthy_input_erase_prev(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_extend_segment)) {
	anthy_input_resize(mAic, 1);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_shorten_segment)) {
	anthy_input_resize(mAic, -1);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_next_candidate)) {
	anthy_input_next_candidate(mAic);
	return DO_NOTHING;
    }
    if (e->is_bind_to(A_prev_candidate)) {
	anthy_input_prev_candidate(mAic);
	return DO_NOTHING;
    }
    return ANTHY_PROC_MASK;
}

int AnthyContext::procInputMode(keyState *e)
{
    int res;

    res = procCommandKey(e);
    if (res & ANTHY_PROC_MASK) {
	int cc;
	cc = e->char_code();
	if (cc) {
	    anthy_input_key(mAic, cc);
	}
	res = DO_NOTHING;
    }
    updateAnthyPreedit();
    if (mPedit->commit) {
	jstring_t js;
	str_to_jstring(&js, mPedit->commit);
	commit_jstring(&js);
    }
    update_preedit();
    return res & KK_PUSHKEY_MASK;
}

void AnthyContext::updateAnthyPreedit()
{
    if (mPedit) {
	anthy_input_free_preedit(mPedit);
	mPedit = NULL;
    }
    mPedit = anthy_input_get_preedit(mAic);
    updateCandidates();
}

int AnthyContext::pushKey(keyState *e)
{
    if (e->is_modifier()) {
	return DO_NOTHING;
    }
    if (!mIsOn) {
	return procRawMode(e);
    }
    return procInputMode(e);
}

void AnthyContext::setMode(int m)
{
    if (mIsOn) {
	anthy_input_quit(mAic);
    }
    mIsOn = true;
    switch (m) {
    case 0:
	mIsOn = false;
	break;
    case 1:
	anthy_input_map_select(mAic, ANTHY_INPUT_MAP_HIRAGANA);
	break;
    case 2:
	anthy_input_map_select(mAic, ANTHY_INPUT_MAP_KATAKANA);
	break;
    case 3:
	anthy_input_map_select(mAic, ANTHY_INPUT_MAP_WALPHABET);
	break;
    }
}

jstring_t *AnthyContext::clear()
{
    // コンテキストを初期化
    return 0;
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
