/* xim.c - XIM handlers, for multiple locale input methods.
   Copyright (C) 1996-2000 Paul Sheer

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */

/* #define DEBUG */
/* #define DEBUG_ENTRY */

#include <config.h>
#include <stdio.h>
#include <my_string.h>
#include <stdlib.h>
#include <stdarg.h>

#include <X11/Intrinsic.h>
#include "lkeysym.h"

#include "stringtools.h"
#include "app_glob.c"

#include "coolwidget.h"
#include "coollocal.h"

#include "font.h"

#include "mad.h"

/* A lot of this stuff is hatched from rxvt-2.6.1: */

/*--------------------------------*-C-*---------------------------------*
 * File:	command.c
 *----------------------------------------------------------------------*
 * $Id: command.c,v 1.85.2.23 1999/08/12 16:32:39 mason Exp $
 *
 * All portions of code are copyright by their respective author/s.
 * Copyright (C) 1992      John Bovey, University of Kent at Canterbury <jdb@ukc.ac.uk>
 *				- original version
 * Copyright (C) 1994      Robert Nation <nation@rocket.sanders.lockheed.com>
 * 				- extensive modifications
 * Copyright (C) 1995      Garrett D'Amore <garrett@netcom.com>
 *				- vt100 printing
 * Copyright (C) 1995      Steven Hirsch <hirsch@emba.uvm.edu>
 *				- X11 mouse report mode and support for
 *				  DEC "private mode" save/restore functions.
 * Copyright (C) 1995      Jakub Jelinek <jj@gnu.ai.mit.edu>
 *				- key-related changes to handle Shift+function
 *				  keys properly.
 * Copyright (C) 1997      MJ Olesen <olesen@me.queensu.ca>
 *				- extensive modifications
 * Copyright (C) 1997      Raul Garcia Garcia <rgg@tid.es>
 *				- modification and cleanups for Solaris 2.x
 *				  and Linux 1.2.x
 * Copyright (C) 1997,1998 Oezguer Kesim <kesim@math.fu-berlin.de>
 * Copyright (C) 1998      Geoff Wing <gcw@pobox.com>
 * Copyright (C) 1998      Alfredo K. Kojima <kojima@windowmaker.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------*/

#ifndef HAVE_XPOINTER
typedef char   *XPointer;
#endif

#ifdef USE_XIM

static void xim_print_error (char *s,...)
{
    char k[1024];
    va_list ap;
    va_start (ap, s);
    vsprintf (k, s, ap);
    fprintf (stderr, "%s: %s\n", CAppName, k);
    va_end (ap);
}

int option_use_xim = 1;

static void IMInstantiateCallback (Display * display, XPointer client_data, XPointer call_data);

void init_xlocale (void)
{
#ifdef LC_CTYPE
    char *xim_locale = NULL;
    xim_locale = setlocale (LC_CTYPE, 0);
    CPushFont ("editor", 0);
    if (!xim_locale) {
	xim_print_error ("Setting locale failed.");
    } else if (!current_font->font_set) {
	xim_print_error ("Font set not loaded - cannot create input method.");
    } else {
	if (option_use_xim)
	    XRegisterIMInstantiateCallback (CDisplay, NULL, NULL, NULL, IMInstantiateCallback, NULL);
    }
    CPopFont ();
#else
    option_use_xim = 0;
#endif
}

void setPosition (CWidget * child, CWidget * w, XPoint * pos)
{
#ifdef dont_forget_this		/* FIXME */
    XWindowAttributes xwa;
    XGetWindowAttributes (CDisplay, TermWin.vt, &xwa);
    pos->x = Col2Pixel (screen.cur.col) + xwa.x;
    pos->y = Height2Pixel ((screen.cur.row + 1)) + xwa.y;
#else
    pos->x = w->width / 2;
    pos->y = w->height / 2;
#endif
}

