/*
 * Copyright (C) 2000 Red Hat Software
 * Copyright (C) 2003 Motonobu Ichimura
 * 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 <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include "iiimcf.h"
#include "gtkimcontextiiim.h"
#include "imswitcher.h"

#include <gtk/gtkinvisible.h>
#include <gdk/gdkproperty.h>
#include <gdk/gdkselection.h>

/* #define DEBUG */
#ifdef DEBUG
#define DEBUG_DO(x) (x)
#else
#define DEBUG_DO(x)
#endif

struct _SwitcherInfo
{
  GdkWindow *switcher;
  GdkAtom  selection_atom;
  GdkAtom  set_current_input_language_atom;
  GdkAtom  set_current_client_atom;
  GdkAtom  set_status_text_atom;
  GdkAtom  set_input_language_list_atom;
  GdkAtom  set_language_engine_list_atom;
  GdkAtom  set_conversion_mode_atom;

  /*
    When switcher is NULL while switcher_x_window isn't and works, it is due
    to gdk_selection_owner_get()'s bug. See bugzilla #126375.
  */
  Window switcher_x_window;
};

/* A listener window for input method switcher */
struct _SwitcherContext
{
  GtkWidget *invisible;

  gulong destroy_handler_id;
  gulong property_handler_id;
};


static GdkFilterReturn switcher_owner_filter (GdkXEvent *xev, GdkEvent *event,
					      gpointer data);
static gboolean filter_destroy_event (Display *d, Window w, XEvent *ev,
				      gpointer data);

gboolean
im_info_switcher_new (GtkIIIMInfo *info)
{
  GdkAtom selection = GDK_NONE;
  SwitcherInfo *sw_info;

  if (info == NULL)
    return FALSE;

  selection = gdk_atom_intern ("_IIIM_SWITCHER", FALSE);

  sw_info = im_info_get_switcher_info (info);
  if (sw_info == NULL)
    {
      sw_info = g_new0 (SwitcherInfo, 1);
      im_info_set_switcher_info (info, sw_info);
    }

  if (selection != GDK_NONE)
    sw_info->switcher = gdk_selection_owner_get (selection);

  sw_info->selection_atom = selection;

  if (sw_info->switcher)
    gdk_window_add_filter (sw_info->switcher,
			   switcher_owner_filter, info);
  else
    {
      /*
	this could be due to bugzilla 126375, hence try to
	do Xlib directly.
      */
      GdkScreen *screen;
      GdkDisplay *display;
      Atom x_atom;
      Window xwindow;

      screen = im_info_get_screen (info);
      if (screen == None)
	  return FALSE;

      display = gdk_screen_get_display (screen);
      x_atom = gdk_x11_atom_to_xatom_for_display (display, 
						  selection);
      xwindow = XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
				    x_atom);
      if (xwindow == None)
	{
	  DEBUG_DO (g_message ("Unable to find input method switcher"));
	  return FALSE;
	}
      sw_info->switcher_x_window = xwindow;

      _XRegisterFilterByType (GDK_DISPLAY_XDISPLAY (display), xwindow,
			      DestroyNotify, DestroyNotify,
			      filter_destroy_event, info);
      XSelectInput (GDK_DISPLAY_XDISPLAY (display), xwindow, StructureNotifyMask);
    }

  sw_info->set_current_input_language_atom
    = gdk_atom_intern ("_IIIM_SWITCHER_CURRENT_INPUT_LANGUAGE", FALSE);

  sw_info->set_current_client_atom =
    gdk_atom_intern ("_IIIM_SWITCHER_CURRENT_CLIENT", FALSE);

  sw_info->set_status_text_atom
    = gdk_atom_intern ("_IIIM_SWITCHER_STATUS_TEXT", FALSE);

  sw_info->set_input_language_list_atom =
    gdk_atom_intern ("_IIIM_SWITCHER_INPUT_LANGUAGE_LIST", FALSE);

  sw_info->set_language_engine_list_atom
    = gdk_atom_intern ("_IIIM_SWITCHER_LANGUAGE_ENGINE_LIST", FALSE);

  sw_info->set_conversion_mode_atom = 
    gdk_atom_intern ("_IIIM_SWITCHER_SET_CONVERSION_MODE", FALSE);

  return TRUE;
}

