/*

  Copyright (c) 2003,2004 uim Project http://uim.freedesktop.org/

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
  3. Neither the name of authors nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  SUCH DAMAGE.
*/

#include <ctype.h>
#include <X11/Xatom.h>
#include "xim.h"
#include "convdisp.h"
#include "canddisp.h"
#include "ximserver.h"
#include <X11/Xutil.h>

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

extern int lib_uim_fd;
extern Atom xim_servers;
InputContext *InputContext::mFocusedContext = NULL;

// tables
static input_style input_style_tab_with_over_the_spot[] = {
    {XIMPreeditNothing|XIMStatusNothing, IS_ROOT_WINDOW},
    //{XIMPreeditPosition|XIMStatusArea, IS_OVER_THE_SPOT},// emacs
    {XIMPreeditPosition|XIMStatusNothing, IS_OVER_THE_SPOT},
    //{XIMPreeditCallbacks|XIMStatusCallbacks, IS_ON_THE_SPOT},// OOo
    //{XIMPreeditArea|XIMStatusArea, IS_ROOT_WINDOW},
    {XIMPreeditCallbacks|XIMStatusNothing, IS_ON_THE_SPOT},
    {0, 0},
};
static input_style input_style_tab_without_over_the_spot[] = {
    {XIMPreeditNothing|XIMStatusNothing, IS_ROOT_WINDOW},
    //{XIMPreeditPosition|XIMStatusArea, IS_OVER_THE_SPOT},// emacs
    //{XIMPreeditPosition|XIMStatusNothing, IS_OVER_THE_SPOT},
    //{XIMPreeditCallbacks|XIMStatusCallbacks, IS_ON_THE_SPOT},// OOo
    //{XIMPreeditArea|XIMStatusArea, IS_ROOT_WINDOW},
    {XIMPreeditCallbacks|XIMStatusNothing, IS_ON_THE_SPOT},
    {0, 0},
};
// XIMPreeditArea,XIMPreeditCallbacks,XIMPreeditPosition
// XIMPreeditNothing,XIMPreeditNone
// XIMStatusArea,XIMStatusCallbacks
// XIMStatusNothing,XIMStatusNone

void print_ustring(uString *s)
{
    uString::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_ustring(uString *s)
{
    s->erase(s->begin(), s->end());
}

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

void str_to_ustring(uString *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バイト文字
	    uchar ch;
	    ch = ((s[i]&0x7f)<<8)|(s[i+1]&0x7f);
	    i++;
	    d->push_back(ch);
	} else {
	    d->push_back(s[i]);
	}
    }
}

XimServer::XimServer(Locale *lc, char *name, char *lang)
{
    mName = name;
    mLang = lang;
    mLocale = lc;
}

InputContext *XimServer::createContext(XimIC *xic)
{
    InputContext *ic = new InputContext(this, xic, mName);
    return ic;
}

struct input_style *
XimServer::getInputStyles()
{
    if (mLocale->supportOverTheSpot()) {
	return input_style_tab_with_over_the_spot;
    }
    return input_style_tab_without_over_the_spot;
}

bool
XimServer::setupConnection()
{
    char *buf = (char *)alloca(15 + strlen(mName));
    sprintf(buf, "@server=uim-%s", mName);
    mServerAtom = XInternAtom(XimServer::gDpy, buf, 0);
    Window owner = XGetSelectionOwner(XimServer::gDpy, mServerAtom);
    if (owner != None) {
	printf("Another instance exists(%s).\n", mName);
	return false;
    }
    mSelectionWin = XCreateSimpleWindow(XimServer::gDpy,
					DefaultRootWindow(XimServer::gDpy),
					0, 0, 1, 1,
					1,0,0);
    XSetSelectionOwner(XimServer::gDpy, mServerAtom, mSelectionWin, CurrentTime);
    XSelectInput(XimServer::gDpy, DefaultRootWindow(XimServer::gDpy), 0);
    XSync(XimServer::gDpy, False);

    Atom type;
    int format;
    unsigned long nr_prop,nr_bytes;
    Atom *prop;
    int mode = PropModePrepend;
    int valuechange = 1;

    XGetWindowProperty(XimServer::gDpy, DefaultRootWindow(XimServer::gDpy),
                       xim_servers, 0, 8192 ,False,
                       XA_ATOM, &type, &format,
                       &nr_prop, &nr_bytes, (unsigned char **)&prop);
    int i;
    if (type != XA_ATOM || format != 32) {
	mode = PropModeReplace;
    } else {
	for (i = 0 ; i < (int)nr_prop ; i++) {
	    if (prop[i] == mServerAtom) {
		mode = PropModeAppend;
		valuechange = 0;
		break;
	    }
	}
    }
    if (nr_prop) {
	XFree(prop);
    }

    XChangeProperty(XimServer::gDpy, DefaultRootWindow(XimServer::gDpy),
		    xim_servers,
		    XA_ATOM, 32,
		    mode, (unsigned char *)&mServerAtom,
		    valuechange ? 1 : 0);
    std::pair<Window, XimServer *> p(mSelectionWin, this);
    gServerMap.insert(p);
    return true;
}

