// KKConv 仮名漢字変換

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

#include <ctype.h>
#include <gdk/gdk.h>
#include "xim.h"
#include "convdisp.h"
#include "kkconv.h"
#include "rkconv.h"
// rkmap.h は一度だけどこかでインクルード
#include "rkmap.h"

#ifndef __GNUC__
# ifdef HAVE_ALLOCA_H
#  include <alloca.h>
# endif
#endif

// キャラクタコード32から、
//このソースはEUCで書かれている。
static char *ascii_wide_tab[]={
    0,"！","”","＃","＄","％","＆","’",
    "（","）","＊","＋","，","−","．","／",
    "０","１","２","３","４","５","６","７",
    "８","９","：","；","＜","＝","＞","？",
    "＠","Ａ","Ｂ","Ｃ","Ｄ","Ｅ","Ｆ","Ｇ",
    "Ｈ","Ｉ","Ｊ","Ｋ","Ｌ","Ｍ","Ｎ","Ｏ",
    "Ｐ","Ｑ","Ｒ","Ｓ","Ｔ","Ｕ","Ｖ","Ｗ",
    "Ｘ","Ｙ","Ｚ","［","＼","］","＾","＿",
    "‘","ａ","ｂ","ｃ","ｄ","ｅ","ｆ","ｇ",
    "ｈ","ｉ","ｊ","ｋ","ｌ","ｍ","ｎ","ｏ",
    "ｐ","ｑ","ｒ","ｓ","ｔ","ｕ","ｖ","ｗ",
    "ｘ","ｙ","ｚ","｛","｜","｝","〜",
};

/*
  modeがINPUT_MODE_WIDEのときは英数で全角化する。
  INPUT_MODE_HIRAのときは日本語の全角にする。
 */
cchar ascii_to_wide(cchar a)
{
    if (a < 256 && isprint(a)) {
	unsigned char *b = (unsigned char *)ascii_wide_tab[a-32];
	if (!b) {
	    return 0;
	}
	cchar c;
	c = ((((int)b[0])<<8)+(int)b[1])&0x7f7f;
	return c;
    }
    return 0;
}

void hirakana_conv(jstring_t *dst, jstring_t *s)
{
    jstring_t::iterator i,j;
    for (i = s->begin(); i != s->end(); i++) {
        cchar c, d;
        c = *i;
        j = i;
        j++;
        if (j == s->end()) {
            d = 0;
        } else {
            d = *j;
        }

        if (c >= 0x2400 && c < 0x2600) {
            if (c < 0x24ff) {
                // 平仮名から片仮名
                if (c == 0x2426 && d == 0x212b) {
                    // う゛
                    c = 0x2574;
                    i++;
                } else {
                    c += 256;
                }
            } else {
                // 片仮名から平仮名
                if (c == 0x2574) {
                    dst->push_back(0x2426);
                    c = 0x212b;
                } else {
                    c -= 256;
                }
            }
        }
        dst->push_back(c);
    }
}

int jstring_get_ctext_len(jstring_t *s)
{
    jstring_t::iterator i;
    bool jp_mode;
    int len;
    int cur;
    
    jp_mode = false;
    len = 0;

    for (i = s->begin(); i != s->end(); i++) {
	cur = *i;
	if (!(cur & 0xff00)) {
	    // 2byte文字ではない
	    if (jp_mode) {
		jp_mode = false;
		len += 4;
	    } else {
		len += 1;
	    }
	} else {
	    // 2byte文字だ、
	    if (jp_mode) {
		len += 2;
	    } else {
		len += 6;
		jp_mode = true;
	    }
	}
    }
    if (jp_mode) {
	len += 3;
    }
    return len;
}

char *jstring_to_ctext(jstring_t *s)
{
    int len;
    char *t;
    
    len = jstring_get_ctext_len(s);
    t = (char *)malloc(len+1);
    t[len] = 0;
    
    jstring_t::iterator i;
    bool jp_mode=false;
    int cur,j=0;
    for (i = s->begin(); i != s->end(); i++) {
	cur = *i;
	if (!(cur & 0xff00)) {
	    // 1byte文字
	    if (jp_mode) {
		t[j++] = 0x1b;
		t[j++] = 0x28;
		t[j++] = 0x42;
		jp_mode = false;
	    }
	    t[j++] = cur & 0xff;
	} else {
	    if (!jp_mode) {
		jp_mode = true;
		t[j++] = 0x1b;
		t[j++] = 0x24;
		t[j++] = 0x29;
		t[j++] = 0x42;
	    }
	    t[j++] = (cur >> 8)|0x80;
	    t[j++] = (cur & 0xff)|0x80;
	}
    }
    if (jp_mode) {
	t[j++] = 0x1b;
	t[j++] = 0x28;
	t[j++] = 0x42;
    }
    return t;
}