static void
destroy_switcher_window (GtkWidget *widget, GtkIMContextIIIM *context_iiim)
{
  SwitcherContext *w = context_iiim->switcher_context;
  if (!w)
    return;
  gtk_widget_destroy (w->invisible);
  g_free (w);
  context_iiim->switcher_context = NULL;
  return;
}

static void
property_notify_switcher_window (GtkWidget *widget, GdkEventProperty *ev,
				 GtkIMContextIIIM *context_iiim)
{
  GdkAtom  type;
  guchar   *data = NULL;
  gint     format;
  gint     length;
  SwitcherInfo *sw_info = im_info_get_switcher_info (context_iiim->iiim_info);

  if (context_iiim->context == NULL)
    return;

  if (ev->atom == sw_info->set_current_input_language_atom)
    {
      gdk_property_get (widget->window, ev->atom, ev->atom,
			0, INT_MAX, FALSE,
			&type, &format, &length, &data);
      im_context_initialize_with_input_language (context_iiim,
						 data);
      g_free (data);
    }
  if (ev->atom == sw_info->set_conversion_mode_atom)
    {
      gdk_property_get (widget->window, ev->atom, ev->atom,
			0, INT_MAX, FALSE,
			&type, &format, &length, &data);
      im_context_change_conversion_mode (context_iiim,
					 data);
      g_free (data);
    }
  return;
}

void
im_context_switcher_set_status_text (GtkIMContextIIIM *context_iiim, 
				     gchar *utf8)
{
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);

  if (sw_info == NULL)
    return;

  if (sw_info->switcher)
    {
      gdk_property_change (sw_info->switcher, 
			   sw_info->set_status_text_atom,
			   sw_info->set_status_text_atom,
			   8,
			   GDK_PROP_MODE_REPLACE,
			   (unsigned char*)utf8,
			   strlen (utf8));
    }
  else if (sw_info->switcher_x_window)
    {
      GdkScreen *screen = im_info_get_screen (info);
      GdkDisplay *display = gdk_screen_get_display (screen);
      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
				   sw_info->set_status_text_atom);

      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		       sw_info->switcher_x_window,
		       x_atom,
		       x_atom,
		       8,
		       PropModeReplace,
		       (guchar *)utf8,
		       strlen (utf8));
    }
}

void
im_context_switcher_set_conversion_mode (GtkIMContextIIIM *context_iiim)
{
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
  IIIMF_status st;
  gint conversion_mode = FALSE;

  if (sw_info == NULL)
    return;

  st = iiimcf_get_current_conversion_mode (context_iiim->context,
					   &conversion_mode);

  if (sw_info->switcher)
    {
      gdk_property_change (sw_info->switcher, 
			   sw_info->set_conversion_mode_atom,
			   sw_info->set_conversion_mode_atom,
			   32,
			   GDK_PROP_MODE_REPLACE,
			   (unsigned char*)&conversion_mode,
			   1);
    }
  else if (sw_info->switcher_x_window)
    {
      GdkScreen *screen = im_info_get_screen (info);
      GdkDisplay *display = gdk_screen_get_display (screen);
      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
			       sw_info->set_conversion_mode_atom);

      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		       sw_info->switcher_x_window,
		       x_atom,
		       x_atom,
		       32,
		       PropModeReplace,
		       (guchar *)&conversion_mode,
		       1);
    }
}

