/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library 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, see <http://www.gnu.org/licenses/>.Free
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include "config.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib/gstdio.h>
#include <gmodule.h>
#include "gtkimmodule.h"
#include "gtkimmoduleprivate.h"
#include "gtkimcontextsimple.h"
#include "gtkmodulesprivate.h"
#include "gtksettings.h"
#include "gtkprivate.h"

#ifdef GDK_WINDOWING_X11
#include "x11/gdkx.h"
#endif

#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#include "gtkimcontextwaylandprivate.h"
#endif

#ifdef GDK_WINDOWING_BROADWAY
#include "broadway/gdkbroadway.h"
#include "gtkimcontextbroadway.h"
#endif

#ifdef GDK_WINDOWING_WIN32
#include "win32/gdkwin32.h"
#include "gtkimcontextime.h"
#endif

#ifdef GDK_WINDOWING_MACOS
#include "macos/gdkmacos.h"
#include "gtkimcontextquartz.h"
#endif

#ifdef GDK_WINDOWING_ANDROID
#include "gtkimcontextandroid.h"
#endif

#ifdef G_OS_WIN32
#include <windows.h>
#endif

#define SIMPLE_ID     "gtk-im-context-simple"
#define ALT_SIMPLE_ID "simple"
#define NONE_ID       "gtk-im-context-none"
#define ALT_NONE_ID   "none"

/**
 * _gtk_im_module_create:
 * @context_id: the context ID for the context type to create
 *
 * Create an IM context of a type specified by the string
 * ID @context_id.
 *
 * Returns: a newly created input context of or @context_id, or
 *   if that could not be created, a newly created `GtkIMContextSimple`
 */
GtkIMContext *
_gtk_im_module_create (const char *context_id)
{
  GIOExtensionPoint *ep;
  GIOExtension *ext;
  GType type;
  GtkIMContext *context = NULL;

  if (strcmp (context_id, NONE_ID) == 0)
    return NULL;

  ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
  ext = g_io_extension_point_get_extension_by_name (ep, context_id);
  if (ext)
    {
      type = g_io_extension_get_type (ext);
      context = g_object_new (type, NULL);
    }

  return context;
}

static gboolean
match_backend (GdkDisplay *display,
               const char *context_id)
{
  if (g_strcmp0 (context_id, "wayland") == 0)
    {
#ifdef GDK_WINDOWING_WAYLAND
      return GDK_IS_WAYLAND_DISPLAY (display) &&
             gdk_wayland_display_query_registry (display,
                                                 "zwp_text_input_manager_v3");
#else
      return FALSE;
#endif
    }

  if (g_strcmp0 (context_id, "broadway") == 0)
#ifdef GDK_WINDOWING_BROADWAY
    return GDK_IS_BROADWAY_DISPLAY (display);
#else
    return FALSE;
#endif

  if (g_strcmp0 (context_id, "ime") == 0)
#ifdef GDK_WINDOWING_WIN32
    return GDK_IS_WIN32_DISPLAY (display);
#else
    return FALSE;
#endif

  if (g_strcmp0 (context_id, "quartz") == 0)
#ifdef GDK_WINDOWING_MACOS
    return GDK_IS_MACOS_DISPLAY (display);
#else
    return FALSE;
#endif

  if (g_strcmp0 (context_id, "android") == 0)
#ifdef GDK_WINDOWING_ANDROID
    return GDK_IS_ANDROID_DISPLAY (display);
#else
    return FALSE;
#endif

  return TRUE;
}

static const char *
lookup_immodule (GdkDisplay  *display,
                 char       **immodules_list)
{
  guint i;

  for (i = 0; immodules_list[i]; i++)
    {
      if (!match_backend (display, immodules_list[i]))
        continue;

      if (g_strcmp0 (immodules_list[i], SIMPLE_ID) == 0 ||
          g_strcmp0 (immodules_list[i], ALT_SIMPLE_ID) == 0)
        return SIMPLE_ID;
      else if (g_strcmp0 (immodules_list[i], NONE_ID) == 0 ||
               g_strcmp0 (immodules_list[i], ALT_NONE_ID) == 0)
        return NONE_ID;
      else
        {
          GIOExtensionPoint *ep;
          GIOExtension *ext;

          ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
          ext = g_io_extension_point_get_extension_by_name (ep, immodules_list[i]);
          if (ext)
            return g_io_extension_get_name (ext);
        }
    }

  return NULL;
}

