/*
 * Copyright (C) 1992 by Software Research Associates, Inc.
 *	Author:	Y. Kawabe <kawabe@sra.co.jp>
 *
 * Permission to use, copy, modify, and distribute, and sell this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Software Research Associates
 * makes no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */

#include <InterViews/color.h>
#include <InterViews/coord.h>
#include <InterViews/event.h>
#include <InterViews/resource.h>
#include <InterViews/ximp.h>
#include <InterViews/window.h>
#include <IV-X11/Xlib.h>
#include <IV-X11/Ximp.h>
#include <IV-X11/xatom.h>
#include <IV-X11/xevent.h>
#include <IV-X11/xwindow.h>
#include <hyperg/OS/memory.h>
#include <hyperg/OS/string.h>
#include <hyperg/OS/ustring.h>
#include <hyperg/OS/table.h>
#include <hyperg/OS/math.h>
#include <string.h>
#include <stdio.h>

#ifdef iv_nls
#include <NLS/charset.h>
#include <NLS/locale.h>
#include <NLS/nlsfile.h>
#include <NLS/wchar.h>
#include <NLS/wstring.h>
#endif

#define	XimpBuf		_lib_iv(XimpBuf)
#define XimpKey		_lib_iv(XimpKey)
#define XimpIM		_lib_iv(XimpIM)
#define XimpIC		_lib_iv(XimpIC)
#define XimpCB		_lib_iv(XimpCB)
#define XimpIMP		_lib_iv(XimpIMP)

/*
 * class XimpBuf	(stream buffer)
 */

class XimpBuf {
  public:
    XimpBuf (int size);
    ~XimpBuf ();
    
  public:
    char*	string ()	const 	{ return text_; }
    int		count ()	const	{ return count_; }
    void	clear ()		{ count_ = 0; }
    void	append (const char*, int);

  private:
    char*       text_;
    int         block_;
    int         size_;
    int         count_;
};

XimpBuf::XimpBuf (int block) {
    block_ = block;
    text_ = new char [block_];
    size_ = block_;
    count_ = 0;
}    

XimpBuf::~XimpBuf () {
    delete text_;
}

void XimpBuf::append (const char* s, int len) {
    if (count_ + len > size_) {
        size_ += (len / block_ + 1) * block_;
        char* text = new char[size_];
        Memory::copy(text_, text, count_);
        delete text_;
        text_ = text;
    }
    Memory::copy(s, text_ + count_, len);
    count_ += len;
}



#ifdef iv_nls

/*
 * class XimpKey	(Key table)
 */

#ifndef IV_KEYSET_PATH
#define IV_KEYSET_PATH         "KeySet"
#endif

/*
 * class XimpKeyTable
 */

declareTable(XimpKeyTable, KeySym, WChar);
implementTable(XimpKeyTable, KeySym, WChar);

/*
 * class XimpKey
 */

class XimpKey {
  public:
    static const char* lookup (KeySym);
    static const char* lookup (const char*, int);

  private:
    static XimpKeyTable	*table_;
    static char*	text_;
};

XimpKeyTable *XimpKey::table_;
char *XimpKey::text_;

const char* XimpKey::lookup (KeySym key) {
    if (table_ == nil) {
	table_ = new XimpKeyTable(256);
	
	const int	argsize = 256;
	char		*argv[argsize], **av;
	int		argc, ac;
	CharSet_T	charset = -1;
	
	nlsFile		file (IV_KEYSET_PATH);
	
	while ((argc = file.getline(argv, argsize)) >= 0) {
	    if (argc == 0) continue;
	    
	    if (!file.continued()) {
		charset = CharSet::find (argv[0]);
		ac = argc - 1, av = argv + 1;
	    } else {
		ac = argc, av = argv;
	    }
	    
	    while (ac > 0 && charset != -1) {
		char *p = index(av[0], '=');
		if (p == 0) continue;
		WChar ch (file.str2int(av[0]), charset);
		table_->insert((KeySym) file.str2int(p + 1), ch);
		ac--, av++;
	    }
	}
    }

    if (text_) {
	delete text_; 
	text_ = nil;
    }

    WChar ch;
    if (table_->find(ch, key)) {
	WString s(&ch, 1);
	text_ = s.string(Locale::setlocale());
    }
    return text_;
}

const char* XimpKey::lookup (const char* t, int n) {
    delete text_; 
    WString s(Locale("CTEXT"), t, n);
    text_ = s.string(Locale::setlocale());
    return text_;
}