void
im_context_switcher_set_input_language (GtkIMContextIIIM *context_iiim,
					gchar *input_lang)
{
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);

  if (sw_info == NULL)
    return;

  if (input_lang == NULL)
    input_lang = context_iiim->current_language;
  if (sw_info->switcher)
    {
      gdk_property_change (sw_info->switcher, 
			   sw_info->set_current_input_language_atom,
			   sw_info->set_current_input_language_atom,
			   8,
			   GDK_PROP_MODE_REPLACE,
			   (unsigned char*)input_lang,
			   strlen (input_lang));
    }
  else if (sw_info->switcher_x_window)
    {
      GdkScreen *screen = im_info_get_screen (info);
      GdkDisplay *display = gdk_screen_get_display (screen);
      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
			       sw_info->set_current_input_language_atom);

      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		       sw_info->switcher_x_window,
		       x_atom,
		       x_atom,
		       8,
		       PropModeReplace,
		       (guchar *)input_lang,
		       strlen (input_lang));
    }
}

void
im_context_switcher_set_language_engine_list (GtkIMContextIIIM *context_iiim,
					      gchar *le_list)
{
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
  gsize len;

  if (sw_info == NULL)
    return;

  len = strlen (le_list);

  if (len == 0)
    return;

  if (sw_info->switcher)
    gdk_property_change (sw_info->switcher, 
			 sw_info->set_language_engine_list_atom,
			 sw_info->set_language_engine_list_atom,
			 8,
			 GDK_PROP_MODE_REPLACE,
			 (guchar*)le_list, len);
  else if (sw_info->switcher_x_window)
    {
      GdkScreen *screen = im_info_get_screen (info);
      GdkDisplay *display = gdk_screen_get_display (screen);
      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
						       sw_info->set_language_engine_list_atom);
      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		       sw_info->switcher_x_window,
		       x_atom,
		       x_atom,
		       8,
		       PropModeReplace,
		       (guchar *)le_list, len);
    }
}

void
im_context_switcher_set_language_list (GtkIMContextIIIM *context_iiim,
				       IIIMCF_language *lang_list,
				       int n_lang)
{
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
  gchar *languages;
  gchar *ptr;
  IIIMF_status st;
  char *langid;
  gint i;
  gsize len;
  const char *separator = ";";
  gsize separator_len;

  if (sw_info == NULL)
    return;

  if (lang_list == NULL || n_lang == 0)
    return;

  /* First part, getting length */
  st = iiimcf_get_language_id (lang_list[0],
			       (const char **) &langid);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  separator_len = strlen (separator);
  len = strlen (langid);
  for (i = 1; i < n_lang; i++)
    {
      st = iiimcf_get_language_id (lang_list[i],
				   (const char **) &langid);
      if (st != IIIMF_STATUS_SUCCESS)
	continue;
      len += strlen (langid);
    }
  len += separator_len * (i - 1);

  /* Second part, building string */
  languages = g_new (gchar, len + 1);

  st = iiimcf_get_language_id (lang_list[0],
			       (const char **) &langid);
  ptr = g_stpcpy (languages, langid);
  for (i = 1; i < n_lang; i++)
    {
      ptr = g_stpcpy (ptr, separator);
      st = iiimcf_get_language_id (lang_list[i],
				   (const char **) &langid);
      if (st != IIIMF_STATUS_SUCCESS)
	continue;
      ptr = g_stpcpy (ptr, langid);
    }

  if (sw_info->switcher)
    gdk_property_change (sw_info->switcher, 
			 sw_info->set_input_language_list_atom,
			 sw_info->set_input_language_list_atom,
			 8,
			 GDK_PROP_MODE_REPLACE,
			 (guchar*)languages, len);
  else if (sw_info->switcher_x_window)
    {
      GdkScreen *screen = im_info_get_screen (info);
      GdkDisplay *display = gdk_screen_get_display (screen);
      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
						       sw_info->set_input_language_list_atom);
      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		       sw_info->switcher_x_window,
		       x_atom,
		       x_atom,
		       8,
		       PropModeReplace,
		       (guchar *)languages, len);
    }
  g_free (languages);
}

