/**
 *
 * $Id: XmIm.c,v 1.13 1999/10/17 07:42:45 danny Exp $
 *
 * Copyright (C) 1995 Free Software Foundation, Inc.
 *
 * This file is part of the GNU LessTif Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **/

static const char rcsid[] = "$Id: XmIm.c,v 1.13 1999/10/17 07:42:45 danny Exp $";

#include <LTconfig.h>
#include <XmI/XmI.h>

#include <Xm/XmP.h>
#include <Xm/VendorSEP.h>
#include <X11/Intrinsic.h>
#include <X11/Xos.h>
#include <stdarg.h>

#include <XmI/DebugUtil.h>

#include "LTmisc.h"

/* For the prototype police */
extern XIC XmImGetXIC(Widget w, XmIMInputPolicy input_policy, ArgList args,
		      Cardinal num_args);
extern XIC XmImSetXIC(Widget w, XIC xic);
extern void XmImCloseXIM(Widget w);
extern void XmImFreeXIC(Widget w, XIC xic);

/*
 * This file contains I18N (Internationalisation) Input Method handling.
 *
 * Comments are deduced from the Motif 2.0 manual pages.
 *
 * The current implementation is *Quick and Dirty* and follows the motif
 * specs only slightly. Not to say not at all.
 * Working on it - Danny 4 Feb 96.
 */

/* Forward */
extern Widget _LtFindVendorExt(Widget);

/*
 * This is a private data structure. A pointer to it is in the
 * VendorShell Extension object.
 */
typedef struct XmICStuff
{
    XIC xic;
    XIM xim;
    Widget ve, text;		/* Don't know if we need this */
    struct XmICStuff *next;
}
XmICStuff;

/*
 * Find the private data structure and cast it correctly.
 * This is close to the implementation of XmImGetXIM.
 */
static struct XmICStuff *
_XmFindICStuff(Widget w)
{
    XmVendorShellExtObject v = (XmVendorShellExtObject)_LtFindVendorExt(w);
    XmICStuff *stuff;

    if (v == NULL)
    {
	return NULL;
    }

    stuff = (XmICStuff *)v->vendor.im_info;

    while (stuff)
    {
	if (stuff->text == w)
	{
	    return stuff;
	}
	stuff = stuff->next;
    }

    return NULL;
}

/*
 * XmImGetXIC creates and registers an XIC with the specified arguments for the
 * widget. If XmINHERIT_POLICY is specified for input_policy, then a new XIC
 * will be created only if required by the arguments or by the VendorShell
 * input policy. Any existing XIC registered with the widget is unregistered.
 *
 * If input_policy is IP_PER_WIDGET, then a new XIC for this widget is created.
 * If it is IP_PER_SHELL, then a new XIC for the shell is created if needed.
 *
 * (Danny's comment :)
 * Reusing an XIC is - in my opinion - not possible between widgets, because
 * the XNClientWindow of an XIC can not be changed after creation.
 *
 * Reusing the XIM is possible though.
 */
