/*
 * Copyright 2003 Sun Microsystems Inc.
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Hidetoshi Tajima <hidetoshi.tajima@sun.com>
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <link.h>
#include <stdlib.h>
#include <string.h>
#include "iiimcf.h"
#include "gtkimcontextiiim.h"
#include <gdk/gdkx.h>
#include "imaux.h"
#include "imaux-int.h"

static void iiim_aux_download (IIIMCF_downloaded_object obj);

#define AUX_BASE_DIR		"/usr/lib/im/"

#define IS_SPACE(len, ptr)	((0 < (len)) &&				\
				 (('\t' == *(p)) || (' ' == *(p))))
#define IS_EOL(len, ptr)	(((len) <= 0) || ('\n' == *(p)))
#define IS_NOT_EOL(len, ptr)	((0 < (len)) && ('\n' != *(p)))

static aux_handle_t *aux_load (char *aux_file_name);
static aux_handle_t *aux_conf_load (char *aux_file_name);
static aux_handle_t *aux_so_load (char * aux_file_name);

static aux_t *aux_get (GtkIMContextIIIM *context_iiim, IIIMCF_event ev,
		       const IIIMP_card16 *aux_name);
static aux_entry_t *aux_entry_get (const IIIMP_card16 *name);

typedef struct {
  int len;
  aux_t *aux;
  IIIMCF_event ev;
  aux_data_t *pad;
} AUXComposed;

/*
 * X auxiliary object service method
 */
static void		service_aux_setvalue(aux_t *,
					     const unsigned char *, int);
static void		service_aux_getvalue(aux_t *,
					     const unsigned char *, int);
static int		service_im_id(aux_t *);
static int		service_ic_id(aux_t *);
static void		service_data_set(aux_t *, int, void *);
static void *		service_data_get(aux_t *, int);
static Display *	service_display(aux_t *);
static Window		service_window(aux_t *);
static XPoint *		service_point(aux_t *, XPoint *);
static XPoint *		service_point_caret(aux_t *, XPoint *);
static size_t		service_utf16_mb(const char **, size_t *,
					 char **, size_t *);
static size_t		service_mb_utf16(const char **, size_t *,
					 char **, size_t *);
static unsigned char *	service_compose(const aux_data_t *, int *);
static int		service_compose_size(aux_data_type_t,
					     const unsigned char *);
static aux_data_t *	service_decompose(aux_data_type_t,
					  const unsigned char *);
static void		service_decompose_free(aux_data_t *);
static void		service_register_X_filter(Display *, Window, int, int,
						  Bool (* filter)(Display *,
								  Window,
								  XEvent *,
								  XPointer),
						  XPointer);
static void		service_unregister_X_filter(Display *, Window,
						    Bool (* filter)(Display *,
								    Window,
								    XEvent *,
								    XPointer),
						    XPointer);
static Bool		service_server(aux_t *);
static Window		service_client_window(aux_t *);
static Window		service_focus_window(aux_t *);
static int		service_screen_number(aux_t *);
static int		service_point_screen(aux_t *, XPoint *);
static int		service_point_caret_screen(aux_t *, XPoint *);
static Bool		service_get_conversion_mode(aux_t*);
static void		service_set_conversion_mode(aux_t*, int);
static aux_t *		service_aux_get_from_id(int im_id, int ic_id,
						IIIMP_card16 *aux_name,
						int aux_name_length);

static aux_service_t
g_aux_service = {
  service_aux_setvalue,
  service_im_id,
  service_ic_id,
  service_data_set,
  service_data_get,
  service_display,
  service_window,
  service_point,
  service_point_caret,
  service_utf16_mb,
  service_mb_utf16,
  service_compose,
  service_compose_size,
  service_decompose,
  service_decompose_free,
  service_register_X_filter,
  service_unregister_X_filter,
  service_server,
  service_client_window,
  service_focus_window,
  service_screen_number,
  service_point_screen,
  service_point_caret_screen,
  service_get_conversion_mode,
  service_set_conversion_mode,
  service_aux_getvalue,
  service_aux_get_from_id
};

static aux_ic_info_t *	aux_ic_info = NULL;

/* public function */
void
iiim_aux_start (GtkIMContextIIIM *context_iiim,
		IIIMCF_event ev)
{
  aux_t *aux;
  AUXComposed ac;

  aux = aux_get (context_iiim, ev, NULL);
  if (!aux)
    return;

  memset(&ac, 0, sizeof(ac));
  ac.aux = aux;
  ac.ev = ev;

  aux->im->ae->dir.method->start (aux, 
				  (XPointer) &ac,
				  /* Always set 0! */
				  0);
  return;
}

void
iiim_aux_draw (GtkIMContextIIIM *context_iiim,
	       IIIMCF_event ev)
{
  aux_t *aux;
  AUXComposed ac;

  aux = aux_get (context_iiim, ev, NULL);
  if (!aux)
    return;

  memset(&ac, 0, sizeof(ac));
  ac.aux = aux;
  ac.ev = ev;

  aux->im->ae->dir.method->draw(aux,
				(XPointer) &ac,
				/* Always set 0! */
				0);
  return;
}