static void IMSendSpot (Window window)
{
    XPoint spot;
    XVaNestedList preedit_attr;
    XIMStyle input_style;
    CWidget *child, *w;
    if (!window)
	return;
    if (!(child = CWidgetOfWindow (window)))
	return;
    if (child->mainid) {
	w = CWidgetOfWindow (child->mainid);
    } else {
	w = child;
    }
    if (!w->input_context)
	return;
    XGetICValues (w->input_context, XNInputStyle, &input_style, NULL);
    if (!(input_style & XIMPreeditPosition))
	return;
    setPosition (child, w, &spot);
    preedit_attr = XVaCreateNestedList (0, XNSpotLocation, &spot, NULL);
    XSetICValues (w->input_context, XNPreeditAttributes, preedit_attr, NULL);
    XFree (preedit_attr);
}

KeySym key_sym_xlat (XEvent * ev, char *x_lat)
{
    static KeySym r;
    static int len = 0;
    static KeySym keysym = 0;
    static XComposeStatus compose =
    {NULL, 0};
    static unsigned char kbuf[512] = "";
    static int valid_keysym = 1;
    Status status_return = 0;
    if (x_lat)
	*x_lat = '\0';
    if (ev->type != KeyPress && ev->type != KeyRelease)
	return 0;
/* we mustn't call this twice with the same event */
    if (ev->xkey.x_root == 31234)
	goto no_repeat_call;
    ev->xkey.x_root = 31234;
    keysym = 0;
    len = 0;
    if (ev->type == KeyRelease) {
	len = XLookupString (&ev->xkey, (char *) kbuf, sizeof (kbuf), &keysym, 0);
	if (!len && (keysym >= 0x0100) && (keysym < 0x0800)) {
	    len = 1;
	    kbuf[0] = (keysym & 0xFF);
	}
    } else if (CIC) {
	CWidget *w;
	IMSendSpot (ev->xkey.window);
	w = CWidgetOfWindow (ev->xkey.window);
	if (!w)
	    return 0;
	if (w->mainid)
	    w = CWidgetOfWindow (w->mainid);
	if (w->input_context != CIC) {
	    printf ("w->input_context != CIC  -->  Huh?\n");	/* FIXME:this seems to actually happen */
	    return 0;
	}
	len = XmbLookupString (CIC, &ev->xkey, (char *) kbuf,
			       sizeof (kbuf), &keysym, &status_return);
	valid_keysym = ((status_return == XLookupKeySym)
			|| (status_return == XLookupBoth));
    } else {
	keysym = 0;
	len = XLookupString (&ev->xkey, (char *) kbuf, sizeof (kbuf), &keysym, &compose);
	if (!len && (keysym >= 0x0100) && (keysym < 0x0800)) {
	    len = 1;
	    kbuf[0] = (keysym & 0xFF);
	}
    }
  no_repeat_call:
    if (x_lat) {
	if (len > 0) {
	    if (len > 7)
		len = 7;
	    memcpy (x_lat, kbuf, len);
	    x_lat[len] = '\0';
	}
    }
    r = ((keysym >= 0x0100) && (keysym < 0x0800)) ? (valid_keysym ? kbuf[0] : 0) : (valid_keysym ? keysym : 0);
    return r;
}


void setSize (CWidget * w, XRectangle * size)
{
#ifdef RXVT_SOURCE		/* FIXME */
    size->x = TermWin_internalBorder;
    size->y = TermWin_internalBorder;
    size->width = Width2Pixel (TermWin.ncol);
    size->height = Height2Pixel (TermWin.nrow);
#else
    size->x = 0;
    size->y = 0;
    size->width = w->width;
    size->height = w->height;
#endif
}

void setColor (CWidget * w, unsigned long *fg, unsigned long *bg)
{
#ifdef dont_forget_this		/* FIXME */
    *fg = PixColors[Color_fg];
    *bg = PixColors[Color_bg];
#else
    *fg = COLOR_BLACK;
    *bg = COLOR_WHITE;
#endif
}