#endif


/*
 * Ximp Protocol Error
 */

const char* XimpErr (enum XimpRequest req, enum XimpError err) {
    const char* req_str = "???";
    switch (req) {
      case XIMP_KEYPRESS:		req_str = "keypress";		break;
      case XIMP_CREATE:			req_str = "create";		break;
      case XIMP_DESTROY:		req_str = "destroy";		break;
      case XIMP_BEGIN:			req_str = "begin";		break;
      case XIMP_END:			req_str = "end";		break;
      case XIMP_SETFOCUS:		req_str = "setfocus";		break;
      case XIMP_UNSETFOCUS:		req_str = "unsetfocus";		break;
      case XIMP_MOVE:			req_str = "move";		break;
      case XIMP_RESET:			req_str = "reset";		break;
      case XIMP_SETVALUE:		req_str = "setvalue";		break;
      case XIMP_GETVALUE:		req_str = "getvalue";		break;
    }
    
    const char* err_str = "???";
    switch (err) {
      case XIMP_NoError:		err_str = "No Error";		break;
      case XIMP_BadAlloc:		err_str = "Bad Alloc";		break;
      case XIMP_BadStyle:		err_str = "Bad Style";		break;
      case XIMP_BadClientWindow:	err_str = "Bad ClientWindow";	break;
      case XIMP_BadFocusWindow:		err_str = "Bad FocusWindow";	break;
      case XIMP_BadArea:		err_str = "Bad Area";		break;
      case XIMP_BadSpotLocation:	err_str = "Bad SpotLocation";	break;
      case XIMP_BadColormap:		err_str = "Bad Colormap";	break;
      case XIMP_BadAtom:		err_str = "Bad Atom";		break;
      case XIMP_BadPixel:		err_str = "Bad Pixel";		break;
      case XIMP_BadPixmap:		err_str = "Bad Pixmap";		break;
      case XIMP_BadName:		err_str = "Bad Font Name";	break;
      case XIMP_BadCursor:		err_str = "Bad Cursor";		break;
      case XIMP_BadProtocol:		err_str = "Bad Protocol";	break;
      case XIMP_BadProperty:		err_str = "Bad Property";	break;
      case XIMP_BadPropertyType:	err_str = "Bad Property Type";	break;
    }

    static char buf[64];
    sprintf (buf, "%s : %s", req_str, err_str);
    return buf;
}

/*
 * Ximp Lookup Keysym
 */

KeySym XimpLookupKeysym (XKeyEvent *ev) {
    char buffer[1];
    KeySym keysym;

    XLookupString (ev, buffer, sizeof(buffer), &keysym, nil);
    return keysym;
}


/*
 * class XimpIMList
 */

class XimpIM;

declarePtrList (XimpIMList, XimpIM);
implementPtrList (XimpIMList, XimpIM);

/*
 * class XimpIM		(Input Method)
 */

class XimpIM : public Resource {
  public:
    static XimpIM* lookup (XDisplay* display, Atom language);
    
  public:
    XDisplay* display () const ;
    XWindow server () const;
    Atom language () const;
    const char* name () const ;
    const char* version () const ;
    const char* vendor () const ;
    
  public:
    XWindow do_bind ();
    boolean start_key (KeySym keysym, int state) const ;
    boolean supported_style (XIMStyle style) const ;

  protected:
    XimpIM (XDisplay* display, Atom language);
    ~XimpIM ();
    
  private:
    XDisplay*		display_;
    Atom		language_;		// Input Language
    XWindow		server_;		// IMS Window
    Ximp_Keys		keys_;			// Convertion Key
    Ximp_Styles		styles_;		// Supported Input Style
    char*		name_;			// name of IMS
    char*		version_;		// version of IMS
    char*		vendor_;		// vendor of IMS
    
  private:
    static XimpIMList	*list_;
};

XimpIMList*	XimpIM::list_;

XimpIM::XimpIM (XDisplay* display, Atom language) : Resource() {
    display_ = display;
    language_ = language;
    server_ = None;
    styles_.nitems = 0;
    styles_.styles = nil;
    keys_.nitems = 0;
    keys_.keys = nil;
    name_ = version_ = vendor_ = nil;
}

XimpIM::~XimpIM () {
    for (int i = 0; i < list_->count() ; i++) {
	if (list_->item(i) == this) {
	    list_->remove(i); break;
	}
    }
    delete styles_.styles;
    delete keys_.keys;
    delete name_;
    delete version_;
    delete vendor_;
}