void
iiim_aux_done (GtkIMContextIIIM *context_iiim,
	       IIIMCF_event ev)
{
  aux_t *aux;
  AUXComposed ac;

  aux = aux_get (context_iiim, ev, NULL);
  if (!aux)
    return;

  memset(&ac, 0, sizeof(ac));
  ac.aux = aux;
  ac.ev = ev;

  aux->im->ae->dir.method->done(aux,
				(XPointer) &ac,
				/* Always set 0! */
				0);
  return;
}

void
iiim_aux_getvalues_reply (GtkIMContextIIIM *context_iiim,
			  IIIMCF_event ev)
{
  aux_t *aux;
  AUXComposed ac;

  aux = aux_get (context_iiim, ev, NULL);
  if (!aux)
    return;

  memset(&ac, 0, sizeof(ac));
  ac.aux = aux;
  ac.ev = ev;

  if (aux->im->ae->dir.method->getvalues_reply)
      aux->im->ae->dir.method->getvalues_reply(aux,
					       (XPointer) &ac,
					       /* Always set 0! */
					       0);
  return;
}

IIIMF_status
iiim_setup_aux_object (IIIMCF_handle iiim)
{
  IIIMF_status st;
  IIIMCF_object_descriptor *pdesc;
  IIIMCF_object_descriptor **ppdescs;
  IIIMCF_downloaded_object *pobjs;
  int i, n1, n2;

  n1 = 0;
  pdesc = NULL;
  st = iiimcf_get_object_descriptor_list (iiim, &n1, &pdesc);

  if (st != IIIMF_STATUS_SUCCESS)
    return st;

  pobjs = g_new0 (IIIMCF_downloaded_object, n1);
  ppdescs = g_new0 (IIIMCF_object_descriptor*, n1);

  for (i = 0, n2 = 0; i < n1; i++, pdesc++)
    {
      if (pdesc->predefined_id == IIIMP_IMATTRIBUTE_BINARY_GUI_OBJECT)
	ppdescs[n2++] = pdesc;
    }
  if (n2 > 0)
    {
      st =  iiimcf_get_downloaded_objects (iiim, n2, ppdescs, pobjs);
      if (st != IIIMF_STATUS_SUCCESS)
	{
	  g_free (ppdescs);
	  g_free (pobjs);
	  return st;
	}
      for (i = 0; i < n2; i++)
	iiim_aux_download (pobjs[i]);
    }
  g_free (ppdescs);
  g_free (pobjs);

  return IIIMF_STATUS_SUCCESS;
}

void
IIim_aux_destrory_ic (GtkIMContextIIIM *context_iiim)
{
  /* MADA aux->im->ae->dir.method->destroy_ic(); */
}

void
IIim_aux_set_icfocus (GtkIMContextIIIM *context_iiim)
{
  /* MADA aux->im->ae->dir.method->set_icfocus(); */
}

void
IIim_aux_unset_icfocus (GtkIMContextIIIM *context_iiim)
{
  /* MADA aux->im->ae->dir.method->unset_icfocus(); */
}

static gchar*
convert_to_string (const IIIMP_card16 *ucstr)
{
  glong read_len, write_len;
  gchar *result;
  result = g_utf16_to_utf8 (ucstr, -1, &read_len, &write_len, NULL);

  return result;
}

static gunichar2*
convert_to_utf16 (const char *str, const size_t len, glong *to_left)
{
  glong read_len, write_len;
  gunichar2 *result = NULL;
  result = g_utf8_to_utf16 (str, len, &read_len, &write_len, NULL);

  *to_left = write_len;

  return result;
}

/*
 * object downloading
 */
void
iiim_aux_download (IIIMCF_downloaded_object obj)
{
  char *aux_file_name;
  char *aux_file_name_buf;
  int	aux_file_name_len;
  char *file_name = NULL;
  char *dir_name;
  int	dir_name_len;
  IIIMF_status st;
  const IIIMP_card16 *u16filename;

  aux_file_name = NULL;
  aux_file_name_buf = NULL;

  st = iiimcf_get_downloaded_object_filename (obj, &u16filename);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  aux_file_name = convert_to_string (u16filename);
  if (aux_file_name == NULL)
    return;

  aux_file_name_buf = aux_file_name;

  if (!aux_file_name)
    return;

  aux_file_name_len = strlen (aux_file_name);

  /*
   * may not start with "/"
   * may not start with "../"
   * may not contain "/../"
   * may not end with "/"
   * may not end with "/."
   * may not end with "/.."
   * may not be ".."
   */

  if (((1 <= aux_file_name_len) &&
       ('/' == *(aux_file_name + 0))) ||
      ((3 <= aux_file_name_len) &&
       ('.' == *(aux_file_name + 0)) &&
       ('.' == *(aux_file_name + 1)) &&
       ('/' == *(aux_file_name + 2))) ||
      (NULL != strstr(aux_file_name, "/../")) ||
      ((1 <= aux_file_name_len) &&
       ('/' == *(aux_file_name + aux_file_name_len - 1))) ||
      ((2 <= aux_file_name_len) &&
       ('/' == *(aux_file_name + aux_file_name_len - 2)) &&
       ('.' == *(aux_file_name + aux_file_name_len - 1))) ||
      ((3 <= aux_file_name_len) &&
       ('/' == *(aux_file_name + aux_file_name_len - 3)) &&
       ('.' == *(aux_file_name + aux_file_name_len - 2)) &&
       ('.' == *(aux_file_name + aux_file_name_len - 1))) ||
      ((2 == aux_file_name_len) &&
       ('.' == *(aux_file_name + 0)) &&
       ('.' == *(aux_file_name + 1))))
    {
      g_free (aux_file_name_buf);
      return;
    }

  /*
   * eliminate leading "./"
   */
  if ((2 <= aux_file_name_len) &&
      ('.' == *(aux_file_name + 0)) &&
      ('/' == *(aux_file_name + 1)))
    {
      aux_file_name += 2;
      aux_file_name_len -= 2;
    }

  dir_name = AUX_BASE_DIR;
  dir_name_len = strlen (dir_name);

  file_name = g_strconcat (dir_name, aux_file_name, NULL);

  (void)aux_load (file_name);

  g_free (aux_file_name_buf);
  g_free (file_name);

  return;
}