char *jstring_to_str(jstring_t *s)
{
    jstring_t::iterator i;
    int l=0;
    char *c;
    for (i = s->begin(); i!= s->end(); i++) {
        l++;
        if (*i > 256) {
            l ++;
        }
    }
    c = (char *)malloc(l+1);
    c[l] = 0;
    l = 0;
    for (i = s->begin() ; i!= s->end(); i++) {
        if (*i < 256) {
            c[l] = *i;
            l++;
        } else {
            c[l] = ((*i)>>8)|0x80;
            c[l+1] = ((*i)&0xff)|0x80;
            l+=2;
        }
    }
    return c;
}

// XXX 動作は未検証
void ctext_to_jstring(char *c, jstring_t *s)
{
    bool jp_mode = false;
    while (*c) {
        if (jp_mode) {
            if (*c != 0x1b) {
                s->push_back(c[0]*256+c[1]);
            } else {
                jp_mode = false;
                c++;c++;c++;
            }
        } else {
            if (*c != 0x1b) {
                s->push_back(*c);
                c++;
            } else {
                jp_mode = true;
                c++;c++;c++;
            }
        }
    }
}

void print_jstring(jstring_t *s)
{
    jstring_t::iterator i;
    printf("length=%d : ", s->size());
    int ch;
    char buf[3];
    for (i = s->begin(); i != s->end(); i++) {
        ch = *i;
	buf[1]=0;
	buf[2]=0;
        if (ch < 256) {
	    buf[0]=ch;
        } else {
	    buf[1]=(ch & 255)|0x80;
	    buf[0]=((ch>>8)&255)|0x80;
        }
	printf(buf);
    }
    printf("\n");
}

void erase_jstring(jstring_t *s)
{
    s->erase(s->begin(), s->end());
}

void append_jstring(jstring_t *d, jstring_t *s)
{
    jstring_t::iterator i;
    for (i = s->begin(); i !=s->end(); i++) {
        d->push_back(*i);
    }
}

// EUCとJISの両方に対応
void str_to_jstring(jstring_t *d, char *s)
{
    int i,len;
    bool inJis= false;
    len = strlen(s);
    for (i = 0; i < len; i++) {
	if (s[i] == 0x1b) {
	    if (s[i+1] == 0x24 && s[i+2]== 0x42) {
		inJis = true;
	    } else if (s[i+1] == 0x28 && s[i+2] == 0x42) {
		inJis = false;
	    }
	    i += 2;
	} else if(inJis || (s[i] & 0x80)) {
	    //2バイト文字
	    cchar ch;
	    ch = ((s[i]&0x7f)<<8)|(s[i+1]&0x7f);
	    i++;
	    d->push_back(ch);
	} else {
	    d->push_back(s[i]);
	}
    }
}

//カット&ペースト
void paste_from_global_buffer(jstring_t *s)
{
}

void copy_to_global_buffer(jstring_t *s)
{
    char *buf;
    buf = jstring_to_str(s);
    free(buf);
    XFlush(gDpy);
}
//
KKConv *current_conv;

KKConv *init_kkconv()
{
    init_conv();
    return current_conv;
}

void register_kkconv(KKConv *conv)
{
    current_conv = conv;
}

void free_kkconv()
{
    if (current_conv) {
        delete current_conv;
    }
}

KKContext *createKKContext(XimIC *ic)
{
    KKContext *c;
    c = NULL;
    if (current_conv) {
	c = current_conv->createContext(ic);
    }
    return c;
}

char **getKKIcon()
{
    char **c;
    c = NULL;
    if (current_conv) {
        c = current_conv->getIcon ();
    }
    return c;
}

// tool barの仮名漢字変換のボタンが押された。
void onPushIcon()
{
    char **c=0;
    if (current_conv) {
	c = current_conv->getIcon ();
	if (c) {
	    current_conv->onPushIcon ();
	}
    }
}

//
// KKContextのメソッド
KKContext::KKContext(XimIC *ic)
{
    m_ic = ic;
    m_pe = new pe_stat(this);
    m_convdisp = 0;
}

KKContext::~KKContext()
{
    if (m_convdisp) {
	m_convdisp->set_pe(0);
    }
    delete m_pe;
}

void KKContext::update_preedit()
{
    OnUpdatePe(m_pe);
    if (m_convdisp) {
        m_convdisp->update_preedit();
    }
}

void KKContext::set_convdisp(Convdisp *c)
{
    m_convdisp = c;
    if (m_convdisp) {
	m_convdisp->set_pe(m_pe);
    }
}

void KKContext::commit_jstring(jstring_t *s)
{
    m_ic->commit_jstring(s);
}