char *
XimServer::uStringToCtext(uString *us)
{
    return mLocale->uStringToCtext(us);
}

void
XimServer::strToUstring(uString *d, const 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バイト文字
	    uchar ch;
	    ch = ((s[i]&0x7f)<<8)|(s[i+1]&0x7f);
	    i++;
	    d->push_back(ch);
	} else {
	    d->push_back(s[i]);
	}
    }
}

XimServer *XimServer::findServer(Window w)
{
    std::map<Window, XimServer *>::iterator it;
    it = gServerMap.find(w);
    if (it == gServerMap.end()) {
	return NULL;
    }
    return it->second;
}

//
// InputContextのメソッド
InputContext::InputContext(XimServer *svr, XimIC *ic, char *engine)
{
    mXic = ic;
    m_pe = new pe_stat(this);
    mConvdisp = 0;
    mUc = createUimContext(engine);
    mServer = svr;
}

InputContext::~InputContext()
{
    if (mFocusedContext == this) {
	mFocusedContext = NULL;
    }
    if (mConvdisp) {
	mConvdisp->set_pe(0);
    }
    delete m_pe;
    uim_release_context(mUc);
}

uim_context
InputContext::createUimContext(char *engine)
{
    uim_context uc = uim_create_context((void *) this, "EUC-JP",
					NULL, engine, uim_iconv,
					InputContext::commit_cb);

    uim_set_preedit_cb(uc,
		       InputContext::clear_cb,
		       InputContext::pushback_cb,
		       InputContext::update_cb);
    uim_set_candidate_selector_cb(uc,
				  InputContext::candidate_activate_cb,
				  InputContext::candidate_select_cb,
				  NULL,
				  InputContext::candidate_deactivate_cb);
    uim_set_prop_list_update_cb(uc,
				InputContext::update_prop_list_cb);
    uim_set_prop_label_update_cb(uc,
				 InputContext::update_prop_label_cb);

    uim_prop_list_update(uc);

    return uc;
}

InputContext*
InputContext::focusedContext()
{
    return mFocusedContext;
}

void
InputContext::deletefocusedContext()
{
    mFocusedContext = NULL;
}

void
InputContext::focusIn()
{
    uim_prop_list_update(mUc);	
    uim_prop_label_update(mUc);	
    uim_helper_client_focus_in(mUc);
    mFocusedContext = this;
}

void
InputContext::focusOut()
{
    uim_helper_client_focus_out(mUc);
}

XimServer *
InputContext::getServer()
{
    return mServer;
}

uim_context
InputContext::getUC()
{
    return mUc;
}

void
InputContext::commit_cb(void *ptr, const char *str)
{
    InputContext *ic = (InputContext *)ptr;
    XimIC *xic = ic->get_ic();

    clear_cb(ptr);
    ic->update_preedit();
    xic->commit_string(str);
}

void InputContext::clear_cb(void *ptr)
{
    InputContext *ic = (InputContext *)ptr;
    ic->clear_preedit();
}

void InputContext::pushback_cb(void *ptr, int attr, const char *str)
{
    InputContext *ic = (InputContext *)ptr;
    ic->pushback_preedit_string(attr, str);
}