/*
 * internal method
 */
static aux_handle_t *
aux_load (char *aux_file_name)
{
  int fd;
  char buf[64];
  int magic_len;
  int len;

  if (-1 == (fd = open(aux_file_name, O_RDONLY, 0)))
    return NULL;

  magic_len = strlen (AUX_CONF_MAGIC);

  len = read (fd, buf, magic_len);

  close(fd);

  if ((len == magic_len) &&
      (0 == memcmp (buf, AUX_CONF_MAGIC, len)))
    return aux_conf_load (aux_file_name);
  else
    return aux_so_load (aux_file_name);
}

static aux_handle_t *aux_handle = NULL;

static aux_handle_t *
aux_conf_load(
    char * aux_file_name
)
{
    int		fd;
    struct stat	st_buf;
    void *		addr;
    char *		p;
    char *		aux_name;
    int		aux_name_len;
    char *		aux_so;
    int		aux_so_len;
    aux_handle_t *	ah;
    int		len;
    int		dir_name_len;
    char *		dir_name_last;

    dir_name_last = strrchr(aux_file_name, '/');
    if (NULL == dir_name_last) {
	return NULL;
    }
    dir_name_len = ((dir_name_last - aux_file_name) + 1);

    if (-1 == (fd = open(aux_file_name, O_RDONLY, 0))) {
	return NULL;
    }

    if (0 != fstat(fd, &st_buf)) {
	close(fd);
	return NULL;
    }

    addr = mmap(0, st_buf.st_size,
		PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);

    close(fd);

    if (MAP_FAILED == addr) {
	return NULL;
    }

    ah = NULL;
	
    for (p = addr, len = st_buf.st_size; 0 < len; ) {
	if ('#' == *p) {
	    while (IS_NOT_EOL(len, p)) {
		p++;
		--len;
	    }
	    if (IS_EOL(len, p)) {
		p++;
		--len;
	    }
	    continue;
	}

	while (IS_SPACE(len, p)) {
	    p++;
	    --len;
	}
	if (IS_EOL(len, p)) {
	    p++;
	    --len;
	    continue;
	}

	aux_name = p;
	while ((!(IS_SPACE(len, p))) && IS_NOT_EOL(len, p)) {
	    p++;
	    --len;
	}
	if (IS_EOL(len, p)) {
	    p++;
	    --len;
	    continue;
	}

	aux_name_len = (p - aux_name);

	while (IS_SPACE(len, p)) {
	    p++;
	    --len;
	}
	if (IS_EOL(len, p)) {
	    p++;
	    --len;
	    continue;
	}

	aux_so = p;
	while ((!(IS_SPACE(len, p))) && IS_NOT_EOL(len, p)) {
	    p++;
	    --len;
	}
	aux_so_len = (p - aux_so);

	ah = (aux_handle_t *)g_new0 (aux_handle_t, 1);
	if (NULL == ah) {
	    break;
	}

	ah->aux_name.len = 0;
	ah->aux_name.ptr = NULL;

	ah->aux_name.ptr = convert_to_utf16 (aux_name, aux_name_len,
					     (glong*)&ah->aux_name.len);
	if ('/' == *aux_so) {
	    ah->file_name = g_new0 (gchar, aux_so_len + 1);
	} else {
	    ah->file_name = g_new0 (gchar, dir_name_len + aux_so_len + 1);
	}
	if (NULL == ah->file_name) {
	    g_free(ah->aux_name.ptr);
	    g_free(ah);
	    break;
	}
	if ('/' == *aux_so) {
	    memcpy(ah->file_name, aux_so, aux_so_len);
	    *(ah->file_name + aux_so_len) = '\0';
	} else {
	    memcpy(ah->file_name, aux_file_name, dir_name_len);
	    memcpy(ah->file_name + dir_name_len, aux_so, aux_so_len);
	    *(ah->file_name + dir_name_len + aux_so_len) = '\0';
	}
	ah->handle = NULL;
	ah->ae = NULL;
	ah->ae_num = 0;
	ah->next = aux_handle;
	aux_handle = ah;
    }

    munmap(addr, st_buf.st_size);

    return ah;
}


/*
 *
 */