XIC
XmImGetXIC(Widget w, XmIMInputPolicy input_policy, ArgList args, Cardinal num_args)
{
    XmVendorShellExtObject v = (XmVendorShellExtObject)_LtFindVendorExt(w);

    XIM xim;			/* the input method */
    XIMStyles *xim_styles;
    XIMStyle input_style = 0;
    XmICStuff *stuff = NULL, *s;
    char *p, *buf, *b, *k;
    static char *styles[] =
    {
	"OverTheSpot",
	"OffTheSpot",
	"Root",
	"Root",
	NULL};
    static XIMStyle style_bits[] =
    {
    /* OverTheSpot */ XIMPreeditPosition | XIMStatusArea,
    /* OffTheSpot */ XIMPreeditArea | XIMStatusArea,
    /* Root */ XIMPreeditNothing | XIMStatusNothing,
    /* Not really root */ XIMPreeditNone | XIMStatusNone
    };

    Boolean found;
    int i, j;

    if (v == 0)
    {
	DEBUGOUT(XdbDebug(__FILE__, w,
			  "XmImRegister: no vendor shell extension found\n"));
	return 0;
    }

    DEBUGOUT(XdbDebug(__FILE__, w, "XmImRegister: InputMethodString '%s'\n",
		      v->vendor.input_method_string
		      ? v->vendor.input_method_string
		      : "(null)"));

    if ((stuff = _XmFindICStuff(w)) == NULL)
    {
	stuff = (XmICStuff *)XtMalloc(sizeof(XmICStuff));
	stuff->ve = (Widget)v;
	stuff->text = w;
	stuff->xim = NULL;
	stuff->xic = NULL;

	stuff->next = (XmICStuff *)v->vendor.im_info;
	v->vendor.im_info = (XtPointer)stuff;
    }

    p = v->vendor.input_method_string;
    xim = NULL;

    /*
     * Try to reuse an XIM
     */
    for (s = (XmICStuff *)v->vendor.im_info; s != NULL; s = s->next)
    {
	if (s->ve == (Widget)v && s != stuff)
	{
	    xim = s->xim;

	    DEBUGOUT(XdbDebug(__FILE__, w, "XmImGetXIC: reuse XIM\n"));
	    break;
	}
    }

    if (!xim)
    {
	if (p)
	{
	    /* Loop over the contents of input_method_string */
	    b = p;
	    while (*b)
	    {
		k = strchr(b, ',');
		if (k)
		{
		    *k = '\0';
		}
		buf = XtMalloc(10 + strlen(b));
		strcpy(buf, "@im=");
		strcat(buf, b);

		if ((p = XSetLocaleModifiers(buf)) != NULL)
		{
		    xim = XOpenIM(XtDisplay(w), NULL, NULL, NULL);

		    if (xim) {
			DEBUGOUT(XdbDebug(__FILE__, w,
			    "XOpenIM succeeded with XSetLocaleModifiers(%s)\n",
			    buf));
		    }
		}
		XtFree(buf);
		if (k)
		{
		    *k = ',';
		    b = k + 1;
		}
		else
		{
		    break;
		}

		if (xim)
		{
		    break;
		}
	    }
	}
	else
	{
	    if ((p = XSetLocaleModifiers("@im=none")) != NULL) {
		xim = XOpenIM(XtDisplay(w), NULL, NULL, NULL);
		if (xim) {
		    DEBUGOUT(XdbDebug(__FILE__, w,
			"XOpenIM succeeded with XSetLocaleModifiers(%s)\n",
			"@im=none"));
		}
	    }
	}

	if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL)
	{
	    xim = XOpenIM(XtDisplay(w), NULL, NULL, NULL);
	    if (xim) {
		DEBUGOUT(XdbDebug(__FILE__, w,
			"XOpenIM succeeded with XSetLocaleModifiers('')\n"));
	    }
	}
    }

    if (!xim)
    {
	DEBUGOUT(XdbDebug(__FILE__, w, "Failed to open input method\n"));
	return 0;
    }

    if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || xim_styles == NULL)
    {
	DEBUGOUT(XdbDebug(__FILE__, w,
			  "Input method doesn't support any style\n"));
	XCloseIM(xim);
	return 0;
    }

    if (XdbInDebug(__FILE__, w))
    {
	DEBUGOUT(XdbDebug(__FILE__, w,
			  "XmImRegister - Supported styles :\n"));
	for (j = 0; j < xim_styles->count_styles; j++)
	{
	    DEBUGOUT(XdbDebug0(__FILE__, w, " (%d) %X %s", j,
			       xim_styles->supported_styles[j],
			   (xim_styles->supported_styles[j] & XIMStatusNothing)
			       ? "XIMStatusNothing" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			       (xim_styles->supported_styles[j] & XIMStatusArea)
			       ? "XIMStatusArea" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			 (xim_styles->supported_styles[j] & XIMStatusCallbacks)
			       ? "XIMStatusCallbacks" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			       (xim_styles->supported_styles[j] & XIMStatusNone)
			       ? "XIMStatusNone" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			  (xim_styles->supported_styles[j] & XIMPreeditNothing)
			       ? "XIMPreeditNothing" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			 (xim_styles->supported_styles[j] & XIMPreeditPosition)
			       ? "XIMPreeditPosition" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			(xim_styles->supported_styles[j] & XIMPreeditCallbacks)
			       ? "XIMPreeditCallbacks" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			     (xim_styles->supported_styles[j] & XIMPreeditNone)
			       ? "XIMPreeditNone" : ""));
	    DEBUGOUT(XdbDebug0(__FILE__, w, " %s",
			     (xim_styles->supported_styles[j] & XIMPreeditArea)
			       ? "XIMPreeditArea" : ""));

	    DEBUGOUT(XdbDebug0(__FILE__, w, "\n"));
	}
    }

    /* Check whether we have input styles that match the resource */
    /*
     * FIX ME
     *
     * This should really use a "find the best option" algorithm, instead 
     * of just looking for the first match.
     */
    for (i = 0, found = False; styles[i] && !found; i++)
    {
	DEBUGOUT(XdbDebug(__FILE__, w,
			  "Trying %s (0x%X)\n", styles[i], style_bits[i]));

	if (strstr(v->vendor.preedit_type_string, styles[i]) == 0)
	{
	    continue;
	}
	for (j = 0; j < xim_styles->count_styles; j++)
	{
	    if (style_bits[i] == xim_styles->supported_styles[j])
	    {
		found = True;
		input_style = style_bits[i];
		break;
	    }
	}
    }
    XFree(xim_styles);

    if (!found)
    {
	DEBUGOUT(XdbDebug(__FILE__, w,
	     "XmImRegister: input method doesn't support our preedit type\n"));
	XCloseIM(xim);
	return 0;
    }

    stuff->xim = xim;

    if (!XtIsRealized(XtParent(v)))
    {
	DEBUGOUT(XdbDebug(__FILE__, w, "XmImRegister: not realized yet\n"));
    }
    else
    {
	stuff->xic = XCreateIC(xim,
			       XNInputStyle, input_style,
			       XNClientWindow, XtWindow(w),
			       XNFocusWindow, XtWindow(w),
			       NULL);

	if (stuff->xic)
	{
	    DEBUGOUT(XdbDebug(__FILE__, w, "We have an IC\n"));
	}
	else
	{
	    DEBUGOUT(XdbDebug(__FILE__, w, "IC Creation failed\n"));
	}
    }

    return stuff->xic;
}

