/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial
portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP OR SUN MICROSYSTEMS, INC. BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/
#include "xiiimp.h"
#include "iiimpIM.h"
#include "iiimpColor.h"
#include "composeIM.h"
#include "guiIMLookup.h"
#include "guiIMPre.h"
#include "lookup.h"
#include "XimpIm.h"
#include "xfactory.h"
#include <X11/keysym.h>
#include "trace_message.h"
#include <stdio.h>

static void doneProc(XicCommon ic, XPointer p);

static void DestroyLookup(XicCommon, XPointer);
static void UpdateLookup(XicCommon);
static void highlightCandidate(XicCommon ic, int, Bool highlight);

static void
DrawLookupString(Display *display, Window win, XFontSet fontset,
		 GC gc, GC rgc, XIMFeedback feedback,
		 int x, int y, char *str, int len) {
  if (fontset == NULL) return;

  if (feedback & XIMReverse) {
    XmbDrawImageString(display, win, fontset, rgc, 
		       x, y, str, len);
  } else {
    XmbDrawImageString(display, win, fontset, gc, 
		       x, y, str, len);
  }
  return;
}

static void
DrawLookupString2(XicCommon ic,
		  Display *display, Window win, XFontSet fontset,
		  GC gc, GC rgc,
		  int x, int y, char *str, int str_len,
		  XIMChoiceObject2 *choice, int idx) {
  XIMText *label = &choice->label[idx];
  XIMText *value = &choice->value[idx];
  int i;
  int j;
  wchar_t *wcstr = 0, *wcstrp;
  size_t wc_len = 0;
  IMFeedbackList *pfeedback;
  int feedback_length;
  XimCommon im = (XimCommon)ic->core.im;

  if (fontset == NULL) return;

  /* label */
  str = label->string.multi_byte;
  str_len = (str) ? strlen(str) : 0;

  /* need to get wchar string */
  wc_len = str_len;
  if (!(wcstr = Xmalloc(sizeof(wchar_t) * (wc_len + 1)))) return;

  if (IIimpMbstoWcs(im, str, str_len, wcstr, wc_len, NULL) == 0) {
    Xfree(wcstr);
    goto error;
  }

  wcstrp = wcstr;

  pfeedback = choice->label_feedback[idx];
  feedback_length = label->length;

  for (i = 0; i < feedback_length; i++, pfeedback++) {
    /* set colors */
    if ((0 == i) || (False == SameIMFeedbackList(pfeedback - 1, pfeedback))) {
      SetIMColors(ic, display, win, gc, (GC)0, pfeedback, (XIMFeedback)0);
    }
    XwcDrawImageString(display, win, fontset, gc,
		       x, y, wcstrp, 1);
    x += XwcTextEscapement(fontset, wcstrp, 1); wcstrp++;
  }
  Xfree(wcstr);

  /* space */
  x += XmbTextEscapement(fontset, " ", 1);
  
  /* value */
  str = value->string.multi_byte;
  str_len = (str) ? strlen(str) : 0;

  /* need to get wchar string */
  wc_len = str_len;
  if (!(wcstr = Xmalloc(sizeof(wchar_t) * (wc_len + 1)))) return;

  if (IIimpMbstoWcs(im, str, str_len, wcstr, wc_len, NULL) == 0) {
    Xfree(wcstr);
    goto error;
  }

  wcstrp = wcstr;

  pfeedback = choice->value_feedback[idx];
  feedback_length = value->length;
  for (i = 0; i < feedback_length; pfeedback++) {
    /* set colors */
    for (j = 1; j < (feedback_length - i); j++, pfeedback++) {
      if (False == SameIMFeedbackList(pfeedback, pfeedback + 1)) {
	break;
      }
    }
    SetIMColors(ic, display, win, gc, (GC)0, pfeedback, (XIMFeedback)0);
    XwcDrawImageString(display, win, fontset, gc, x, y, wcstrp, j);
    x = XwcTextEscapement(fontset, wcstrp, j);
    wcstrp += j;
    i += j;
  }
  Xfree(wcstr);
error:
  return;
}

#if 0
#define xMargin 5
#define yMargin 10
#define cMargin 5
#define bMargin 10
#else /* !0 */
#define xMargin 4
#define yMargin 4
#define cMargin 6
#define bMargin 12
#endif /* !0 */

static int
boundValue(int value, int lower, int upper)
{
  if (value > upper)
    return upper;
  if (value < lower)
    return lower;

  return value;
}