XimpIM* XimpIM::lookup (XDisplay* dpy, Atom language) {
    if (list_ == nil) { list_ = new XimpIMList (); }
    for (ListItr(XimpIMList) i(*list_); i.more(); i.next()) {
        if (i.cur()->display_ == dpy && i.cur()->language_ == language) {
            return i.cur();
        }
    }
    XimpIM* im = new XimpIM(dpy, language);
    list_->append(im);
    return (im);
}

XWindow XimpIM::do_bind () {
    /* Get IMS Window ID */
    XWindow owner = XGetSelectionOwner (display_, language_);
    
    if (owner != server_) {
	server_ = owner;
    
	delete styles_.styles;
	delete keys_.keys;
	delete name_;
	delete version_;
	delete vendor_;

	styles_.nitems = 0;
	styles_.styles = nil;
	keys_.nitems = 0;
	keys_.keys = nil;
	name_ = version_ = vendor_ = nil;
	
	if (server_ == None)
	    return server_;

	Atom			type;
	int			format;
	unsigned long 		nitems;
	unsigned long 		bytes;
	unsigned char*		prop;
	int			i, j;

	/* Get STYLE Property */
	Atom atom_style = XAtom::intern(display_, "_XIMP_STYLE");
	if (XGetWindowProperty(display_, server_, atom_style, 0L, 10240L,
			       False, atom_style, &type, &format,
			       &nitems, &bytes, &prop) == Success) {
	    
	    Ximp_Style *styles = new Ximp_Style [nitems];
	    for(i = 0, j = 0 ; i < nitems; i++) {
		styles[i].style = (XIMStyle)(((long *)prop) [j++]);
	    }
	    XFree((char*) prop);
	    styles_.styles = styles;
	    styles_.nitems = int(nitems);
    	}
    
	/* Get KEYS Property */
	Atom atom_keys = XAtom::intern(display_, "_XIMP_KEYS");
	if (XGetWindowProperty(display_, server_, atom_keys, 0L, 10240L,
			       False, atom_keys, &type, &format,
			       &nitems, &bytes, &prop) == Success) {
	    int n = int(nitems) / 3;
	    Ximp_Key *keys = new Ximp_Key [n];
	    for(i = 0, j = 0 ; i < n; i++) {
		keys[i].modifier = ((long *) prop) [j++];
		keys[i].mask = ((long *) prop) [j++];
		keys[i].keysym = ((long *) prop) [j++];
	    }
	    XFree((char*) prop);
	    keys_.keys = keys;
	    keys_.nitems = n;
	}
    
	/* Get SERVERNAME Property */
	Atom atom_name = XAtom::intern(display_, "_XIMP_SERVERNAME");
	if (XGetWindowProperty(display_, server_, atom_name,
			       0L, 10240L, False, XA_STRING, &type, &format,
			       &nitems, &bytes, &prop) == Success) {
	    name_ = new char[nitems + 1];
	    strncpy(name_, (char*) prop, int(nitems));
	    name_[nitems] = '\0';
	    XFree ((char*) prop);
	}
	
	/* Get SERVERVERSION Property */
	Atom atom_version = XAtom::intern(display_, "_XIMP_SERVERVERSION");
	if (XGetWindowProperty(display_, server_, atom_version,
			       0L, 10240L, False, XA_STRING, &type, &format,
			       &nitems, &bytes, &prop) == Success) {
	    version_ = new char[nitems + 1];
	    strncpy(version_, (char*) prop, int(nitems));
	    version_[nitems] = '\0';
	    XFree ((char*) prop);
	}
	
	/* Get VONDORNAME Property */
	Atom atom_vendor = XAtom::intern(display_, "_XIMP_VENDORNAME");
	if (XGetWindowProperty(display_, server_, atom_vendor,
			       0L, 10240L, False, XA_STRING, &type, &format,
			       &nitems, &bytes, &prop) == Success) {
	    vendor_ = new char[nitems + 1];
	    strncpy(vendor_, (char*) prop, int(nitems));
	    vendor_[nitems] = '\0';
	    XFree ((char*) prop);
	}
    }
    return server_;
}

inline XDisplay* XimpIM::display() const { return display_; }
inline XWindow XimpIM::server() const { return server_; }
inline Atom XimpIM::language() const { return language_; }
inline const char* XimpIM::name () const { return name_; }
inline const char* XimpIM::version () const { return version_; }
inline const char* XimpIM::vendor () const { return vendor_; }