/*
 * XmImRegister registers a widget with its input manager. This adds the specified
 * widget to a list of widgets that are supported by the input manager for an input
 * method. If an input method has not been opened by a previous call to XmImRegister,
 * the first time this routine is called it opens an input method using the XmNinputMethod
 * resource for the VendorShell. If the XmNinputMethod is NULL, an input method is
 * opened using the current locale.
 *
 * If an input method cannot be opened in the current locale, XLookupString provides
 * input processing. The application is responsible for unregistering a widget by
 * calling XmImUnregister.
 */
void
XmImRegister(Widget w, unsigned int reserved)
{
    XIC xic;

    xic = XmImGetXIC(w, IP_PER_SHELL, NULL, 0);
}

/*
 * XmImUnregister removes the specified widget from the list of widgets
 * registered for input by the input manager.
 */
void
XmImUnregister(Widget w)
{
    DEBUGOUT(XdbDebug(__FILE__, w,
		      "XmImUnregister is not implemented yet.\n"));
}

/*
 * XmImSetFocusValues notifies the input manager that the specified widget
 * has received input focus. This function also updates the attributes of
 * the input context associated with the widget.
 * If the previous parameters for the widget's IC do not allow the previously
 * registered IC to be reused, that IC will be unregistered, and a new one will
 * be created and registered with the widget.
 */
void
XmImSetFocusValues(Widget w, ArgList args, Cardinal num_args)
{
    XmICStuff *stuff = _XmFindICStuff(w);


    if (stuff == NULL)
    {
	DEBUGOUT(XdbDebug(__FILE__, w, "XmImSetFocusValues (no IC found)\n"));
	return;
    }

    DEBUGOUT(XdbDebug(__FILE__, w, "XmImSetFocusValues\n"));

    if (stuff->xic) {
	XSetICFocus(stuff->xic);
    } else {
	XmImRegister(w, 0);

	if (stuff->xic)
	    XSetICFocus(stuff->xic);
    }
}