static int
getChoiceIndex(XicCommon ic, XEvent *event) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  int x = event->xbutton.x;
  int y = event->xbutton.y;
  int curColumns, curRows;
  int candW;
  int candH;
  int currentCol = 0;
  int currentRow = 0;
  int w, h;
  int idx;

  if (!lookup) return -1;

  x = (x < xMargin ? 0: x - xMargin);
  y = (y < yMargin ? 0: y - yMargin);

  if (lookup->nrows == 1) {
    int i;
    curColumns = 0;
    w = 0;
    for (i = 0; i < lookup->ncolumns; i++) {
      w += lookup->candWidth[i] + cMargin;
      if (w > x)
	break;
      currentCol++;
    }
  } else {
    curColumns = (lookup->width + cMargin) / (lookup->maxWidth + cMargin);
    candW = (int)((lookup->width) / curColumns);

    for (w = candW; w < lookup->width; w += candW) {
      if (w > x) {
	break;
      }
      currentCol++;
    }
    currentCol = boundValue(currentCol, 0, curColumns - 1);
  }

  curRows = (lookup->height + cMargin) / (lookup->column_height + cMargin);
  candH = (int)((lookup->height) / curRows); 
  for (h = candH; h < lookup->height; h += candH) {
    if (h > y) {
      break;
    }
    currentRow++;
  }
  currentRow = boundValue(currentRow, 0, curRows - 1);

  idx = currentRow * curColumns + currentCol;
  return idx;
}

static void
selectCandidate(XicCommon ic, int idx) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  KeySym keysym;
  XEvent ev;
  char charValue;

  if (!XIM_IS_IIIMP(ic->core.im)) return;

  if (idx >= 0 && idx < lookup->length)
    charValue = lookup->labelArray[idx][0];
  else {
    fprintf(stderr,
	    "ButtonPress: something is wrong with %d index\n",
	    idx);
    return;
  }

  memset(&ev, 0, sizeof(XEvent));

  /* labelArray should be of 1 character length... */
  if (charValue >= 'a' && charValue <= 'z') {
    keysym = XK_a + charValue - 'a';
  } else if (charValue >= 'A' && charValue <= 'Z') {
    keysym = XK_A + charValue - 'A';
    ev.xkey.state |= ShiftMask;

  } else if (charValue >= '0' && charValue <= '9') {
    keysym = XK_0 + charValue - '0';
  } else {
      /* Index is neither alpabet nor number?, well let's assgin
	 index + 1 to keysym value */
      keysym = XK_0 + idx + 1;
  }
	  
  ev.xkey.x = 0;
  ev.xkey.y = 0;
  ev.xkey.x_root = 0;
  ev.xkey.y_root = 0;
  ev.xany.display = ic->core.im->core.display;
  ev.type = (int)KeyPress;
  ev.xkey.window = lookup->window;
  ev.xkey.keycode = XKeysymToKeycode(ev.xany.display,
				     keysym);

  IMForwardEvent(ic, &ev);
  PutBackXKeyEvent(ic);
  return;
}

static Bool
FilterButtonPress(Display *d, Window window,
		  XEvent *ev, XPointer client_data) {
  int idx;
  XicCommon ic = (XicCommon)client_data;

  idx = getChoiceIndex(ic, ev);
  if (idx == -1) return False;

  /* wait for button released */
  return True;
}

static Bool
FilterButtonRelease(Display *d, Window window,
		    XEvent *ev, XPointer client_data) {
  int idx;
  XicCommon ic = (XicCommon)client_data;
  XimCommon im = (XimCommon)ic->core.im;

  idx = getChoiceIndex(ic, ev);
  if (idx == -1) return False;

  if (XIM_IS_COMPOSE(im) &&
      (XIC_COMPOSE(ic, imstate)->type == LookupState ||
       XIC_COMPOSE(ic, imstate)->type == CodeInputState)) {
    Ximp_Local_Lookup_Button_Filter(ic, idx, ev); /* in lookup.c */
    return True;
  } else {
    selectCandidate(ic, idx);
  }
  return True;
}

static Bool
FilterMotionNotify(Display *d, Window w, XEvent *ev, XPointer client_data) {
  XicCommon ic = (XicCommon)client_data;
  int idx = getChoiceIndex(ic, ev);
  if (idx == -1) return False;
  highlightCandidate(ic, idx, True);
  return True;
}

static Bool
FilterKeyPress(Display *d, Window w, XEvent *ev, XPointer client_data) {
  XicCommon ic = (XicCommon)client_data;
  XimCommon im = (XimCommon)ic->core.im;
  if (XIM_IS_COMPOSE(im) &&
      (XIC_COMPOSE(ic, imstate)->type == LookupState ||
       XIC_COMPOSE(ic, imstate)->type == CodeInputState)) {
    Bool returnflag;
    returnflag = Ximp_Local_KeyFilter(d, w, ev, (XPointer)ic); /* in localIM.c */
    if (!returnflag) {
      ev->xkey.window = ic->core.focus_window;
      XPutBackEvent(d, ev);
    }
    return True;
  } else {
    IMForwardEvent(ic, ev);
    PutBackXKeyEvent(ic);
  }
  return True;
}

static Bool
RepaintLookup(Display *d, Window w, XEvent *ev, XPointer client_data) {
  LookupWin lookup;
  XicCommon ic = (XicCommon)client_data;
  lookup = (LookupWin)(ic->gui_icpart->lookup);
  if (!lookup) return False;
  UpdateLookup(ic);
  return True;
}