void setPreeditArea (CWidget * w, XRectangle * preedit_rect, XRectangle * status_rect, XRectangle * needed_rect)
{
#ifdef RXVT_SOURCE		/* FIXME */
    preedit_rect->x = needed_rect->width
	+ (scrollbar_visible () && !(Options & Opt_scrollBar_right)
	   ? (SB_WIDTH + sb_shadow * 2) : 0);
    preedit_rect->y = Height2Pixel (TermWin.nrow - 1)
	+ ((menuBar.state == 1) ? menuBar_TotalHeight () : 0);

    preedit_rect->width = Width2Pixel (TermWin.ncol + 1) - needed_rect->width
	+ (!(Options & Opt_scrollBar_right)
	   ? (SB_WIDTH + sb_shadow * 2) : 0);
    preedit_rect->height = Height2Pixel (1);

    status_rect->x = (scrollbar_visible () && !(Options & Opt_scrollBar_right))
	? (SB_WIDTH + sb_shadow * 2) : 0;
    status_rect->y = Height2Pixel (TermWin.nrow - 1)
	+ ((menuBar.state == 1) ? menuBar_TotalHeight () : 0);

    status_rect->width = needed_rect->width ? needed_rect->width
	: Width2Pixel (TermWin.ncol + 1);
    status_rect->height = Height2Pixel (1);
#else
    preedit_rect->x = 0;
    preedit_rect->y = 0;
    preedit_rect->width = w->width;
    preedit_rect->height = w->height - 20;
    status_rect->x = 0;
    status_rect->y = w->height - 20;
    status_rect->width = w->width;
    status_rect->height = 20;
#endif
}

static long destroy_input_context (CWidget * w)
{
    w->input_context = 0;
    return 0;
}

void IMDestroyCallback (XIM xim, XPointer client_data, XPointer call_data)
{
    XRegisterIMInstantiateCallback (CDisplay, NULL, NULL, NULL,
				    IMInstantiateCallback, NULL);
    for_all_widgets ((void *) destroy_input_context, 0, 0);
    CIC = 0;
}

/* returns zero on error */
XIMStyle get_input_style (void)
{
    int found = 0;
    char tmp[1024] = "", *s = 0;
    char *end = 0, *next_s = 0;
    XIMStyle input_style = 0;
    XIMStyles *xim_styles = NULL;
    if (!CIM) {
	if (option_use_xim)
	    xim_print_error ("Trying to get input_style, but Input Method is null.");
	return 0;
    }
    if (XGetIMValues (CIM, XNQueryInputStyle, &xim_styles, NULL)
	|| !xim_styles) {
	xim_print_error ("input method doesn't support any style");
	return 0;
    }
#ifdef RXVT_SOURCE
    strncpy (tmp, (rs[Rs_preeditType] ? rs[Rs_preeditType]
		   : "OverTheSpot,OffTheSpot,Root"),
	     sizeof (tmp) - 1);
#else
    strncpy (tmp, "OverTheSpot,OffTheSpot,Root",
	     sizeof (tmp) - 1);
#endif
    for (found = 0, s = tmp; *s && !found; s = next_s + 1) {
	unsigned short i;

	for (; *s && isspace (*s); s++);
	if (!*s)
	    break;
	for (end = s; (*end && (*end != ',')); end++);
	for (next_s = end--; ((end >= s) && isspace (*end)); end--);
	*(end + 1) = '\0';

	if (!strcmp (s, "OverTheSpot"))
	    input_style = (XIMPreeditPosition | XIMStatusNothing);
	else if (!strcmp (s, "OffTheSpot"))
	    input_style = (XIMPreeditArea | XIMStatusArea);
	else if (!strcmp (s, "Root"))
	    input_style = (XIMPreeditNothing | XIMStatusNothing);
	for (i = 0; i < xim_styles->count_styles; i++)
	    if (input_style == xim_styles->supported_styles[i]) {
		found = 1;
		break;
	    }
    }
    XFree (xim_styles);
    if (found == 0) {
	xim_print_error ("input method doesn't support my preedit type");
	return 0;
    }
    if ((input_style != (XIMPreeditNothing | XIMStatusNothing))
	&& (input_style != (XIMPreeditArea | XIMStatusArea))
	&& (input_style != (XIMPreeditPosition | XIMStatusNothing))) {
	xim_print_error ("This program does not support the preedit type");
	return 0;
    }
    return input_style;
}