static aux_dir_t *
get_aux_dir_from_aux_info (void *handle, unsigned int *ifversion)
{
    aux_info_t *aux_info;

    aux_info = (aux_info_t*)dlsym(handle, AUX_INFO_SYMBOL);

    if (aux_info && aux_info->if_version >= AUX_IF_VERSION_2
	    && aux_info->register_service != NULL) {
	aux_info->register_service (AUX_IF_VERSION_2, &g_aux_service);
	*ifversion = aux_info->if_version;
	return aux_info->dir;
    }

    return NULL;
}


/*
 *
 */
static aux_handle_t *
aux_so_load(
    char * aux_file_name
)
{
    void *		handle = (void *)NULL;
    aux_dir_t *	aux_dir;
    aux_dir_t *	ad;
    int		adn;
    aux_handle_t *	ah;
    aux_handle_t *	ah_free;
    int		i;
    unsigned int	ifversion;

    /*
     * check whether the object is already loaded
     */
    for (ah = aux_handle; NULL != ah; ah = ah->next) {
	if ((0 == strcmp(aux_file_name, ah->file_name)) &&
	    (NULL != ah->handle)) {
	    return ah;
	}
    }

    /*
     * load the object and construct aux_handle_t structure for it
     */
    handle = dlopen(aux_file_name, RTLD_LAZY);
    if (NULL == handle) {
	return NULL;
    }

    if ((aux_dir = get_aux_dir_from_aux_info (handle, &ifversion)) == NULL) {
	aux_dir = (aux_dir_t *)dlsym(handle, AUX_DIR_SYMBOL);
	if (NULL == aux_dir) {
	    dlclose(handle);
	    return NULL;
	}
	ifversion = 0;
    }

    for (adn = 0, ad = aux_dir; 0 < ad->name.len; ad += 1, adn += 1);

    if (NULL == ah) {
	ah = (aux_handle_t *)g_new0 (aux_handle_t, 1);
	if (NULL == ah) {
	    dlclose(handle);
	    return NULL;
	}
	memset(ah, 0, sizeof (aux_handle_t));

	ah_free = ah;
    } else {
	ah_free = NULL;
    }

    if (NULL == ah->file_name) {
	ah->file_name = strdup(aux_file_name);
	if (NULL == ah->file_name) {
	    g_free(ah);
	    dlclose(handle);
	    return NULL;
	}
    }
    ah->handle = handle;
    ah->ae_num = adn;
    ah->ae = g_new0 (aux_entry_t, adn);
    if (NULL == ah->ae) {
	if (NULL != ah_free) {
	    g_free(ah->file_name);
	    g_free(ah);
	}
	dlclose(handle);
	return NULL;
    }
    for (i = 0; i < adn; i++) {
	(ah->ae + i)->created = 0;
	memcpy(&((ah->ae + i)->dir), aux_dir + i, sizeof (aux_dir_t));
	(ah->ae + i)->if_version = ifversion;
    }

    ah->next = aux_handle;
    aux_handle = ah;

    return ah;
}

static int
auxname_strncmp (const IIIMP_card16 *s1,
		 const IIIMP_card16 *s2,
		 int len)
{
  len /= sizeof (IIIMP_card16);
  for (;len > 0; len--, s1++, s2++)
    {
      if (*s1 > *s2)
	return 1;
      if (*s1 < *s2)
	return -1;
      if (!*s1)
	return 0;
    }
  if (!*s1)
    return 0;
  return 2;
}

static
aux_im_data_t*
create_aux_im_data (aux_t *aux, const IIIMP_card16 *auxname)
{
  aux_entry_t *ae;
  aux_im_data_t *aux_im;
  GtkIMContextIIIM *context_iiim;
  IIIMCF_handle handle;
  IIIMF_status st;

  ae = aux_entry_get (auxname);
  if (!ae)
    return NULL;
  
  aux_im = g_new0 (aux_im_data_t, 1);

  /* The following members are only for fake. */
  context_iiim = (GtkIMContextIIIM *)aux->ic;
  handle = im_info_get_handle(context_iiim->iiim_info);
  if (handle == NULL)
    return NULL;
  st = iiimcf_get_im_id(handle, &aux_im->im_id);
  if (st != IIIMF_STATUS_SUCCESS)
    return NULL;
  st = iiimcf_get_ic_id(context_iiim->context, &aux_im->ic_id);
  if (st != IIIMF_STATUS_SUCCESS)
    return NULL;
  /* end */

  aux_im->ae = ae;
  aux_im->next = aux->im_list;
  aux->im_list = aux_im;
  aux->im = aux_im;
  if (!ae->created)
    {
      if (!ae->dir.method->create(aux))
	return NULL;
      ae->created = 1;
    }
  return aux_im;
}