static void
UpdateLookup(XicCommon ic) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  int current_columns = lookup->ncolumns;
  int currentC = 0;
  int i;
  int rows;
  int x = xMargin;
  int y = yMargin;

  if (lookup->fontset) {
    XFontSetExtents *fse;
    fse = XExtentsOfFontSet(lookup->fontset);
    TRACE_MESSAGE('l', ("UpdateLookup: ink = (%d %d %d %d) logical = (%d %d %d %d)\n",
		  fse->max_ink_extent.x,
		  fse->max_ink_extent.y,
		  fse->max_ink_extent.width,
		  fse->max_ink_extent.height,
		  fse->max_logical_extent.x,
		  fse->max_logical_extent.y,
		  fse->max_logical_extent.width,
		  fse->max_logical_extent.height));
    y -= (fse->max_logical_extent.height + fse->max_logical_extent.y);
    y -= cMargin;
  }

  if (lookup->candidates == NULL || lookup->length == 0)
    return;

  if (lookup->ncolumns > lookup->length)
    current_columns = lookup->length;
  rows = 0;

  if (lookup->redraw & CONTENTS) {
    XClearArea(ic->core.im->core.display, lookup->window, 0, 0, 0, 0, False);
  }
  for (i = 0; i < lookup->length; i++) {
    if (i % current_columns == 0) {
      currentC = 0;
      rows++;
      y += lookup->column_height + cMargin;
      x = xMargin;
    } else {
      if (lookup->nrows == 1)
	x += lookup->candWidth[i-1] + cMargin;
      else
	x += lookup->maxWidth + cMargin;
      currentC++;
    }
    if (lookup->candidates->label_feedback &&
	lookup->candidates->value_feedback &&
	lookup->candidates->label_feedback[i] &&
	lookup->candidates->value_feedback[i]) {
      DrawLookupString2(ic, ic->core.im->core.display, lookup->window,
			lookup->fontset, lookup->gc, lookup->rgc,
			x, y, lookup->candidateArray[i],
			strlen(lookup->candidateArray[i]),
			lookup->candidates, i);
    } else {
      if (lookup->redraw == DRAW_NOTHING &&
	  i == lookup->previous) {
	DrawLookupString(ic->core.im->core.display, lookup->window,
			 lookup->fontset, lookup->gc, lookup->rgc, 0,
			 x, y, lookup->candidateArray[i],
			 strlen(lookup->candidateArray[i]));
	if (lookup->current != i) {
	  continue;
	}
      }
      if (i == lookup->current) {
	DrawLookupString(ic->core.im->core.display, lookup->window,
			 lookup->fontset, lookup->gc, lookup->rgc,
			 XIMReverse,
			 x, y, lookup->candidateArray[i],
			 strlen(lookup->candidateArray[i]));
	continue;
      }
      if (lookup->redraw & CONTENTS) {
	DrawLookupString(ic->core.im->core.display, lookup->window,
			 lookup->fontset, lookup->gc, lookup->rgc, 0,
			 x, y, lookup->candidateArray[i],
			 strlen(lookup->candidateArray[i]));
      }
    }
  }
  lookup->redraw = DRAW_ALL;
  return;
}

static void
highlightCandidate(XicCommon ic, int highlight_index, Bool highlight)
{ 
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  GC                  gc;
  int column;
  int row;
  int x;
  int y;
  int width;
  int height;

  if (highlight) {
    if (highlight_index == lookup->highlight_index) { /* nothing changed */
      return;
    } else { /* need to clear prior highlight */
      highlightCandidate(ic, lookup->highlight_index, False);
    }
  }

  if ((highlight_index < 0) || (lookup->length <= highlight_index)) {
	  return;
  }

  if (highlight) {
    gc = lookup->gc;
  } else {
    gc = lookup->rgc;
  }

  column = (highlight_index % lookup->ncolumns);
  row = (highlight_index / lookup->ncolumns);

  if (lookup->nrows == 1) {
    int i;
    x = xMargin / 2 ;
    for (i = 0; i < column; i++)
      x += lookup->candWidth[i] + cMargin;
  } else
    x = (xMargin / 2) + ((lookup->maxWidth + cMargin) * column);

  y = (yMargin / 2) + ((lookup->column_height + cMargin) * row);

  if (lookup->nrows == 1)
    width = lookup->candWidth[column] + xMargin - 1;
  else
    width = lookup->maxWidth + xMargin - 1;
  height = lookup->column_height + yMargin - 1;

  TRACE_MESSAGE('l', 
		("idx=%d column=%d row=%d x=%d y=%d width=%d height=%d\n",
		 highlight_index, column, row, x, y, width, height));

  XDrawRectangle(ic->core.im->core.display, lookup->window, gc,
		 x, y, width, height);

  if (highlight) {
    lookup->highlight_index = highlight_index;
  } else {
    lookup->highlight_index = -1;
  }

  return;
}

static Bool
SetupLookupExt(XicCommon ic) {
  LookupWin lookup;

  if (!(ic->ximp_icpart->value_mask & XIMP_CLIENT_WIN)) return False;

  lookup = (LookupWin)Xmalloc(sizeof(LookupWinRec));
  if (!lookup) return False;
  memset((char *)lookup, 0, sizeof(LookupWinRec));
  lookup->title_width = -1;
  ic->gui_icpart->lookup = (void *)lookup;
  return True;
}