long create_input_context (CWidget * w, XIMStyle input_style)
{
    XVaNestedList preedit_attr = 0;
    XVaNestedList status_attr = 0;
    XPoint spot;
    XRectangle rect, status_rect, needed_rect;
    XIMCallback ximcallback;
    unsigned long fg, bg;
    if (w->kind != C_WINDOW_WIDGET)
	return 0;
    if (w->mainid)
	return 0;
    if (w->input_context)
	return 0;
    if (!CIM)
	return 1;
    if (!input_style)
	return 1;
    ximcallback.callback = IMDestroyCallback;
    ximcallback.client_data = NULL;
    if (input_style & XIMPreeditPosition) {
	setSize (w, &rect);
	setPosition (0, w, &spot);
	setColor (w, &fg, &bg);
	preedit_attr = XVaCreateNestedList (0, XNArea, &rect,
					    XNSpotLocation, &spot,
					    XNForeground, fg,
					    XNBackground, bg,
				       XNFontSet, current_font->font_set,
					    NULL);
    } else if (input_style & XIMPreeditArea) {
	setColor (w, &fg, &bg);
	/* 
	 * The necessary width of preedit area is unknown
	 * until create input context.
	 */
	needed_rect.width = 0;
	setPreeditArea (w, &rect, &status_rect, &needed_rect);
	preedit_attr = XVaCreateNestedList (0, XNArea, &rect,
					    XNForeground, fg,
					    XNBackground, bg,
				       XNFontSet, current_font->font_set,
					    NULL);
	status_attr = XVaCreateNestedList (0, XNArea, &status_rect,
					   XNForeground, fg,
					   XNBackground, bg,
				       XNFontSet, current_font->font_set,
					   NULL);
    }
    w->input_context = XCreateIC (CIM, XNInputStyle, input_style,
				  XNClientWindow, w->winid,
				  XNFocusWindow, w->winid,
				  XNDestroyCallback, &ximcallback,
			       preedit_attr ? XNPreeditAttributes : NULL,
				  preedit_attr,
				  status_attr ? XNStatusAttributes : NULL,
				  status_attr,
				  NULL);
    if (preedit_attr)
	XFree (preedit_attr);
    if (status_attr)
	XFree (status_attr);
    if (!w->input_context) {
	xim_print_error ("Failed to create input context for widget %s", w->ident);
	return 1;
    }
    return 0;
}

long set_status_position (CWidget * w)
{
    XIMStyle input_style;
    XRectangle preedit_rect, status_rect, *needed_rect = 0;
    XVaNestedList preedit_attr, status_attr;

    if (!w->input_context)
	return 0;

    XGetICValues (w->input_context, XNInputStyle, &input_style, NULL);

    if (input_style & XIMPreeditArea) {
	/* Getting the necessary width of preedit area */
	status_attr = XVaCreateNestedList (0, XNAreaNeeded, &needed_rect, NULL);
	XGetICValues (w->input_context, XNStatusAttributes, status_attr, NULL);
	XFree (status_attr);

	setPreeditArea (w, &preedit_rect, &status_rect, needed_rect);

	preedit_attr = XVaCreateNestedList (0, XNArea, &preedit_rect, NULL);
	status_attr = XVaCreateNestedList (0, XNArea, &status_rect, NULL);

	XSetICValues (w->input_context,
		      XNPreeditAttributes, preedit_attr,
		      XNStatusAttributes, status_attr, NULL);

	XFree (preedit_attr);
	XFree (status_attr);
    }
    return 0;
}