static aux_t *
aux_get (GtkIMContextIIIM *context_iiim, IIIMCF_event ev,
	 const IIIMP_card16 *auxname)
{
  IIIMF_status st;
  aux_t *aux;
  aux_im_data_t *aux_im;
  aux_ic_info_t *aux_ic;
  IIIMCF_handle handle;

  if (auxname == NULL)
    {
      st = iiimcf_get_aux_event_value (ev, &auxname,
				       NULL, NULL, NULL, NULL, NULL);
      if (st != IIIMF_STATUS_SUCCESS)
	return NULL;
    }

  /*
   * create aux object if it is not created
   */
  aux = context_iiim->aux;
  if (!aux)
    {
      aux = g_new0 (aux_t, 1);
      aux->ic = (aux_ic_data_t*)context_iiim;
      aux->service = &g_aux_service;
      context_iiim->aux = aux;

      aux_ic = g_new0 (aux_ic_info_t, 1);
      handle = im_info_get_handle(context_iiim->iiim_info);
      if (handle == NULL)
	{
	  g_free(aux_ic);
	  return NULL;
	}
      st = iiimcf_get_im_id(handle, &aux_ic->im_id);
      if (st != IIIMF_STATUS_SUCCESS)
	{
	  g_free(aux_ic);
	  return NULL;
	}
      st = iiimcf_get_ic_id(context_iiim->context, &aux_ic->ic_id);
      if (st != IIIMF_STATUS_SUCCESS)
	{
	  g_free(aux_ic);
	  return NULL;
	}
      aux_ic->ic_data = (aux_ic_data_t*)context_iiim;
      aux_ic->next = aux_ic_info;
      aux_ic_info = aux_ic;
    }

  if (aux)
    {
      /*
       * search for aux_im_data corresponding to auxname.
       */
      for (aux_im = aux->im_list; aux_im; aux_im = aux_im->next)
	{
	  if (!auxname_strncmp (auxname,
				aux_im->ae->dir.name.ptr,
				aux_im->ae->dir.name.len))
	    {
	      aux->im = aux_im;
	      return aux;
	    }
	}
    }
  
  aux_im = create_aux_im_data (aux, auxname);
  if (!aux_im)
    return NULL;

  return aux;
}

static aux_entry_t *
aux_entry_get(
    const IIIMP_card16 *name
)
{
    aux_handle_t *ah;
    aux_handle_t *ah0;
    aux_entry_t *ae;
    int	i;

    if (!name) return NULL;

    for (ah = aux_handle; NULL != ah; ah = ah->next) {
	if ((ah->aux_name.len > 0)
	    && (!auxname_strncmp(name, ah->aux_name.ptr, ah->aux_name.len))) {
	    /* This handle is created from a configuration file.
	       Load SO now.  */
	    ah0 = aux_so_load(ah->file_name);
	    if (!ah0) continue;
	    ah = ah0;
	}
	for (ae = ah->ae, i = ah->ae_num; 0 < i; ae += 1, --i) {
	    if (!auxname_strncmp(name, ae->dir.name.ptr, ae->dir.name.len)) {
		return ae;
	    }
	}
    }

    return NULL;
}

static int
aux_string_length(
    const IIIMP_card16 *str
)
{
    int n;
    for (n = 0; *str; str++, n++);
    return n;
}

static AUXComposed*
create_composed_from_event(
    aux_t *aux,
    IIIMCF_event ev
)
{
    unsigned char *p;
    AUXComposed *pac;
    IIIMF_status st;
    const IIIMP_card16 *aux_name;
    IIIMP_card32 class_idx;
    int num_intvals;
    const IIIMP_card32 *pintvals;
    int num_strvals;
    const IIIMP_card16 **pstrs;
    aux_data_t *pad;

    int i, n;
    int aux_data_t_n;
    int aux_name_len, aux_name_n;
    int integer_list_n, string_list_n;
    int string_n;
    int *pstring_len;

#define ROUNDUP(n) ((n + sizeof(int) - 1) / sizeof(int) * sizeof(int))

    st = iiimcf_get_aux_event_value(ev, &aux_name, &class_idx,
				    &num_intvals, &pintvals,
				    &num_strvals, &pstrs);
    if (st != IIIMF_STATUS_SUCCESS) return NULL;

    /* first of all, caliculate size. */
    n = ROUNDUP(sizeof(AUXComposed));
    aux_data_t_n = n;
    n += sizeof(aux_data_t);
    aux_name_n = n = ROUNDUP(n);
    aux_name_len = aux_string_length(aux_name);
    n += (aux_name_len + 1) * sizeof(IIIMP_card16);
    if (num_intvals > 0) {
	integer_list_n = n = ROUNDUP(n);
	n += num_intvals * sizeof(int);
    }
    pstring_len = NULL;
    if (num_strvals > 0) {
	pstring_len = (int*) malloc(sizeof(int) * num_strvals);
	if (!pstring_len) return NULL;
	string_list_n = n = ROUNDUP(n);
	n += num_strvals * sizeof(aux_string_t);
	string_n = n = ROUNDUP(n);
	for (i = 0; i < num_strvals; i++) {
	    pstring_len[i] = aux_string_length(pstrs[i]);
	    n += (pstring_len[i] + 1) * sizeof(IIIMP_card16);
	}
    }
    p = (unsigned char*) malloc(n);
    if (!p) {
	if (pstring_len) free(pstring_len);
	return NULL;
    }
    memset(p, 0, n);

    pac = (AUXComposed*) p;
    pac->len = n;
    pac->ev = ev;
    pad = (aux_data_t*)(p + aux_data_t_n);
    pac->pad = pad;

    if (aux) {
	pac->aux = aux;
	pad->im = aux->im->im_id;
	pad->ic = aux->im->ic_id;
    }

    pad->aux_index = class_idx;
    pad->aux_name = p + aux_name_n;
    memcpy(pad->aux_name, aux_name, (aux_name_len + 1) * sizeof(IIIMP_card16));
    pad->aux_name_length = aux_name_len * sizeof(IIIMP_card16);

    pad->integer_count = num_intvals;
    if (num_intvals > 0) {
	pad->integer_list = (int*)(p + integer_list_n);
	for (i = 0; i < num_intvals; i++) {
	    pad->integer_list[i] = pintvals[i];
	}
    }
    pad->string_count = num_strvals;
    pad->string_ptr = p;
    if (num_strvals > 0) {
	aux_string_t *pas;

	pad->string_list = pas = (aux_string_t*)(p + string_list_n);
	p += string_n;
	for (i = 0; i < num_strvals; i++, pas++) {
	    pas->length = pstring_len[i] * sizeof(IIIMP_card16);
	    pas->ptr = p;
	    n = (pstring_len[i] + 1) * sizeof(IIIMP_card16);
	    memcpy(p, pstrs[i], n);
	    p += n;
	}
    }

    if (pstring_len) free(pstring_len);
    return pac;

#undef ROUNDUP
}