static Bool
SetupLookupWindow(XicCommon ic) {
  Display *display = ic->core.im->core.display;
  Window parent, win;
  unsigned long fg, bg;
  unsigned long val_mask;
  XGCValues gcval;
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  XIMFilterRec filters[10];
  int	n;
  XClassHint class_hint;

  TRACE_MESSAGE('l', ("SetupLookupWindow: 0x%08x\n", lookup->window));

  if (lookup->window) return True; /* don't need to recreate a window */

  parent = RootWindow(display, XIC_GUI(ic, screen_number));

  lookup->x = lookup->y = 0;			/* default position */

  lookup->width = lookup->height = 10;		/* default size */

  lookup->x_real = lookup->y_real = -1;

  if (NULL != lookup->title) {
    free(lookup->title);
    lookup->title = NULL;
    lookup->title_width = -1;
  }

  if (XIMP_CHK_PREBGMASK(ic)) 
    bg = ic->core.preedit_attr.background;
  else
    bg = WhitePixel(display, XIC_GUI(ic, screen_number));

  if (XIMP_CHK_PREFGMASK(ic))
    fg = ic->core.preedit_attr.foreground;
  else
    fg = BlackPixel(display, XIC_GUI(ic, screen_number));

  n = 0;
  filters[n].type = KeyPress;
  filters[n].filter = FilterKeyPress;
  filters[n].client_data = (XPointer)ic;
  n++;
  filters[n].type = Expose;
  filters[n].filter = RepaintLookup;
  filters[n].client_data = (XPointer)ic;
  n++;
  filters[n].type = MotionNotify;
  filters[n].filter = FilterMotionNotify;
  filters[n].client_data = (XPointer)ic;
  n++;
  filters[n].type = ButtonPress;
  filters[n].filter = FilterButtonPress;
  filters[n].client_data = (XPointer)ic;
  n++;
  filters[n].type = ButtonRelease;
  filters[n].filter = FilterButtonRelease;
  filters[n].client_data = (XPointer)ic;
  n++;
  
  win = XFactoryCreateIMWindow(display, parent,
			       (ic->core.client_window ? ic->core.client_window:parent),
			       lookup->x, lookup->y, lookup->width, lookup->height,
			       bg,
			       KeyPressMask|ExposureMask|
			       ButtonPressMask |PointerMotionMask|
			       ButtonReleaseMask |
			       StructureNotifyMask,
			       filters, n);
  if (!win) return False;

  class_hint.res_name = "iiimx-im-lookup";
  class_hint.res_class = "IiimxImLookup";
  XSetClassHint(display, win, &class_hint);

  lookup->window = win;

  val_mask = GCForeground | GCBackground;
  gcval.foreground = fg;
  gcval.background = bg;
  lookup->gc = XCreateGC(display, win, val_mask, &gcval);
  gcval.foreground = bg;
  gcval.background = fg;
  lookup->rgc = XCreateGC(display, win, val_mask, &gcval);

  /* fontset */
  lookup->need_free_fontset = False;
  if (ic->core.status_attr.fontset) {
    lookup->fontset = ic->core.status_attr.fontset;
  }
  if (ic->core.preedit_attr.fontset) {
    lookup->fontset = ic->core.preedit_attr.fontset;
  }
  if (!lookup->fontset) {
    lookup->fontset = XFactoryCreateDefaultFontSet(display,
						   XIM_IIIMP(ic->core.im, default_font_name));
    lookup->need_free_fontset = True;
  }

  return True;
}

static void
moveLookupWindow(XicCommon ic) { 
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  XPoint point;
  int x = 0, y = 0;	/* default */

  point.x = point.y = 0;	/* default */
  if (XIMP_CHK_PREAREAMASK(ic)) {
    x = ic->core.preedit_attr.area.x;
    y = ic->core.preedit_attr.area.y;
    XFactoryGetLocationOnScreen(ic->core.im->core.display,
				ic->core.client_window, x, y, &point);
  } else if (XIMP_CHK_PRESPOTLMASK(ic)) {
    point.x = -1;
    point.y = -1;
    PreeditCaretPlacement(ic, &point);
    if ((-1 == point.x) && (-1 == point.y)) {
      XFontSetExtents *fse;
      fse = XExtentsOfFontSet(lookup->fontset);

      x = ic->core.preedit_attr.spot_location.x;
      y = ic->core.preedit_attr.spot_location.y;
      y += (fse->max_logical_extent.height + fse->max_logical_extent.y);
      y += yMargin;
    
      XFactoryGetLocationOnScreen(ic->core.im->core.display,
				  ic->core.focus_window, x, y, &point);
    } else {
      point.y += 4; /* offset */
    }
  } else {
      Window parent;
      Window root;
      int x_return,y_return;
      unsigned int width_return,height_return,border_width_return,depth_return;
      parent =  XFactoryGetFrameWindow (ic->core.im->core.display,
		      ic->core.focus_window);
      XGetGeometry (ic->core.im->core.display,
		      parent, &root,&x_return,&y_return,
		      &width_return,&height_return,&border_width_return, &depth_return);
      XFactoryGetLocationOnScreen(ic->core.im->core.display,
		      parent, 0, height_return, &point);
      point.x -= border_width_return;
      point.y += border_width_return;
  }

  if (lookup->fontset) {
    XFontSetExtents *fse;
    fse = XExtentsOfFontSet(lookup->fontset);
    if (fse) {
      int height = fse->max_logical_extent.height;
#if 0
      point.y += height;
#endif /* 0 */
      lookup->column_height = height;
    }
  }

  x = point.x;
  y = point.y;
  XFactoryAdjustPlacementInsideScreen(ic->core.im->core.display,
				      lookup->window,
				      point.x, point.y,
				      lookup->width + (2 * xMargin),
				      lookup->height + (2 * yMargin) + bMargin,
				      &x, &y);
  lookup->x_real = point.x;
  lookup->y_real = point.y;
  if ((False == lookup->mapped) ||
      (x != lookup->x_real) || (y != lookup->y_real)) {
    XSizeHints hints;
    hints.flags = PPosition;
    hints.x = x;
    hints.y = y;
    XSetWMNormalHints (ic->core.im->core.display,
		       lookup->window, &hints);
    XMoveWindow(ic->core.im->core.display, lookup->window, x, y);
  }
  lookup->x = point.x;
  lookup->y = point.y;
  TRACE_MESSAGE('l', ("moveLookupWindow: point=(%d, %d) lookup=(%d %d) x=%d y=%d\n",
		      point.x, point.y, lookup->x, lookup->y, x, y));
  return;
}