/**
 * _gtk_im_module_get_default_context_id:
 * @display: The display to look up the module for
 *
 * Return the context_id of the best IM context type
 * for the given window.
 *
 * Returns: the context ID (will never be %NULL)
 */
const char *
_gtk_im_module_get_default_context_id (GdkDisplay *display)
{
  const char *context_id = NULL;
  const char *envvar;
  GtkSettings *settings;
  GIOExtensionPoint *ep;
  GList *l;
  char *tmp;

  envvar = g_getenv ("GTK_IM_MODULE");
  if (envvar)
    {
      char **immodules;
      immodules = g_strsplit (envvar, ":", 0);
      context_id = lookup_immodule (display, immodules);
      g_strfreev (immodules);

      if (context_id)
        return context_id;
      else
        {
          static gboolean warned;

          if (!warned)
            {
              g_warning ("No IM module matching GTK_IM_MODULE=%s found", envvar);
              warned = TRUE;
            }
        }
    }

  /* Check if the certain immodule is set in XSETTINGS. */
  settings = gtk_settings_get_for_display (display);
  g_object_get (G_OBJECT (settings), "gtk-im-module", &tmp, NULL);
  if (tmp)
    {
      char **immodules;

      immodules = g_strsplit (tmp, ":", 0);
      context_id = lookup_immodule (display, immodules);
      g_strfreev (immodules);
      g_free (tmp);

      if (context_id)
        return context_id;
    }

  ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
  for (l = g_io_extension_point_get_extensions (ep); l; l = l->next)
    {
      GIOExtension *ext = l->data;

      context_id = g_io_extension_get_name (ext);
      if (match_backend (display, context_id))
        return context_id;
    }

  g_error ("GTK was run without any IM module being present. This must not happen.");

  return SIMPLE_ID;
}

void
gtk_im_module_ensure_extension_point (void)
{
  GIOExtensionPoint *ep;
  static gboolean registered = FALSE;

  if (registered)
    return;

  GTK_DEBUG (MODULES, "Registering extension point %s", GTK_IM_MODULE_EXTENSION_POINT_NAME);

  ep = g_io_extension_point_register (GTK_IM_MODULE_EXTENSION_POINT_NAME);
  g_io_extension_point_set_required_type (ep, GTK_TYPE_IM_CONTEXT);

  registered = TRUE;
}

void
gtk_im_modules_init (void)
{
  GIOModuleScope *scope;
  char **paths;
  int i;

  gtk_im_module_ensure_extension_point ();

  g_type_ensure (gtk_im_context_simple_get_type ());
#ifdef GDK_WINDOWING_WAYLAND
  g_type_ensure (gtk_im_context_wayland_get_type ());
#endif
#ifdef GDK_WINDOWING_BROADWAY
  g_type_ensure (gtk_im_context_broadway_get_type ());
#endif
#ifdef GDK_WINDOWING_WIN32
  g_type_ensure (gtk_im_context_ime_get_type ());
#endif
#ifdef GDK_WINDOWING_MACOS
  g_type_ensure (gtk_im_context_quartz_get_type ());
#endif
#ifdef GDK_WINDOWING_ANDROID
  g_type_ensure (gtk_im_context_android_get_type ());
#endif

  scope = g_io_module_scope_new (G_IO_MODULE_SCOPE_BLOCK_DUPLICATES);

  paths = _gtk_get_module_path ("immodules");
  for (i = 0; paths[i]; i++)
    {
      GTK_DEBUG (MODULES, "Scanning io modules in %s", paths[i]);
      g_io_modules_scan_all_in_directory_with_scope (paths[i], scope);
    }
  g_strfreev (paths);

  g_io_module_scope_free (scope);

  if (GTK_DEBUG_CHECK (MODULES))
    {
      GIOExtensionPoint *ep;
      GList *list, *l;

      ep = g_io_extension_point_lookup (GTK_IM_MODULE_EXTENSION_POINT_NAME);
      list = g_io_extension_point_get_extensions (ep);
      for (l = list; l; l = l->next)
        {
          GIOExtension *ext = l->data;
          g_print ("extension: %s: type %s\n",
                   g_io_extension_get_name (ext),
                   g_type_name (g_io_extension_get_type (ext)));
        }
    }
}