static AUXComposed*
create_composed_from_aux_data(const aux_data_t *pad1, int *size)
{
    unsigned char *p;
    AUXComposed *pac;
    aux_data_t *pad2;

    int i, n;
    int aux_data_t_n;
    int aux_name_n;
    int integer_list_n, string_list_n;
    int string_n;

#define ROUNDUP(n) ((n + sizeof(int) - 1) / sizeof(int) * sizeof(int))

    /* first of all, caliculate size. */
    n = ROUNDUP(sizeof(AUXComposed));
    aux_data_t_n = n;
    n += sizeof(aux_data_t);
    aux_name_n = n = ROUNDUP(n);
    n += pad1->aux_name_length + sizeof(IIIMP_card16);
    integer_list_n = n = ROUNDUP(n);
    n += pad1->integer_count * sizeof(int);
    string_list_n = n = ROUNDUP(n);
    n += pad1->string_count * sizeof(aux_string_t);
    string_n = n = ROUNDUP(n);
    for (i = 0; i < pad1->string_count; i++) {
	n += pad1->string_list[i].length + sizeof(IIIMP_card16);
    }

    p = (unsigned char*) malloc(n);
    if (!p) return NULL;
    memset(p, 0, n);

    if (size)
      *size = n;
    pac = (AUXComposed*) p;
    pac->len = n;
    pad2 = (aux_data_t*)(p + aux_data_t_n);
    pac->pad = pad2;

    *pad2 = *pad1;
    pad2->aux_name = p + aux_name_n;
    memcpy(pad2->aux_name, pad1->aux_name, pad1->aux_name_length);

    if (pad1->integer_count > 0) {
	pad2->integer_list = (int*)(p + integer_list_n);
	memcpy(pad2->integer_list, pad1->integer_list,
	       sizeof(int) * pad1->integer_count);
    } else {
	pad2->integer_list = NULL;
    }

    pad2->string_ptr = p;
    if (pad1->string_count > 0) {
	aux_string_t *pas1, *pas2;

	pas1= pad1->string_list;
	pad2->string_list = pas2 = (aux_string_t*)(p + string_list_n);
	p += string_n;
	for (i = 0; i < pad1->string_count; i++, pas1++, pas2++) {
	    pas2->length = pas1->length;
	    pas2->ptr = p;
	    memcpy(p, pas1->ptr, pas2->length);
	    p += pas2->length + sizeof(IIIMP_card16);
	}
    } else {
	pad2->string_list = NULL;
    }

    return pac;

#undef ROUNDUP
}

static void
delete_aux_ic(GtkIMContextIIIM *context_iiim)
{
    aux_t *aux;
    aux_im_data_t *aux_im;
    aux_ic_info_t *aux_ic;
    aux_ic_info_t *prev_aux_ic;

/* MADA
 *    aux = aux_get (context_iiim, ev, NULL);
 *    if (!aux)
 *	return;
 *
 *    for (aux_im = aux->im_list; aux_im; aux_im = aux_im->next) {
 *	if (aux_im->ae->if_version >= AUX_IF_VERSION_2
 *	    && aux_im->ae->dir.method->destroy_ic != NULL) {
 *	    aux->im = aux_im;
 *	    aux_im->ae->dir.method->destroy_ic(aux);
 *	}
 *    }
 * MADA */

    prev_aux_ic = NULL;
    for (aux_ic = aux_ic_info; aux_ic != NULL; aux_ic = aux_ic->next) {
	if (aux_ic->ic_data == (aux_ic_data_t *)context_iiim) {
	    if (prev_aux_ic == NULL)
		aux_ic_info = aux_ic->next;
	    else
		prev_aux_ic->next = aux_ic->next;
	    g_free(aux_ic);
	    break;
	}
    }
    prev_aux_ic = aux_ic;
}

/*
 * aux service function
 */