void InputContext::update_cb(void *ptr)
{
    InputContext *ic = (InputContext *)ptr;
    ic->update_preedit();
}

void InputContext::candidate_activate_cb(void *ptr, int nr, int display_limit)
{
    InputContext *ic = (InputContext *)ptr;
    ic->candidate_activate(nr, display_limit);
}

void InputContext::candidate_select_cb(void *ptr, int index)
{
    InputContext *ic = (InputContext *)ptr;
    ic->candidate_select(index);
}

void InputContext::candidate_deactivate_cb(void *ptr)
{
    InputContext *ic = (InputContext *)ptr;
    ic->candidate_deactivate();
}

void InputContext::update_prop_list_cb(void *ptr, const char *str)
{
    InputContext *ic = (InputContext *)ptr;
    ic->update_prop_list(str);
}

void InputContext::update_prop_label_cb(void *ptr, const char *str)
{
    InputContext *ic = (InputContext *)ptr;
    ic->update_prop_label(str);
}

void InputContext::clear_preedit()
{
    m_pe->clear();
}

void InputContext::pushback_preedit_string(int attr, const char *str)
{
    int p = 0;
    if (attr & UPreeditAttr_UnderLine) {
	p |= PE_UNDERLINE;
    }
    if (attr & UPreeditAttr_Reverse) {
	p |= PE_REVERSE;
    }
    if (attr & UPreeditAttr_Cursor) {
	m_pe->caret_pos = m_pe->get_char_count();
    }
    if (!strlen(str)) {
	return;
    }
    m_pe->new_segment(p);
    uString js;
    mServer->strToUstring(&js, str);

    uString::iterator it;
    for (it = js.begin(); it != js.end(); it++) {
	m_pe->push_uchar(*it);
    }
}

void InputContext::update_preedit()
{
    if (mConvdisp) {
	mConvdisp->update_preedit();
    }
}

int InputContext::pushKey(keyState *k)
{
    int key = k->key();
    if (key != UKey_Other) {
	int rv;
	rv = uim_press_key(mUc, key, k->modifier());
	if (rv) {
	    return COMMIT_RAW;
	} else {
	    return UPDATE_MODE;
	}
    }
    return COMMIT_RAW;
}

int InputContext::getMode()
{
    return 0;
}

void InputContext::setMode(int mode)
{
}

uString *InputContext::clear()
{
    return NULL;
}

void InputContext::setConvdisp(Convdisp *c)
{
    mConvdisp = c;
    if (mConvdisp) {
	mConvdisp->set_pe(m_pe);
    }
}

void InputContext::commit_string(char *s)
{
    mXic->commit_string(s);
}

bool InputContext::extra_input(char *s)
{
    return false;
}

XimIC *InputContext::get_ic()
{
    return mXic;
}

void InputContext::candidate_activate(int nr, int display_limit)
{
    int i;
    uim_candidate cand[nr];
    std::vector<const char*> candidates;
    
    Canddisp* disp = canddisp_singleton();

    for (i = 0; i < nr; i++) {
	cand[i] = uim_get_candidate(mUc, i,i);
        candidates.push_back((const char *)uim_candidate_get_cand_str(cand[i]));
    }
    disp->activate(candidates);
    //    disp->update();
    for (i = 0; i < nr; i++) {
	    uim_candidate_free(cand[i]);
    }
}

void InputContext::candidate_select(int index)
{
    Canddisp* disp = canddisp_singleton();
    disp->select(index);
}

void InputContext::candidate_deactivate()
{
    Canddisp* disp = canddisp_singleton();
    disp->deactivate();
}

void InputContext::update_prop_list(const char* str)
{
    char tmp[BUFSIZ];
    int len;

    len = snprintf(tmp, sizeof(tmp),
		   "prop_list_update\ncharset=EUC-JP\n%s", str);

    if (len != -1 && (unsigned int)len <= sizeof(tmp)) {
	uim_helper_send_message(lib_uim_fd, tmp);
    }
}