boolean XimpIM::start_key (KeySym keysym, int state) const {
    for (int i = 0; i < keys_.nitems; i++) {
	register Ximp_Key &keys = keys_.keys[i];
	if (keys.keysym == keysym && (state & keys.mask) == keys.modifier) {
	    return true;
	}
    }
    return false;
}

boolean XimpIM::supported_style (XIMStyle style) const {
    for (int i = 0; i < styles_.nitems; i++) {
	if (style == styles_.styles[i].style) {
	    return true;
	}
    }
    return false;
}


/*
 * class XimpICList
 */

class XimpIC;

declarePtrList (XimpICList, XimpIC);
implementPtrList (XimpICList, XimpIC);

/*
 * class XimpCB		(call back)
 */

class XimpCB : public Resource {
  public:
    XimpCB (XimpBuf* t) {text_ = t;}
    ~XimpCB() {};
    
  public:
    void handle_create (XimpIC *);
    void handle_destroy (XimpIC *);
    void handle_begin (XimpIC *);
    void handle_end (XimpIC *);
    void handle_reset (XimpIC *);
    void handle_key (XimpIC*, unsigned int, unsigned int);
    void handle_read (XimpIC *, const char*, int);
    void handle_error (XimpIC *, const char*, int);

  private:
    XimpBuf*	text_;
};

/*
 * class XimpIC		(Input Context)
 */

#define XIMP_STATUS_BOUND	0x0001
#define XIMP_STATUS_CREAT	0x0002
#define XIMP_STATUS_BEGIN	0x0004

class XimpIC : public Resource {
  public:
    static XimpIC* lookup (XimpIM*, XWindow);
    
  public:
    XDisplay* display() const;
    XWindow window() const;

  public:			/* Send Request */
    void IC_Create (XIMStyle, unsigned long);
    void IC_Destroy ();
    void IC_Begin ();
    void IC_End ();
    void IC_SetFocus ();
    void IC_UnSetFocus ();
    void IC_KeyPress (unsigned int keycode, unsigned int state);
    void IC_Move (IntCoord x, IntCoord y);
    void IC_GetValue (unsigned long);
    void IC_SetValue (unsigned long);
    void IC_Reset ();
    
  public:			/* Receive Request */
    boolean HandleMsg (const Event&, XimpCB*);
    boolean HandleKey (const Event&, XimpCB*);
    
  public:
    void SendMessage(
        int format, unsigned long, unsigned long = 0, 
	unsigned long = 0, unsigned long = 0
    );
    void PushKeyEvent(
	unsigned long serial, unsigned int keycode, unsigned int state
    );
    
  protected:
    XimpIC (XimpIM*, XWindow);
    ~XimpIC();

  private:
    XimpIM		*im_;			// IMS
    XWindow		window_;		// client window
    ICID		icid_;			// Context ID
    XimpBuf		*text_;			// message buffer
    unsigned long	status_;
    
  public:
    Ximp_Preedit	*preedit_area_;		// preedit area attribute
    Ximp_Status		*status_area_;		// status area attribute
    const char		*preedit_font_;		// fontset of preedit area 
    const char		*status_font_;		// fontset of status area
    
  protected:
    static XimpICList	*list_;
};

/*
 * class XimpIC
 */

XimpICList*	XimpIC::list_;

XimpIC::XimpIC (XimpIM* server, XWindow win) : Resource() {
    im_ = server;
    window_ = win;
    status_ = 0;
    text_ = new XimpBuf(256);
    preedit_area_ = nil;
    status_area_ = nil;
    preedit_font_ = nil;
    status_font_ = nil;
}

XimpIC::~XimpIC() {
    for (int i = 0; i < list_->count() ; i++) {
	if (list_->item(i) == this) {
	    list_->remove(i); break;
	}
    }
    if (status_ & XIMP_STATUS_CREAT) {
	IC_Destroy();
    }
    delete text_;
}

XimpIC* XimpIC::lookup (XimpIM* server, XWindow win) {
    if (list_ == nil) { list_ = new XimpICList(); }
    for (ListItr(XimpICList) i(*list_); i.more(); i.next()) {
	if (i.cur()->im_ == server && i.cur()->window_ == win)
	    return i.cur();
    }
    
    XimpIC* ic = new XimpIC (server, win);
    list_->append(ic);
    return (ic);
}