static void
service_aux_setvalue(
    aux_t * aux,
    const unsigned char * p,
    int len
)
{
    AUXComposed *pac = (AUXComposed*) p;

    if (pac->ev) {
      im_context_aux_set_values ((GtkIMContextIIIM*)aux->ic, pac->ev);
    } else if (pac->pad) {
	int i;
	aux_data_t *pad = pac->pad;
	IIIMF_status st;
	IIIMCF_event ev;
	IIIMP_card32 *pintvals;
	const IIIMP_card16 **pstrs;

	if (pad->integer_count > 0) {
	    pintvals = (IIIMP_card32*) malloc(sizeof(*pintvals) * pad->integer_count);
	    if (!pintvals) return;
	    for (i = 0; i < pad->integer_count; i++) {
		pintvals[i] = pad->integer_list[i];
	    }
	} else {
	    pintvals = NULL;
	}
	if (pad->string_count > 0) {
	    pstrs = (const IIIMP_card16**) malloc(sizeof(*pstrs) * pad->string_count);
	    if (!pstrs) {
		if (pintvals) free(pintvals);
		return;
	    }
	    for (i = 0; i < pad->string_count; i++) {
		pstrs[i] = (const IIIMP_card16*) pad->string_list[i].ptr;
	    }
	} else {
	    pstrs = NULL;
	}

	st = iiimcf_create_aux_setvalues_event((IIIMP_card16*) pad->aux_name,
					       pad->aux_index,
					       pad->integer_count,
					       pintvals,
					       pad->string_count,
					       pstrs,
					       &ev);
	if (st == IIIMF_STATUS_SUCCESS)
	  im_context_aux_set_values ((GtkIMContextIIIM*)aux->ic, ev);

	if (pintvals) free(pintvals);
	if (pstrs) free(pstrs);
    }

}

static void
service_aux_getvalue(
    aux_t * aux,
    const unsigned char * p,
    int len
)
{
    AUXComposed *pac = (AUXComposed*) p;

    if (pac->ev)
      im_context_aux_get_values ((GtkIMContextIIIM*)aux->ic, pac->ev);
    else if (pac->pad) {
	int i;
	aux_data_t *pad = pac->pad;
	IIIMF_status st;
	IIIMCF_event ev;
	IIIMP_card32 *pintvals;
	const IIIMP_card16 **pstrs;

	if (pad->integer_count > 0) {
	    pintvals = (IIIMP_card32*) malloc(sizeof(*pintvals) * pad->integer_count);
	    if (!pintvals) return;
	    for (i = 0; i < pad->integer_count; i++) {
		pintvals[i] = pad->integer_list[i];
	    }
	} else {
	    pintvals = NULL;
	}
	if (pad->string_count > 0) {
	    pstrs = (const IIIMP_card16**) malloc(sizeof(*pstrs) * pad->string_count);
	    if (!pstrs) {
		if (pintvals) free(pintvals);
		return;
	    }
	    for (i = 0; i < pad->string_count; i++) {
		pstrs[i] = (const IIIMP_card16*) pad->string_list[i].ptr;
	    }
	} else {
	    pstrs = NULL;
	}

	st = iiimcf_create_aux_getvalues_event((IIIMP_card16*) pad->aux_name,
					       pad->aux_index,
					       pad->integer_count,
					       pintvals,
					       pad->string_count,
					       pstrs,
					       &ev);
	if (st == IIIMF_STATUS_SUCCESS)
	  im_context_aux_get_values((GtkIMContextIIIM*)aux->ic, ev);

	if (pintvals) free(pintvals);
	if (pstrs) free(pstrs);
    }

}

static int
service_im_id(
    aux_t * aux
)
{
    return aux->im->im_id;
}


static int
service_ic_id(
    aux_t * aux
)
{
    return aux->im->ic_id;
}


static void
service_data_set(
    aux_t * aux,
    int im_id,
    void * data
)
{
    aux_im_data_t *	aux_im;

    for (aux_im = aux->im; NULL != aux_im; aux_im = aux_im->next) {
	if (im_id == aux_im->im_id) {
	    aux_im->data = data;
	}
    }

    return;
}


static void *
service_data_get(
    aux_t * aux,
    int im_id
)
{
    aux_im_data_t *	aux_im;

    for (aux_im = aux->im; NULL != aux_im; aux_im = aux_im->next) {
	if (im_id == aux_im->im_id) {
	    return aux_im->data;
	}
    }

    return NULL;
}


static Display *
service_display(
    aux_t * aux
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;
  GdkScreen *screen = im_info_get_screen (context->iiim_info);
  return GDK_SCREEN_XDISPLAY (screen);
}


static Window
service_window(
    aux_t * aux
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;
  if (context->client_window)
    return GDK_WINDOW_XWINDOW (context->client_window);
  else
    return None;
}

static XPoint *
service_point(
    aux_t * aux,
    XPoint * point
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;

  point->x = context->cursor.x;
  point->y = context->cursor.y;
  return point;
}

static XPoint *
service_point_caret(
    aux_t * aux,
    XPoint * point
)
{
  return service_point(aux, point);
}

/*
  convert a utf16 string into a string in locale's encoding
  Return value : The byte length of the converted string
*/
static size_t
service_utf16_mb (
    const char **inbuf,
    size_t *inbytesleft,
    char **outbuf,
    size_t *outbytesleft
)
{
  gchar *utf8_string = NULL;
  gsize bytes_read = 0, bytes_written = 0;
  gchar *result = NULL;

  utf8_string = convert_to_string ((const IIIMP_card16 *)(*inbuf));
  result = g_locale_from_utf8 (utf8_string, -1,
			       &bytes_read, &bytes_written, NULL);
  strcpy (*outbuf, result);
  outbuf += bytes_written;
  *inbytesleft -= bytes_read;
  *outbytesleft -= bytes_written;

  g_free (utf8_string);
  g_free (result);
  return (size_t)bytes_written;
}