static void
resizeLookupWindow(XicCommon ic) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  int x;
  int y;
  int height;
  int width;

  width = lookup->width + (2 * xMargin);
  height = lookup->height + (2 * yMargin);
  XFactoryAdjustPlacementInsideScreen(ic->core.im->core.display,
				      lookup->window,
				      lookup->x, lookup->y,
				      width, height + bMargin,
				      &x, &y);
  if ((False == lookup->mapped) ||
      (x != lookup->x_real) || (y != lookup->y_real) ||
      (width != lookup->w_real) || (height != lookup->h_real)) {
    XMoveResizeWindow(ic->core.im->core.display, lookup->window,
		      x, y, width, height);
    lookup->x_real = x;
    lookup->y_real = y;
    lookup->w_real = width;
    lookup->h_real = height;
  }
  TRACE_MESSAGE('l', ("resizeLookupWindow %d %d %d %d %d %d %d %d\n",
		      lookup->x, lookup->y, x, y,
		      lookup->width, lookup->height, width, height));
}

static LUCUpdate
CompareLookupData(XIMChoiceObject2 *lc1, int lc1_start,
		  XIMChoiceObject2 *lc2, int lc2_start,
		  int length) {
  int i, j;

  if (lc1 == NULL || lc2 == NULL)
    return DRAW_NOTHING;
        
  for (i = 0; i < length; i++) {
    char* lc1_value = lc1->value[lc1_start + i].string.multi_byte;
    char* lc2_value = lc2->value[lc2_start + i].string.multi_byte;
    if (lc1_value && lc2_value) {
      if (strcmp(lc1_value, lc2_value)) return CONTENTS;
    }
    else {
      return CONTENTS;
    }
  }
  if (lc1->value_feedback == 0 ||
      lc2->value_feedback == 0 ||
      lc1->value_feedback[lc1_start] == 0 ||
      lc2->value_feedback[lc2_start] == 0) {
    return DRAW_NOTHING;
  }
  for (i = 0; i < length; i++) {
    IMFeedbackList *p = lc1->value_feedback[lc1_start + i];
    IMFeedbackList *q = lc2->value_feedback[lc2_start + i];
    int feedback_len = lc1->value[lc1_start + i].length;
    if (p->count_feedbacks != q->count_feedbacks) return FEEDBACKS;
    for (j = 0; j < feedback_len; j++) {
      if (p->feedbacks[j].type != q->feedbacks[j].type ||
	  p->feedbacks[j].value != q->feedbacks[j].value) {
	return FEEDBACKS;
      }
    }
  }
  return DRAW_NOTHING;
}

static void
FreeLookupData(LookupWin lookup) {
  if (lookup->array_size == 0) return;

  if (lookup->n_choices && lookup->candidates) {
    XIMText *label = lookup->candidates->label;
    XIMText *value = lookup->candidates->value;
    int i;

    for (i = 0; i < lookup->n_choices; i++) {
      if (lookup->candidates->label_feedback)
	FreeFeedbackList(lookup->candidates->label_feedback[i],
			 label[i].length);
      if (lookup->candidates->value_feedback)
	FreeFeedbackList(lookup->candidates->value_feedback[i],
			 value[i].length);
      Xfree(lookup->candidateArray[i]);
      Xfree(lookup->labelArray[i]);
      Xfree(value[i].feedback);
      Xfree(label[i].feedback);
      Xfree(value[i].string.multi_byte);
      Xfree(label[i].string.multi_byte);
    }
    Xfree(value);
    Xfree(label);
    if (lookup->candidates->label_feedback)
      Xfree(lookup->candidates->label_feedback);
    if (lookup->candidates->value_feedback)
      Xfree(lookup->candidates->value_feedback);
  }
}