inline XDisplay* XimpIC::display () const { return im_->display(); }
inline XWindow XimpIC::window () const { return window_; }

/*
 * Send Request to IMS
 */

void XimpIC::IC_Create (XIMStyle style, unsigned long mask) {
    IC_SetValue (mask);
    if (status_ & XIMP_STATUS_BOUND) {
	SendMessage (32, XIMP_CREATE, window_, style, mask);
    }
}

void XimpIC::IC_Destroy () {
    if (status_ & XIMP_STATUS_CREAT) {
	SendMessage (32, XIMP_DESTROY, icid_);
    }
}

void XimpIC::IC_Begin () {
    if (status_ & XIMP_STATUS_CREAT) {
	SendMessage (32, XIMP_BEGIN, icid_);
    }
}

void XimpIC::IC_End () {
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage (32, XIMP_END, icid_);
    }
}

void XimpIC::IC_SetFocus () {
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage (32, XIMP_SETFOCUS, icid_);
    }
}

void XimpIC::IC_UnSetFocus () {
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage (32, XIMP_UNSETFOCUS, icid_);
    }
}

void XimpIC::IC_KeyPress (unsigned int keycode, unsigned int state) {
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage (32, XIMP_KEYPRESS, icid_, keycode, state);
    }
}

void XimpIC::IC_Move (IntCoord x, IntCoord y) {
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage (32, XIMP_MOVE, icid_, x, y);
    }
}

void XimpIC::IC_SetValue (unsigned long mask) {
    
    register XDisplay* display = im_->display();
    
    if (!(status_ & XIMP_STATUS_BOUND)) {
	status_ |= XIMP_STATUS_BOUND;
	
	Atom atom_version = XAtom::intern(display, "_XIMP_VERSION");
	XChangeProperty(display, window_, atom_version,
			XA_STRING, 8, PropModeReplace,
			(unsigned char *)XIMP_PROTOCOL_VERSION,
			strlen (XIMP_PROTOCOL_VERSION));
	
	Atom atom_focus = XAtom::intern(display, "_XIMP_FOCUS");
	XChangeProperty(display, window_, atom_focus,
			XA_WINDOW, 32, PropModeReplace,
			(unsigned char *) &window_, 1);
    }
    
    if ((mask & XIMP_PRE_MASK) && preedit_area_) {
	Atom atom_preedit = XAtom::intern(display, "_XIMP_PREEDIT");
	XChangeProperty(display, window_, atom_preedit,
			atom_preedit, 32, PropModeReplace,
			(unsigned char *) preedit_area_,
			sizeof(Ximp_Preedit) / 4);
    }
    
    if ((mask & XIMP_STS_MASK) && status_area_) {
	Atom atom_status = XAtom::intern(display, "_XIMP_STATUS");
	XChangeProperty(display, window_, atom_status,
			atom_status, 32, PropModeReplace,
			(unsigned char *) status_area_,
			sizeof(Ximp_Status) / 4);
    }
    
    if ((mask & XIMP_PRE_FONT_MASK) && preedit_font_) {
	const char *name = preedit_font_;
	Atom atom_preedit_font = XAtom::intern(display, "_XIMP_PREEDITFONT");
	XChangeProperty(display, window_, atom_preedit_font,
			XA_STRING, 8, PropModeReplace,
			(unsigned char *)preedit_font_, strlen(preedit_font_));
    }
    
    if ((mask & XIMP_STS_FONT_MASK) && status_font_) {
	Atom atom_status_font = XAtom::intern(display, "_XIMP_STATUSFONT");
	XChangeProperty(display, window_, atom_status_font,
			XA_STRING, 8, PropModeReplace,
			(unsigned char *)status_font_, strlen(status_font_));
    }
    
    if (status_ & XIMP_STATUS_BEGIN) {
	SendMessage(32, XIMP_SETVALUE, icid_, mask);
    }
}

void XimpIC::IC_GetValue (unsigned long) {
    /* not implemented */
}

void XimpIC::IC_Reset () {
    SendMessage (32, XIMP_RESET, icid_);
}

/*
 * Event Handler
 */