/*
  convert a string in locale's encoding into a utf16 string.
  Return value : The byte length of the converted string
*/
static size_t
service_mb_utf16(
    const char **inbuf,
    size_t *inbytesleft,
    char **outbuf,
    size_t *outbytesleft
)
{
  gchar *utf8_string = NULL;
  gsize bytes_read = 0, bytes_written = 0;
  gsize r;
  gunichar2 *result = NULL;

  utf8_string = g_locale_to_utf8 ((const char*)(*inbuf), *inbytesleft,
				  &bytes_read, &bytes_written, NULL);
  result = convert_to_utf16 (utf8_string, -1, (glong*)&r);

  memcpy (*outbuf, result, r);
  outbuf += r;
  *inbytesleft -= bytes_read;
  *outbytesleft -= r;

  g_free (utf8_string);
  g_free (result);

  return (size_t)r;
}


static unsigned char *
service_compose(
    const aux_data_t *pad,
    int *size
)
{
    AUXComposed *pac;

    pac = create_composed_from_aux_data(pad, size);
    return (unsigned char*) pac;
}

static int
service_compose_size(
    aux_data_type_t type,
    const unsigned char *p
)
{
    /* now this function is dummy... */
    return 0;
}

static aux_data_t *
service_decompose(
    aux_data_type_t type,
    const unsigned char * p
)
{
    AUXComposed *pac = (AUXComposed*) p;

    if (pac->pad) {
	pac = create_composed_from_aux_data(pac->pad, NULL);
	if (!pac) return NULL;
	pac->pad->type = type;

	return pac->pad;
    }

    if (pac->ev) {
	pac = create_composed_from_event(pac->aux, pac->ev);
	if (!pac) return NULL;
	pac->pad->type = type;

	return pac->pad;
    }

    return NULL;
}

static void
service_decompose_free(
    aux_data_t *pad
)
{
    if (!pad) return;
    if (!pad->string_ptr) return;
    free(pad->string_ptr);
}

static void
service_register_X_filter(
    Display *	display,
    Window window,
    int	start_type,
    int	end_type,
    Bool (* filter)(Display *, Window, XEvent *, XPointer),
    XPointer client_data
)
{
    _XRegisterFilterByType(display, window, start_type, end_type,
			   filter, client_data);

    return;
}

static void
service_unregister_X_filter(
    Display *display,
    Window window,
    Bool (* filter)(Display *, Window, XEvent *, XPointer),
    XPointer client_data
)
{
    _XUnregisterFilter(display, window, filter, client_data);
}


static Bool
service_server(
    aux_t * aux
)
{
  return False;
}

static Window
service_client_window(
    aux_t * aux
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;

  if (context->client_window)
    return GDK_WINDOW_XWINDOW (context->client_window);
  else
    return None;
}

static Window
service_focus_window(
    aux_t * aux
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;

  if (context->client_window)
    return GDK_WINDOW_XWINDOW (context->client_window);
  else
    return None;
}

static int
service_screen_number(
    aux_t * aux
)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;
  GdkScreen *screen = im_info_get_screen (context->iiim_info);
  return gdk_screen_get_number (screen);
}

static int
service_point_screen(
    aux_t * aux,
    XPoint * point
)
{
  Display *display;
  Window window = None;
  Window root_window = None;
  Window child = None;
  int screen_number;
  int new_x;
  int new_y;

  display = service_display (aux);
  screen_number = service_screen_number (aux);
  root_window = RootWindow (display, screen_number);
  service_point (aux, point);
  window = service_window (aux);

  XTranslateCoordinates (display, window, root_window,
			 point->x, point->y,
			 &new_x, &new_y, &child);
  point->x = new_x;
  point->y = new_y;
  return screen_number;
}

static int
service_point_caret_screen(
    aux_t * aux,
    XPoint * point
)
{
  return service_point_screen (aux, point);
}

static Bool
service_get_conversion_mode(aux_t * aux)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;
  gboolean conversion_mode = FALSE;

  if (context)
    iiimcf_get_current_conversion_mode (context->context,
					&conversion_mode);
  return conversion_mode;
}

static void
service_set_conversion_mode(aux_t * aux, int conversion_mode)
{
  GtkIMContextIIIM *context = (GtkIMContextIIIM*)aux->ic;
  if (context)
    {
      if (conversion_mode == 1)
	im_context_change_conversion_mode (context, "on");
      else
	im_context_change_conversion_mode (context, "off");
    }
}

static aux_t *
service_aux_get_from_id(int im_id, int ic_id,
			IIIMP_card16 *aux_name, int aux_name_length)
{
  aux_ic_info_t *aux_ic;

  for (aux_ic = aux_ic_info; aux_ic != NULL; aux_ic = aux_ic->next) {
    if (aux_ic->im_id == im_id && aux_ic->ic_id == ic_id)
      break;
  }
  if (aux_ic == NULL)
    return NULL;

  return aux_get((GtkIMContextIIIM *)aux_ic->ic_data, NULL, aux_name);
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