/* Varargs interface to the above. */
void
XmImVaSetFocusValues(Widget w,...)
{
    va_list ap;
    Arg *a;
    int n, i;

    va_start(ap, w);
    n = 0;

    while (va_arg(ap, char *) != NULL)
    {
	(void)va_arg(ap, XtPointer);
	n++;
    }

    va_end(ap);

    a = (Arg *)XtCalloc(n, sizeof(Arg));
    va_start(ap, w);
    for (i = 0; i < n; i++)
    {
	a[i].name = va_arg(ap, char *);
	a[i].value = va_arg(ap, XtArgVal);
    }

    va_end(ap);

    XmImSetFocusValues(w, a, n);
    XtFree((XtPointer)a);
}

/*
 * XmImSetValues updates attributes of the input context associated with the
 * specified widget.
 * If the previous parameters for the widget's IC do not allow the previously
 * registered IC to be reused, that IC will be unregistered, and a new one will
 * be created and registered with the widget.
 */
void
XmImSetValues(Widget w, ArgList args, Cardinal num_args)
{
    DEBUGOUT(XdbDebug(__FILE__, w, "XmImSetValues is not implemented yet\n"));

#if 0
    int i;
    XmICStuff *stuff = _XmFindICStuff(w);
    Arg *al = (Arg *)XtCalloc(sizeof(Arg), num_args);
    int n = 0;

    for (i = 0; i < num_args; i++)
    {
	if (strcmp(args[i].name, XmNforeground) == 0)
	{
	    XtSetArg(al[n], XNForeground, args[i].value);
	    n++;
	}
	else if (strcmp(args[i].name, XmNbackground) == 0)
	{
	    XtSetArg(al[n], XNBackground, args[i].value);
	    n++;
	}
	else if (strcmp(args[i].name, XmNbackgroundPixmap) == 0)
	{
	    XtSetArg(al[n], XNBackgroundPixmap, args[i].value);
	    n++;
	}
	else if (strcmp(args[i].name, XmNcursor) == 0)
	{
	    XtSetArg(al[n], XNCursor, args[i].value);
	    n++;
	}
	else if (strcmp(args[i].name, XmNfontList) == 0)
	{
	    XtSetArg(al[n], XNFontSet, args[i].value);
	    n++;
	}
	else if (strcmp(args[i].name, XmNfocusWindow) == 0)
	{
	    XtSetArg(al[n], XNFocusWindow, args[i].value);
	    n++;
	}
	/* FIX ME - XNLineSpacing, XNColormap, XNStdColormap,
	   XNAreaNeeded, XNSpotLocation, XNArea, ... */
    }

    /*
     * FIX ME -
     *      should check whether the vendor shell is realized ??
     *      how do you cope with XNVaNestedList ??
     */
    (void)XSetICValues(stuff->xic, XNVaNestedList, al, NULL);
#endif
}

/* Varargs interface to the above. */
void
XmImVaSetValues(Widget w,...)
{
    va_list ap;
    Arg *a;
    int n, i;

    va_start(ap, w);
    n = 0;
    while (va_arg(ap, char *) != NULL)
    {
	(void)va_arg(ap, XtPointer);
	n++;
    }
    va_end(ap);

    a = (Arg *)XtCalloc(n, sizeof(Arg));
    va_start(ap, w);
    for (i = 0; i < n; i++)
    {
	a[i].name = va_arg(ap, char *);
	a[i].value = va_arg(ap, XtArgVal);
    }
    va_end(ap);

    XmImSetValues(w, a, n);
    XtFree((XtPointer)a);
}

/*
 * XmImUnsetFocus unsets a specified widget's focus, then notifies the input
 * manager that the specified widget has lost its input focus.
 */