boolean XimpIC::HandleKey (const Event& e, XimpCB*) {
    XEvent& ev = e.rep()->xevent_;
    register XDisplay* display_ = im_->display();
    register XWindow server = im_->server();
    
    if (im_->do_bind() != server) { status_ = 0; }

    if (im_->server() != None) {
	KeySym keysym = XimpLookupKeysym(&ev.xkey);
	if (status_ & XIMP_STATUS_BEGIN) {
	    /* IC_KeyPress (ev.xkey.keycode, ev.xkey.state); */
	    return true;
	} else if (im_->start_key(keysym, ev.xkey.state)) {
	    if (status_ & XIMP_STATUS_CREAT) {
		IC_Begin ();
	    } else {
		IC_Create (
		    XIMPreeditNothing | XIMStatusNothing,
		    XIMP_FOCUS_WIN_MASK
		);
	    }
	    return true;
	}
    }
    return false;
}

boolean XimpIC::HandleMsg (const Event& e, XimpCB* handler) {
    XEvent& ev = e.rep()->xevent_;
    WindowRep *w = e.window()->rep();
    register XDisplay* display = im_->display();
    boolean result = false;
    
    if (im_->server() != None) {
	if (ev.xclient.format == 32) {
	    switch (ev.xclient.data.l[0]) {
	      case XIMP_KEYPRESSED:
		if ((status_ & XIMP_STATUS_BEGIN) &&
		    ev.xclient.data.l[1] == icid_) {
		    handler->handle_key(
			(XimpIC *)this,
                        (unsigned int) ev.xclient.data.l[2],
                        (unsigned int) ev.xclient.data.l[3]
                    );
		    result = true;
		}
		break;
		
	      case XIMP_CREATE_RETURN:
		if ((status_ & XIMP_STATUS_BOUND) && 
		    (status_ & XIMP_STATUS_CREAT) == 0) {
		    status_ |= XIMP_STATUS_CREAT;
		    icid_ = ev.xclient.data.l[1];
		    handler->handle_create((XimpIC *)this);
		    result = true;
		}
		break;
		
	      case XIMP_PROCESS_BEGIN:
		if ((status_ & XIMP_STATUS_CREAT) && 
		    icid_ == ev.xclient.data.l[1]) {
		    status_ |= XIMP_STATUS_BEGIN;
		    text_->clear();
		    handler->handle_begin((XimpIC *)this);
		    result = true;
		}
		break;
		
	      case XIMP_PROCESS_END:
		if ((status_ & XIMP_STATUS_CREAT) &&
		    icid_ == ev.xclient.data.l[1]) {
		    status_ &= ~XIMP_STATUS_BEGIN;
		    handler->handle_end((XimpIC *)this);
		    text_->clear();
		    result = true;
		}
		break;
		
	      case XIMP_READPROP:
		if ((status_ & XIMP_STATUS_CREAT) &&
		    icid_ == ev.xclient.data.l[1]) {
		    Atom property = ev.xclient.data.l[2];
		    Atom		type;
		    int			format;
		    unsigned long 	nitems;
		    unsigned long 	bytes;
		    unsigned char	*prop;

		    if(XGetWindowProperty(display, im_->server(), property,
					  0L, 10240L, True, AnyPropertyType,
					  &type, &format, &nitems, 
					  &bytes, &prop) == Success) {
			handler->handle_read(
			    (XimpIC *)this, (char*)prop, int(nitems)
			);
			XFree((char*) prop);
		    }
		    result = true;
		}
		break;
		
	      case XIMP_GETVALUE_RETURN:
		/* not implemented */ 
		break;
		
	      case XIMP_RESET_RETURN:
		if ((status_ & XIMP_STATUS_CREAT) &&
		    icid_ == ev.xclient.data.l[1]) {
		    Atom property = ev.xclient.data.l[2];
		    Atom		type;
		    int			format;
		    unsigned long 	nitems;
		    unsigned long 	bytes;
		    unsigned char	*prop;
		    
		    if(XGetWindowProperty(display, im_->server(), property,
					  0L, 10240L, True, AnyPropertyType,
					  &type, &format, &nitems, 
					  &bytes, &prop) == Success) {
			handler->handle_read(
			    (XimpIC *)this, (char*)prop, int(nitems)
			);
			XFree((char*) prop);
		    }
		    status_ &= ~ XIMP_STATUS_BEGIN;
		    handler->handle_reset((XimpIC *)this);
		    text_->clear();
		    result = true;
		}
		break;
		
	      case XIMP_ERROR:
		if ((status_ & XIMP_STATUS_CREAT) &&
		    icid_ == ev.xclient.data.l[1]) {
		    const char *err = XimpErr(
		        (enum XimpRequest) ev.xclient.data.l[2],
			(enum XimpError) ev.xclient.data.l[3]
		    );
		    handler->handle_error(
		        (XimpIC *)this, err, strlen(err)
		    );
		    result = true;
		}
		break;
		
	      default:
		/* Unknown Reply Message */
		break;
	    }
	} else if (ev.xclient.format == 8 && ev.xclient.data.l[0] == icid_) {
	    int size = int(ev.xclient.data.b[4]);
	    const char* data = &(ev.xclient.data.b[5]);
	    if (size > 15) {
		text_->append(data, 15);
	    } else {
		text_->append(data, size);
		handler->handle_read(
		    (XimpIC *)this, text_->string(), text_->count()
		);
		text_->clear ();
	    }
	    result = true;
	}
    }
    return result;
}