static void
CopyFeedbackList(XicCommon ic, IMFeedbackList **to_flist,
		 IMFeedbackList *from_flist,
		 int len) {
  IMFeedbackList *from, *to, *top;

  if (!from_flist) {
    *to_flist = 0;
    return;
  }
  top = (IMFeedbackList*)Xmalloc(sizeof(IMFeedbackList) * len);
  if (!top) return;

  for (from = from_flist, to = top; to < &top[len]; from++, to++) {
    to->count_feedbacks = from->count_feedbacks;
    to->feedbacks = Xmalloc(sizeof(IMFeedback) * to->count_feedbacks);
    if (to->feedbacks) {
      IMFeedback *p, *q;
      for (p = to->feedbacks, q = from->feedbacks;
	   p < &to->feedbacks[to->count_feedbacks];
	   p++, q++) {
	p->type = q->type;
	p->value = q->value;
      }
    }
  }
  *to_flist = top;
  return;
}

static void
CopyXIMText(XicCommon ic, XIMText *to, XIMText *from) {
  to->length = from->length;
  to->encoding_is_wchar = False;

  to->feedback = Xmalloc(sizeof(XIMFeedback) * to->length);

  if (from->encoding_is_wchar == False) {
    int len;
    if (from->string.multi_byte) {
      len = strlen(from->string.multi_byte);
      to->string.multi_byte = Xmalloc(len + 1);
      strcpy(to->string.multi_byte, from->string.multi_byte);
    }
    else {
      len = 0;
      to->string.multi_byte = NULL;
    }
  } else {
    XimCommon im = (XimCommon)ic->core.im;
    int len = from->length * XLC_PUBLIC(im->core.lcd, mb_cur_max);
    to->string.multi_byte = Xmalloc(len + 1);
    if ((len = IIimpWcstoMbs(im, from->string.wide_char, from->length,
			     to->string.multi_byte, len, NULL)) < 0) {
      return;
    }
    to->string.multi_byte[len] = 0;
  }
  (void)memmove((void *)(to->feedback), (void *)(from->feedback),
		sizeof(XIMFeedback) * to->length);
  return;
}

static Bool
CopyLookupData(XicCommon ic, LookupWin lookup,
	       XIMLookupDrawCallbackStruct2 *call_data) {
  int i;
  char *candidate;
  int len;
  int realRow = 0;
  int cand_width = 0;
  char *value_str, *label_str;
  XIMText *value;
  XIMText *label;
  IMFeedbackList **value_flist, **label_flist;
  int actual_width = 0;

  if (lookup->array_size == 0) {
    lookup->array_size = 26;	/* init size */
    lookup->candidateArray = Xmalloc(sizeof(char*) * lookup->array_size);
    lookup->labelArray = Xmalloc(sizeof(char*) * lookup->array_size);
    lookup->candWidth = Xmalloc(sizeof(int) * lookup->array_size);
    lookup->candidates = Xmalloc(sizeof(XIMChoiceObject2));
  }
  if (lookup->array_size < lookup->n_choices) {
    lookup->array_size = lookup->n_choices;
    lookup->candidateArray = Xrealloc(lookup->candidateArray,
				    sizeof(char*) * lookup->array_size);
    lookup->labelArray = Xrealloc(lookup->labelArray,
				sizeof(char*) * lookup->array_size);
    lookup->candWidth = Xrealloc(lookup->candWidth,
				 sizeof(int) * lookup->array_size);
  }
  if (!lookup->candidateArray || !lookup->labelArray ||
      !lookup->candidates) {
    /* something wrong */
    return False;
  }

  lookup->candidates->label = Xmalloc(sizeof(XIMText) * lookup->n_choices);
  lookup->candidates->value = Xmalloc(sizeof(XIMText) * lookup->n_choices);

  if (!lookup->candidates->label || !lookup->candidates->value) {
    /* something wrong */
    return False;
  }
  if (call_data->choices->label_feedback &&
      call_data->choices->value_feedback) {
    lookup->candidates->label_feedback =
      Xmalloc(sizeof(IMFeedbackList*) * lookup->n_choices);
    lookup->candidates->value_feedback =
      Xmalloc(sizeof(IMFeedbackList*) * lookup->n_choices);
    if(!lookup->candidates->label_feedback ||
       !lookup->candidates->value_feedback) {
      /* something wrong */
      return False;
    }
    memset(lookup->candidates->label_feedback, 0,
	   sizeof(IMFeedbackList*) * lookup->n_choices);
    memset(lookup->candidates->value_feedback, 0,
	   sizeof(IMFeedbackList*) * lookup->n_choices);
  } else {
    lookup->candidates->label_feedback = 0;    
    lookup->candidates->value_feedback = 0;    
  }

  label = lookup->candidates->label;
  value = lookup->candidates->value;
  label_flist = lookup->candidates->label_feedback;
  value_flist = lookup->candidates->value_feedback;

  lookup->maxWidth = 0;
  for (i = 0; i < lookup->n_choices; i++) {
    CopyXIMText(ic, &(label[i]), &(call_data->choices->label[i]));
    CopyXIMText(ic, &(value[i]), &(call_data->choices->value[i]));
    if (label_flist)
      CopyFeedbackList(ic, &(label_flist[i]),
		       call_data->choices->label_feedback[i],
		       label[i].length);
    if (value_flist)
      CopyFeedbackList(ic, &(value_flist[i]),
		       call_data->choices->value_feedback[i],
		       value[i].length);

    label_str = label[i].string.multi_byte;
    value_str = value[i].string.multi_byte;
    if (call_data->choices->label_decoration != NO_DECORATION) {
      len = (label_str ? strlen(label_str) : 0) +
	(value_str ? strlen(value_str) : 0) + 5;
    } else {
      len = (label_str ? strlen(label_str) : 0) +
	(value_str ? strlen(value_str) : 0) + 7;
    }
    candidate = Xmalloc(len);
    candidate[0] = 0;
    if (label_str) {
      switch (call_data->choices->label_decoration) {
      case BRACKET_DECORATION:
	strcpy(candidate, "(");
	strcat(candidate, label_str);
	strcat(candidate, ")");
	break;
      case NO_DECORATION:
      default:
	strcpy(candidate, label_str);
	break;
      }
      strcat(candidate, " ");
    }
    if (value_str) strcat(candidate, value_str);
    len = strlen(candidate);
    if (lookup->maxWidth < (cand_width = XmbTextEscapement(lookup->fontset,
							   candidate, len))) {
      lookup->maxWidth = cand_width;
    }
    actual_width += cand_width;

    lookup->candidateArray[i] = candidate;
    lookup->labelArray[i] = (char*)strdup((label_str ? label_str : ""));
    lookup->candWidth[i] = cand_width;
    if (i % lookup->ncolumns == 0) {
      realRow++;
    }
  }

  /* adjust maxWidth for displaying title */
  if ((lookup->maxWidth * lookup->ncolumns) < lookup->title_width) {
    lookup->maxWidth = (lookup->title_width / lookup->ncolumns);
  }

  if (lookup->nrows == 1)
    lookup->width = actual_width + cMargin * (lookup->length > lookup->ncolumns ?
					      lookup->ncolumns : lookup->length); 
  else
    lookup->width = (lookup->maxWidth + cMargin) * (lookup->length > lookup->ncolumns ?
						    lookup->ncolumns : lookup->length);
  lookup->height = (lookup->column_height + cMargin) * realRow;
  lookup->width -= cMargin;
  lookup->height -= cMargin;

  return True;
}