void
im_context_switcher_new (GtkIMContextIIIM *context_iiim)
{
  SwitcherContext *w = g_new0 (SwitcherContext, 1);

  g_return_if_fail (context_iiim != NULL);
  g_return_if_fail (context_iiim->iiim_info != NULL);

  w->invisible = gtk_invisible_new ();
  gtk_widget_realize (w->invisible);

  gtk_widget_add_events (w->invisible,
			 GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);

  w->destroy_handler_id =
    g_signal_connect (G_OBJECT (w->invisible), "destroy",	
		      G_CALLBACK (destroy_switcher_window),
		      context_iiim);

  w->property_handler_id = 
    g_signal_connect (G_OBJECT (w->invisible), "property-notify-event",
		      G_CALLBACK (property_notify_switcher_window),
		      context_iiim);
  context_iiim->switcher_context = w;
}

gboolean
im_info_switcher_active (GtkIIIMInfo *info)
{
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
  return (sw_info && (sw_info->switcher || sw_info->switcher_x_window));
}

void
im_context_switcher_set_focus (GtkIMContextIIIM *context_iiim)
{
  SwitcherContext *w;
  GtkIIIMInfo *info = context_iiim->iiim_info;
  SwitcherInfo *sw_info;

  if (!im_info_switcher_active (info))
    {
      im_info_switcher_new (info);
      if (!im_info_switcher_active (info))
	return;
    }

  if (context_iiim->switcher_context == NULL)
    im_context_switcher_new (context_iiim);

  w = context_iiim->switcher_context;
  sw_info = im_info_get_switcher_info (info);
  if (w && w->invisible)
    gdk_selection_convert (w->invisible->window,
			   sw_info->selection_atom,
			   sw_info->set_current_client_atom,
			   gtk_get_current_event_time ());
}


/* input method switcher */
static
GdkFilterReturn
switcher_owner_filter (GdkXEvent *xev, GdkEvent *event, gpointer data)
{
  XEvent *xevent = (GdkXEvent *)xev;
  GtkIIIMInfo *info = data;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);

  if (sw_info != NULL)
    {
      switch (event->type)
	{
	case SelectionClear:
	  sw_info->switcher = NULL;
	  sw_info->switcher_x_window = None;
	  g_free (sw_info);
	  im_info_set_switcher_info (info, NULL);
	  break;
	default:
	  break;
	}
    }
  return GDK_FILTER_CONTINUE;
}

static gboolean
filter_destroy_event (Display *d, Window w, XEvent *ev, gpointer data)
{
  GtkIIIMInfo *info = data;
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);

  if (sw_info && sw_info->switcher_x_window == w)
    {
      sw_info->switcher = NULL;
      sw_info->switcher_x_window = None;
      g_free (sw_info);
      im_info_set_switcher_info (info, NULL);
      return TRUE;
    }
  return FALSE;
}

void
im_info_switcher_shutdown (GtkIIIMInfo *info)
{
  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
  if (sw_info)
    {
      if (sw_info->switcher)
	gdk_window_remove_filter (sw_info->switcher,
				  switcher_owner_filter, info);
      else if (sw_info->switcher_x_window)
	{
	  GdkScreen *screen = im_info_get_screen (info);
	  GdkDisplay *display = gdk_screen_get_display (screen);

	  _XUnregisterFilter (GDK_DISPLAY_XDISPLAY (display), 
			      sw_info->switcher_x_window,
			      filter_destroy_event, info);
	}
    }
}

void
im_context_switcher_finalize (GtkIMContextIIIM *context_iiim)
{
  SwitcherContext *w = context_iiim->switcher_context;

  if (w == NULL)
    return;
  g_signal_handler_disconnect (G_OBJECT (w->invisible), w->destroy_handler_id);
  g_signal_handler_disconnect (G_OBJECT (w->invisible), w->property_handler_id);
  gtk_widget_destroy (w->invisible);
  g_free (w);
  context_iiim->switcher_context = NULL;
  return;
}