static void IMInstantiateCallback (Display * display, XPointer client_data, XPointer call_data)
{
    char *p;
    XIMStyle input_style = 0;
    XIMCallback ximcallback;

    if (CIC)
	return;

    ximcallback.callback = IMDestroyCallback;
    ximcallback.client_data = NULL;

#ifdef RXVT_SOURCE
    if (rs[Rs_inputMethod] && *rs[Rs_inputMethod]) {
	strncpy (tmp, option_imput_method, sizeof (tmp) - 1);
	for (s = tmp; *s; s = next_s + 1) {
	    for (; *s && isspace (*s); s++);
	    if (!*s)
		break;
	    for (end = s; (*end && (*end != ',')); end++);
	    for (next_s = end--; ((end >= s) && isspace (*end)); end--);
	    *(end + 1) = '\0';

	    if (*s) {
		strcpy (buf, "@im=");
		strncat (buf, s, sizeof (buf) - 4 - 1);
		if ((p = XSetLocaleModifiers (buf)) != NULL && *p
		 && (CIM = XOpenIM (CDisplay, NULL, NULL, NULL)) != NULL)
		    break;
	    }
	    if (!*next_s)
		break;
	}
    }
#endif

    /* try with XMODIFIERS env. var. */
    if (CIM == NULL && (p = XSetLocaleModifiers ("")) != NULL && *p)
	CIM = XOpenIM (CDisplay, NULL, NULL, NULL);

    /* try with XMODIFIERS env. var. */
    if (CIM == NULL && (p = XSetLocaleModifiers ("@im=control")) != NULL && *p)
	CIM = XOpenIM (CDisplay, NULL, NULL, NULL);

    /* try with no modifiers base */
    if (CIM == NULL && (p = XSetLocaleModifiers ("@im=none")) != NULL && *p)
	CIM = XOpenIM (CDisplay, NULL, NULL, NULL);

    if (!CIM)
	return;

/* got the Input Method, now set up all dialogs */

    XSetIMValues (CIM, XNDestroyCallback, &ximcallback, NULL);

    if (!(input_style = get_input_style ())) {
	XCloseIM (CIM);
	CIM = 0;
    }
    CPushFont ("editor", 0);
    if (for_all_widgets ((void *) create_input_context, (void *) input_style, 0)) {
	input_style = 0;
	XCloseIM (CIM);
	CIM = 0;
    }
    CPopFont ();
    if (input_style & XIMPreeditArea)
	for_all_widgets ((void *) set_status_position, 0, 0);
}

#else

int option_use_xim = 0;

KeySym key_sym_xlat (XEvent * ev, char *x_lat)
{
    static int len = 0;
    static KeySym keysym = 0;
    static XComposeStatus compose =
    {NULL, 0};
    static unsigned char kbuf[512] = "";
    static int valid_keysym = 1;
    Status status_return = 0;
    if (x_lat)
	*x_lat = '\0';
    if (ev->type != KeyPress && ev->type != KeyRelease)
	return 0;
/* we mustn't call this twice with the same event */
    if (ev->xkey.x_root == 31234)
	goto no_repeat_call;
    ev->xkey.x_root = 31234;
    keysym = 0;
    len = 0;
    if (ev->type == KeyRelease) {
	len = XLookupString (&ev->xkey, (char *) kbuf, sizeof (kbuf), &keysym, 0);
	if (!len && (keysym >= 0x0100) && (keysym < 0x0800)) {
	    len = 1;
	    kbuf[0] = (keysym & 0xFF);
	}
    } else {
	keysym = 0;
	len = XLookupString (&ev->xkey, (char *) kbuf, sizeof (kbuf), &keysym, &compose);
	if (!len && (keysym >= 0x0100) && (keysym < 0x0800)) {
	    len = 1;
	    kbuf[0] = (keysym & 0xFF);
	}
    }
  no_repeat_call:
    if (x_lat) {
	if (len > 0) {
	    if (len > 7)
		len = 7;
	    memcpy (x_lat, kbuf, len);
	    x_lat[len] = '\0';
	}
    }
    return valid_keysym ? keysym : 0;
}

#endif