void InputContext::update_prop_label(const char* str)
{
    char tmp[BUFSIZ];
    int len;

    len = snprintf(tmp, sizeof(tmp),
		   "prop_label_update\ncharset=EUC-JP\n%s", str);

    if (len != -1 && (unsigned int)len <= sizeof(tmp)) {
	uim_helper_send_message(lib_uim_fd, tmp);
    }
}

keyState::keyState(keyEventX *x)
{
    mModifier = 0;
    if (x->state & ShiftMask) {
	mModifier += UMod_Shift;
    }
    if (x->state & LockMask) {
	// CapsLock
    }
    if (x->state & ControlMask) {
	mModifier |= UMod_Control;
    }
    if (x->state & Mod1Mask) {
	mModifier |= UMod_Alt;
    }
    if (x->state & Mod2Mask) {
	mModifier |= UMod_Meta;
    }
    if (x->state & Mod3Mask) {  /* assuming mod3 */
	mModifier |= UMod_Super;
    }
    if (x->state & Mod4Mask) {  /* assuming mod4 */
	mModifier |= UMod_Hyper;
    }
    if (x->key_sym < 128 && x->key_sym >= 32) {
	mKey = x->key_sym;
    } else {
	switch (x->key_sym) {
	case XK_BackSpace: mKey = UKey_Backspace; break;
	case XK_Delete: mKey = UKey_Delete; break;
	case XK_Escape: mKey = UKey_Escape; break;
	case XK_Tab: mKey = UKey_Tab; break;
	case XK_Return: mKey = UKey_Return; break;
	case XK_Left: mKey = UKey_Left; break;
	case XK_Up: mKey = UKey_Up; break;
	case XK_Right: mKey = UKey_Right; break;
	case XK_Down: mKey = UKey_Down; break;
	case XK_Prior: mKey = UKey_Prior; break;
	case XK_Next: mKey = UKey_Next; break;
	case XK_Home: mKey = UKey_Home; break;
	case XK_End: mKey = UKey_End; break;
	case XK_Zenkaku_Hankaku: mKey = UKey_Zenkaku_Hankaku; break;
	case XK_F1: mKey = UKey_F1; break;
	case XK_F2: mKey = UKey_F2; break;
	case XK_F3: mKey = UKey_F3; break;
	case XK_F4: mKey = UKey_F4; break;
	case XK_F5: mKey = UKey_F5; break;
	case XK_F6: mKey = UKey_F6; break;
	case XK_F7: mKey = UKey_F7; break;
	case XK_F8: mKey = UKey_F8; break;
	case XK_F9: mKey = UKey_F9; break;
	case XK_F10: mKey = UKey_F10; break;
	case XK_F11: mKey = UKey_F11; break;
	case XK_F12: mKey = UKey_F12; break;
	case XK_Multi_key: mKey = UKey_Multi_key; break;
	case XK_Mode_switch: mKey = UKey_Mode_switch; break;
	case XK_Henkan_Mode: mKey = UKey_Henkan_Mode; break;
	case XK_Muhenkan: mKey = UKey_Muhenkan; break;
	case XK_KP_0:
	case XK_KP_1:
	case XK_KP_2:
	case XK_KP_3:
	case XK_KP_4:
	case XK_KP_5:
	case XK_KP_6:
	case XK_KP_7:
	case XK_KP_8:
	case XK_KP_9:
	case XK_KP_Equal:
	case XK_KP_Multiply:
	case XK_KP_Add:
	case XK_KP_Separator:
	case XK_KP_Subtract:
	case XK_KP_Decimal:
	case XK_KP_Divide:
	case XK_KP_Enter:
	case XK_KP_Home:
	case XK_KP_Left:
	case XK_KP_Up:
	case XK_KP_Right:
	case XK_KP_Down:
	case XK_KP_Prior:
	case XK_KP_Next:
	case XK_KP_End:
	case XK_KP_Begin:
	case XK_KP_Insert:
	case XK_KP_Delete:
	    mKey = x->key_sym;
	    break;
	default: mKey = UKey_Other;
	}
    }
}

int keyState::key()
{
    return mKey;
}


int keyState::modifier()
{
    return mModifier;
}

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

void keyState::print()
{
    printf("key code=%x,modifier=%d.\n",
	   mKey, mModifier);
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