/*
 * Send ClientMessage to IMS
 */

void XimpIC::SendMessage(
    int format, unsigned long data1, unsigned long data2,
    unsigned long data3, unsigned long data4
) {
    register XDisplay* display = im_->display();
    register XWindow server = im_->server();
    
    XClientMessageEvent message;
    message.type = ClientMessage;
    message.display = display;
    message.window = server;
    message.message_type = XAtom::intern(display, "_XIMP_PROTOCOL");
    message.format = format;
    
    if (format == 32) {
        message.data.l[0] = (unsigned long) data1;
	message.data.l[1] = (unsigned long) data2;
	message.data.l[2] = (unsigned long) data3;
	message.data.l[3] = (unsigned long) data4;
    } else if (format == 16) {
        message.data.s[0] = (unsigned short) data1;
	message.data.s[1] = (unsigned short) data2;
	message.data.s[2] = (unsigned short) data3;
	message.data.s[3] = (unsigned short) data4;
    } else if (format == 8) {
        message.data.b[0] = (unsigned char) data1;
	message.data.b[1] = (unsigned char) data2;
	message.data.b[2] = (unsigned char) data3;
	message.data.b[3] = (unsigned char) data4;
    }
    XSendEvent(display, server, False, 0L, (XEvent *)&message);
    XFlush(display);
}

/*
 * Push KeyPressEvent in EventQueue
 */
    
void XimpIC::PushKeyEvent(
    unsigned long serial, unsigned int keycode, unsigned int state
) {
    register XDisplay* display_ = im_->display();

    XKeyEvent key;
    key.type = KeyPress;
    key.serial = serial;
    key.send_event = True;
    key.display = display_;
    key.window = window_;
    XQueryPointer(display_, window_, &key.root, &key.subwindow,
		  &key.x_root, &key.y_root, &key.x, &key.y, &key.state);
    key.keycode = keycode;
    key.state = state;
    XPutBackEvent(display_, (XEvent *) &key);
}


/*
 * class XimpCB
 */

void XimpCB::handle_destroy (XimpIC *)	{}
void XimpCB::handle_end     (XimpIC *)	{}
void XimpCB::handle_reset   (XimpIC *)	{}

void XimpCB::handle_create  (XimpIC *ic) {
    ic->IC_Begin();
}

void XimpCB::handle_begin (XimpIC *) {
    /* ic->IC_SetFocus(); */
}

void XimpCB::handle_key (XimpIC *ic, unsigned int key, unsigned int state) {
    ic->PushKeyEvent(0, key, state);
}

void XimpCB::handle_read (XimpIC *ic, const char* t, int n) {
    text_->clear();
    text_->append(t, n);
    ic->PushKeyEvent(0, 0, 0);
}

void XimpCB::handle_error (XimpIC *, const char* t, int n) {
    fprintf (stderr, "XimpErr: %.*s\n", n, t);
}



/*
 * class XimpIMP	(IM + IC + CB)
 */

class XimpIMP : public Resource {
  public:
    XimpIMP(const String&);
    ~XimpIMP();
   
  public:
    XimpBuf*	Text() const { return text_; }
    XimpIM*	IM() const { return im_; }
    XimpIC*	IC() const { return ic_; }
    XimpCB*	CB() const { return cb_; }

  public:
    boolean handleKey (Window*, const Event&);
    boolean handleMsg (Window*, const Event&);
    
  protected:
    boolean do_bind (Window*);

  protected:
    String		*language_;
    XimpBuf		*text_;
    XimpIM		*im_;
    XimpIC		*ic_;
    XimpCB		*cb_;
};

XimpIMP::XimpIMP (const String& language) {
    language_ = new String(language);
    text_ = nil;
    im_ = nil;
    ic_ = nil;
    cb_ = nil;
}