static void
setCandidates(XicCommon ic, XPointer p) {
  XIMLookupDrawCallbackStruct2 *call_data = (XIMLookupDrawCallbackStruct2*)p;
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);

  lookup->redraw = DRAW_ALL;		/* default */

  if (lookup->length == (call_data->last_index - call_data->first_index + 1) &&
      DRAW_NOTHING == (lookup->redraw = CompareLookupData(lookup->candidates,
							  lookup->first,
							  call_data->choices,
							  call_data->first_index,
							  lookup->length))) {
    /* nothing to do */
  } else {
    FreeLookupData(lookup);
    lookup->n_choices = call_data->n_choices;
    lookup->first = call_data->first_index;
    lookup->last = call_data->last_index;
    lookup->length = lookup->last - lookup->first + 1;

    if (lookup->length != lookup->n_choices) 
      fprintf(stderr, "something is wrong?\n");

    if (!CopyLookupData(ic, lookup, call_data)) {
      DestroyLookup(ic, NULL);
      return;
    }
  }
  lookup->previous = lookup->current;
  lookup->current = call_data->current_index;
  return;
}

static void
setTitle(XicCommon ic, XPointer p) {
  XIMLookupDrawCallbackStruct2 *call_data = (XIMLookupDrawCallbackStruct2*)p;
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  Display *display = ic->core.im->core.display;
  XTextProperty prop;
  char *title_string;

  title_string = NULL;

  if ((call_data->title != NULL) && 
      (call_data->title->length != 0)) {
    int title_width;

    title_string = call_data->title->string.multi_byte;

    if ((lookup->title_width <= 0) ||
	((NULL != lookup->title) &&
	 (0 != strcmp(lookup->title, title_string)))) {
      title_width = XmbTextEscapement(lookup->fontset,
				      title_string,
				      strlen(title_string));
      XFactoryRemoveDecorationExceptTitle(display, lookup->window);
      free(lookup->title);
      lookup->title = strdup(title_string);
      lookup->title_width = title_width;
    } else {
      title_width = lookup->title_width;
      title_string = NULL;
    }
    if (lookup->maxWidth * lookup->ncolumns < title_width) {
      lookup->maxWidth = title_width/lookup->ncolumns;
    }
  } else {
    if ((NULL != lookup->title) || (-1 == lookup->title_width)) {
      free(lookup->title);
      lookup->title = NULL;
      lookup->title_width = 0;
      XFactoryRemoveDecoration(display, lookup->window);
#ifdef	linux
      title_string = "iiimx IM Lookup";	/* XFactoryRemoveDecoration()
					   will not work for some window
					   manager */
#else
      title_string = "";			/* empty string */
#endif
    }
  }

  if (NULL != title_string) {
    XmbTextListToTextProperty(display,
			      &title_string, 1,
			      XCompoundTextStyle, &prop);
    XSetWMName(display, lookup->window, &prop);
    XFree(prop.value);
  }

  return;
}