void
XmImUnsetFocus(Widget w)
{
    XmICStuff *stuff;

    if (w == NULL) {
#ifdef	LESSTIF_VERBOSE
	_XmWarning(w, "XmImUnsetFocus(NULL)\n");
#endif
	return;
    }

    stuff = _XmFindICStuff(w);
    if (stuff == NULL) {
#ifdef	LESSTIF_VERBOSE
	_XmWarning(w, "XmImUnsetFocus: _XmFindICStuff returned NULL\n");
#endif
	return;
    }

    DEBUGOUT(XdbDebug(__FILE__, w, "XmImUnsetFocus\n"));

    if (stuff->text == w && stuff->xic)
    {
	XUnsetICFocus(stuff->xic);
    }
}

/*
 * XmImGetXIM retrieves the XIM data structure representing the input method
 * that the input manager has opened for the specified widget.
 * If an input method has not been opened by a previous call to XmImRegister,
 * the first time this routine is called it opens an input method using the
 * XmNinputMethod resource for the VendorShell.
 */
XIM
XmImGetXIM(Widget w)
{
    XmICStuff *stuff = _XmFindICStuff(w);

    if (stuff)
    {
	return stuff->xim;
    }

    /* FIX ME (see comment above) */
    return NULL;
}

/*
 * XmImMbLookupString returns a string composed in the locale associated with
 * the widget's input method and a KeySym that is currently mapped to the
 * keycode in the KeyPressedEvent.
 */
int
XmImMbLookupString(Widget w, XKeyPressedEvent *evp,
		   char *buf, int nbytes, KeySym *keysym, int *status)
{
    XmICStuff *stuff = _XmFindICStuff(w);
    XIC xic = NULL;

    if (stuff)
    {
	xic = stuff->xic;
    }

    if (xic)
    {
	return XmbLookupString(xic, evp, buf, nbytes, keysym, status);
    }

    if (status)
    {
	*status = XLookupBoth;
    }

    return XLookupString(evp, buf, nbytes, keysym, NULL);
}

/*
 * XmImSetXIC registers the X Input Context (XIC) with the specified widget.
 * Any existing XIC registered for the widget is unregistered. The new XIC
 * registered for the widget is returned.
 */
XIC
XmImSetXIC(Widget w, XIC xic)
{
    XmVendorShellExtObject v = (XmVendorShellExtObject)_LtFindVendorExt(w);
    XmICStuff *stuff;

    if (v == NULL || xic == NULL)
    {
	return NULL;		/* FIX ME - is this right ? */
    }

    XmImUnregister(w);

    if (_XmFindICStuff(w) == NULL)
    {
	stuff = (XmICStuff *)XtMalloc(sizeof(XmICStuff));
	stuff->ve = (Widget)v;
	stuff->text = w;
	stuff->xim = XIMOfIC(xic);
	stuff->xic = xic;

	stuff->next = (XmICStuff *)v->vendor.im_info;
	v->vendor.im_info = (XtPointer)stuff;
    }

    return xic;
}

/*
 * XmImCloseXIM closes all input contexts associated with the input method of
 * the specified widget. The widget is used to identify the Display that specifies
 * the Input Method opened for the widget. Upon closure, all widgets registered
 * with the input contexts are unregistered. Also the Input Method specified by
 * Display is closed.
 */

/* FIX ME
 * Currently we only tackle stuff under our own shell widget.
 * This is not enough, as the above text (which reflects the Motif 2.0 spec) indicates
 * that we should deal with the "Display".
 * If more than one window is open on the same Display, then we need to find them.
 * This requires the XmDisplay to keep track of top level widgets, or of (Vendor)Shells.
 * FIX ME
 */
void
XmImCloseXIM(Widget w)
{
    XmICStuff *stuff;
    XIM xim;

    if (!w)
    {
	return;
    }
    if ((stuff = _XmFindICStuff(w)) == NULL)
    {
	return;
    }

    xim = stuff->xim;
    XmImUnregister(w);
    if (xim)
    {
	XCloseIM(xim);
    }
}

/*
 * XmImFreeXIC unregisters all widgets associated with the specified XIC. The
 * specified widget must be specified with the specified XIC.
 */
void
XmImFreeXIC(Widget w, XIC xic)
{
}

void
_XmImChangeManaged(Widget w)
{
}

void
_XmImRealize(Widget w)
{
}

void
_XmImResize(Widget w)
{
}

void
_XmImRedisplay(Widget w)
{
}