XimpIMP::~XimpIMP () {
    delete language_;
    delete text_;
    Resource::unref(im_);
    Resource::unref(ic_);
    Resource::unref(cb_);
}

boolean XimpIMP::do_bind(Window* window) {
    if (im_ == nil ) {
	XDisplay* xdpy = window->rep()->dpy();
	char ximp_lang[1024];
	if (language_->index('@') < 0) {
	    sprintf(ximp_lang, "%s%.*s", _XIMP_BASE,
		    language_->length(), language_->string());
	} else {
	    sprintf(ximp_lang, "%s%.*s.%d", _XIMP_BASE, 
		    language_->length(), language_->string(),
		    DefaultScreen(xdpy));
	}
	Atom lang = XAtom::intern(xdpy, ximp_lang);
	im_ = XimpIM::lookup(xdpy, lang);
	Resource::ref(im_);
    }
    XWindow xwin = window->rep()->xwindow_;
    if (ic_ == nil || ic_->window() != xwin) {
	Resource::unref(ic_);
	ic_ = XimpIC::lookup(im_, xwin);
	Resource::ref(ic_);
    }
    if (cb_ == nil) {
	text_ = new XimpBuf(256);
	cb_ = new XimpCB(text_);
	Resource::ref(cb_);
    }
    return ic_ != nil;
}

boolean XimpIMP::handleKey (Window* w, const Event& e) {
    return do_bind (w) && ic_->HandleKey(e, cb_);
}

boolean XimpIMP::handleMsg (Window* w, const Event& e) {
    return do_bind (w) && ic_->HandleMsg(e, cb_);
}



/*
 * class XimpManagerRep
 */

declarePtrList(XimpManagerRep, XimpIMP);
implementPtrList(XimpManagerRep, XimpIMP);

/*
 * class XimpManager
 */

static int strcopy(char* dist, int distlen, const char* from, int fromlen) {
    register int len = Math::min(distlen, fromlen);
    for (register int i = len; i > 0; i--) {
	*dist++ = *from++;
    }
    return len;
}

XimpManager::XimpManager (Window *window, const String& name) : Handler() {
    win_ = window;
    if (name.length() > 0) {
	rep_ = new XimpManagerRep (4);
	int start = 0;
	int len = name.length();
	const char* s = name.string();
	
	while (start < len) {
            int stop;
	    for (stop = start; stop < len && s[stop] != ','; stop++);
	    String language(&s[start], stop - start);
	    XimpIMP* imp = new XimpIMP(language);
	    Resource::ref(imp);
	    rep_->append(imp);
	    start = stop + 1;
	}
    } else {
	rep_ = nil;
    }
}

XimpManager::~XimpManager () {
    if (rep_) {
	for (ListItr(XimpManagerRep) i(*rep_); i.more(); i.next()) {
	    Resource::unref(i.cur());
	}
	delete rep_;
    }
}

int XimpManager::lookup (const Event& e, char* t, int len) {
    XEvent& ev = e.rep()->xevent_;
    if (ev.xkey.keycode == 0) {
	if (rep_) {
	    for (ListItr(XimpManagerRep) i(*rep_); i.more(); i.next()) {
		XimpBuf* b = i.cur()->Text();
		if (b->count() > 0) {
#ifdef iv_nls
		    const char* s = XimpKey::lookup(b->string(), b->count());
		    return strcopy(t, len, s, strlen(s));
#else
		    return strcopy(t, len, b->string(), b->count());
#endif
		}
	    }
	}
	return 0;
    }
    if (ev.xkey.send_event == False) {
	if (rep_) {
	    for (ListItr(XimpManagerRep) i(*rep_); i.more(); i.next()) {
		if (i.cur()->handleKey(win_, e)) {
		    return 0;
		}
	    }
	}
    }
#ifdef iv_nls
    KeySym keysym = XimpLookupKeysym(&ev.xkey);
    const char* keystr = XimpKey::lookup(keysym);
    if (keystr) {
	return strcopy(t, len, keystr, strlen(keystr));
    }
#endif
    return -1;
}

boolean XimpManager::event (Event& e) {
    XEvent& ev = e.rep()->xevent_;
    if (ev.type == ClientMessage && rep_) {
	for (ListItr(XimpManagerRep) i(*rep_); i.more(); i.next()) {
	    if (i.cur()->handleMsg(win_, e)) {
		return true;
	    }
	}
    }
    return false;
}