static void
DestroyLookup(XicCommon ic, XPointer p) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);

  if (!lookup) return;

  if (lookup->fontset && lookup->need_free_fontset) {
    XFactoryFreeDefaultFontSet (ic->core.im->core.display);
    lookup->need_free_fontset = False;
  }
  XDestroyWindow(ic->core.im->core.display,
		 lookup->window);
  _XUnregisterFilter(ic->core.im->core.display, lookup->window,
		     RepaintLookup, (XPointer)ic);
  _XUnregisterFilter(ic->core.im->core.display, lookup->window,
		     FilterKeyPress, (XPointer)ic);
  _XUnregisterFilter(ic->core.im->core.display, lookup->window,
		     FilterMotionNotify, (XPointer)ic);
  _XUnregisterFilter(ic->core.im->core.display, lookup->window,
		     FilterButtonPress, (XPointer)ic);
  _XUnregisterFilter(ic->core.im->core.display, lookup->window,
		     FilterButtonRelease, (XPointer)ic);

  if (lookup->gc) XFreeGC(ic->core.im->core.display, lookup->gc);
  if (lookup->rgc) XFreeGC(ic->core.im->core.display, lookup->rgc);

  if (lookup->candidateArray) Xfree(lookup->candidateArray);
  if (lookup->labelArray) Xfree(lookup->labelArray);
  if (lookup->candidates) Xfree(lookup->candidates);
  if (lookup->candWidth) Xfree(lookup->candWidth);
  Xfree(lookup);
  ic->gui_icpart->lookup = (LookupWin)NULL;
  return;
}

static void
createProc(XicCommon ic, XPointer p) {
  /* nothing to do until startProc is called  */
  return;
}

static void
startProc(XicCommon ic, XPointer p) {
  XIMLookupStartCallbackStruct2 *call_data = (XIMLookupStartCallbackStruct2*)p;
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);

  TRACE_MESSAGE('l', ("startProc\n"));

  if (!lookup) {
    SetupLookupExt(ic);
    lookup = ic->gui_icpart->lookup;
  }
  if (!lookup) return;

  SetupLookupWindow(ic);

  moveLookupWindow(ic);

  lookup->ncolumns = call_data->ncolumns;
  lookup->nrows = call_data->nrows;
  lookup->choice_per_window = call_data->choice_per_window;
  return;
}

static void
drawProc(XicCommon ic, XPointer p) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);

  TRACE_MESSAGE('l', ("drawProc\n"));

  if (!lookup) return;

  setTitle(ic, p);

  setCandidates(ic, p);

  resizeLookupWindow(ic);

  if (IsIMLookupEnabled ()) {
    XMapRaised(ic->core.im->core.display, lookup->window);

    if (True == lookup->mapped) {
      UpdateLookup(ic);
    }

    lookup->mapped = True;
  }

  return;
}

static void
processProc(XicCommon ic, XPointer p) {
  fprintf(stderr, "lookup process\n");
}

static void
doneProc(XicCommon ic, XPointer p) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);

  TRACE_MESSAGE('l', ("doneProc\n"));

  if (!lookup) return;

  if (IsIMLookupEnabled () || lookup->mapped) {
    lookup->mapped = False;
    XUnmapWindow(ic->core.im->core.display,
		 lookup->window);
  }

  FreeLookupData(lookup);
  lookup->n_choices = 0;
  lookup->first = 0;
  lookup->last = 0;
  lookup->length = 0;

  if (NULL != lookup->title) {
    free(lookup->title);
    lookup->title = NULL;
    lookup->title_width = -1;
  }

  return;
}

static void
moveProc(XicCommon ic, XPointer p) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  Display *display = ic->core.im->core.display;
  XTextProperty prop;

  if (!lookup) return;

  /* if lookup window has a title, we don't move it */
  if (XGetWMName(display, lookup->window, &prop)) {
    if (prop.nitems == 0)
      /* we move lookup window if there is no title */
      moveLookupWindow(ic);
  }
  return;
}

static void
setFocusLookup(XicCommon ic, XPointer p) {
  LookupWin lookup = (LookupWin)(ic->gui_icpart->lookup);
  Display *display = ic->core.im->core.display;

  if (!lookup) return;
  if (lookup->mapped)
     XRaiseWindow(display, lookup->window);
  return;
}
typedef void (*lookupCB)(XicCommon, XPointer);

static lookupCB lookup_methods[] = {
  createProc,			/* LOOKUP_CREATE */
  startProc,			/* LOOKUP_START */
  drawProc,			/* LOOKUP_DRAW */
  processProc,			/* LOOKUP_PROCESS */
  doneProc,			/* LOOKUP_DONE */
  moveProc,			/* LOOKUP_MOVE */
  DestroyLookup,		/* LOOKUP_DESTROY */
  setFocusLookup		/* LOOKUP_SETFOCUS */
};

/* Public function */
void
ChangeLookup(XIC xic, LookupAttr reason, XPointer call_data) {
  XicCommon ic = (XicCommon)xic;
  if (reason > LOOKUP_SETFOCUS) return;
  lookup_methods[reason](ic, call_data);
  return;
}