void KKContext::candidate_selected(int n)
{
}

bool KKContext::extra_input(jstring_t *s)
{
    return false;
}

XimIC *KKContext::get_ic()
{
    return m_ic;
}

keyState::keyState(keyEventX *x)
{
    atom = 0;
    m_key_code = x->key_sym;
    m_state = x->state;
    m_bPush = x->press;
    m_char_code = conv_keysym_to_charcode(x->key_sym);
}

keyState::keyState(GdkEventKey *g)
{
    atom = 0;
    m_state = conv_gdk_state((int)g->state);
    if (g->type == GDK_KEY_RELEASE) {
        m_bPush = false;
    } else {
        m_bPush = true;
    }
    m_key_code = g->keyval;
    m_char_code = conv_keysym_to_charcode(g->keyval);
}

atom_t keyState::get_atom()
{
    char *m,*k;
    char buf[32];
    if (atom) {
        return atom;
    }
    m = modifier_to_str();
    k = gdk_keyval_name(m_key_code);
    if (m) {
        sprintf(buf,"%s-%s",m,k);
    } else {
        sprintf(buf,"%s",k);
    }

    atom = get_atom_by_name(buf);
    return atom;
}

int keyState::char_code()
{
    return m_char_code;
}

int keyState::key_code()
{
    return m_key_code;
}

bool keyState::is_modifier()
{
    // ugly, but this works
    if (m_key_code >= XK_Shift_L && m_key_code <= XK_Hyper_R) {
	return true;
    }
    return false;
}

int keyState::to_lower()
{
    if (m_char_code < 256) {
	return tolower(m_char_code);
    }
    return m_char_code;
}

bool keyState::modifier(int type)
{
    return type & m_state;
}

bool keyState::is_push()
{
    return m_bPush;
}

void keyState::print()
{
    printf("key code=%x,char_code=%x,modifier=%d,name=%s.\n",
	   m_key_code, m_char_code,
	   m_state, get_atom_name(get_atom()));
}

bool keyState::is_alpha()
{
    return isalpha(m_char_code);
}

bool keyState::is_bind_to(atom_t a)
{
    return ::is_bind_to(get_atom(),a);
}

int keyState::conv_gdk_state(int s)
{
    int r=0;
    if (s & GDK_SHIFT_MASK) {
        r |= SHIFT_KEY;
    }
    if (s & GDK_LOCK_MASK) {
        r |= CAPS_LOCK;
    }
    if (s & GDK_CONTROL_MASK) {
        r |= CTRL_KEY;
    }
    // XXX KANA_LOCKはGDKのどれにあたるのでしょうか？
    return r;
}

int keyState::conv_keysym_to_charcode(int k)
{
    if (k > 127 || k < 32 || (m_state&CTRL_KEY)) {
        return 0;
    }
    return k;
}

char *keyState::modifier_to_str()
{
    static char buf[8];
    int i = 0;
    if (m_state & SHIFT_KEY) {
        buf[i] = 'S';
        i++;
    }
    if (m_state & CTRL_KEY) {
        buf[i] = 'C';
        i++;
    }
    if (m_state & ALT_KEY) {
        buf[i] = 'A';
        i++;
    }
    if (i == 0) {
        return 0;
    }
    buf[i] = 0;
    return buf;
}

Candidates::Candidates()
{
    nth = 0;
    opCount = 0;
}

void Candidates::clear()
{
    nth = 0;
    cands.erase(cands.begin(),cands.end());
}

int Candidates::proc_key(keyState *k,jstring_t *s)
{
    int size = (int)cands.size();
    if (k->is_bind_to(A_next_candidate) && nth < size) {
	nth ++;
	opCount ++;
	if (nth == size) {
	    return CAND_EXHAUST;
	}
	return CAND_NOP;
    }
    if (k->is_bind_to(A_go_up)) {
	if (nth > 0) {
	    opCount ++;
	    nth --;
	}
	return CAND_NOP;
    }

    if (k->is_bind_to(A_go_down)) {
	if (nth < size-1) {
	    opCount ++;
	    nth++;
	}
	return CAND_NOP;
    }

    if (k->is_bind_to(A_page_up) && nth > 0) {
	nth -= CAND_COUNT;
	opCount ++;
	if (nth < 0) {
	    nth = 0;
	}
	return CAND_NOP;
    }

    if (k->is_bind_to(A_page_down)) {
	nth += CAND_COUNT;
	opCount ++;
	if (nth > size-1) {
	    nth = size-1;
	}
	return CAND_NOP;
    }

    if (k->is_bind_to(A_cancel)) {
	return CAND_CANCEL;
    }
    if (s) {
	*s = cands[nth];
    }
    return CAND_COMMIT;
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
