/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  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.
 */
#include "swindow/sx11/SXInputMethod.h"
#include "swindow/sx11/SX11Color.h"
#include "stoolkit/STypes.h"
#include "stoolkit/SEncoder.h"
#include "stoolkit/SStringVector.h"

#ifdef HAVE_LOCALE
#include <X11/Xlocale.h>
#endif

#ifdef NEED_SET_IM_VALUES_PROTO
extern "C"
{
extern char *
XSetIMValues(
#if NeedVarargsPrototypes
    XIM /* im */, ...
#endif
);
}
#endif

void destroyIMCB (XIM xim, XPointer p0, XPointer p1);
static XFontSet fontSet = 0;

/**
 * This is out X11 input method. Currently only kinput2 is here, so
 * we have one class only.
 */
SXInputMethod::SXInputMethod (SX11Impl* _impl, long _id, long _eventMask)
{
  inputStyle = "none";
  impl = _impl;
  id = _id;
  name = "";
  kproperty = None;
  conversionOwner = None;
  xim = 0;
  xic = 0;
  statusAreaOK = false;
  clientWindow = _id;
  isAscii = true;
  eventMask = _eventMask;
}

/**
 * return true if only ascii input is accepted.
 */
bool
SXInputMethod::isAsciiInput ()
{
  return isAscii;
}

bool
SXInputMethod::hasStatusArea ()
{
  return statusAreaOK;
}
/**
 * return true if kinput is active and up
 */
bool
SXInputMethod::isKInput ()
{
  if (isAsciiInput() || name != ATOM_KINPUT2) return false;
  /* oops its has gone */
  if (conversionOwner == None)
  {
    impl->removeXEventHandler (clientWindow, PropertyNotify);
    impl->removeXEventHandler (clientWindow, ClientMessage);
    kproperty = None;
    isAscii = true;
    return false;
  }

  Window owner = getOwner (toAtom (name));
  /* olle its has gone */
  if (owner == None || conversionOwner != owner)
  {
    conversionOwner = None;
    impl->removeXEventHandler (clientWindow, PropertyNotify);
    impl->removeXEventHandler (clientWindow, ClientMessage);
    kproperty = None;
    isAscii = true;
    return false;
  }
  return true;
}

SXInputMethod::~SXInputMethod()
{
  impl->removeXEventHandler (clientWindow, PropertyNotify);
  impl->removeXEventHandler (clientWindow, ClientMessage);
}

/**
 * Start kinput2 conversion.
 * @param _name is the conversion name - only "_JAPANESE_CONVERSION" is OK now.
 * @param properties contains the properties for input.
 */
bool
SXInputMethod::start (const SString& _name, const SProperties& properties)
{
  SString oldName = name;
  if (name == _name) return true;
  /* need to stop current kinput2 */
  if (name == ATOM_KINPUT2) stop ();

  name = "x-ascii";
  isAscii = true;
  statusAreaOK = false;

  if (_name.size() > 0 && (_name[0] =='x' || _name[0] == 'X'))
  {
    return (createIC(_name, properties));
  }
  statusAreaOK = true;

  /* switch off current one */
  if (oldName.size() > 1 && oldName[0] == 'x' && oldName[1] == '-'
   && oldName != "x-ascii")
  {
    createIC ("x-ascii", properties);
  }

  if (_name != ATOM_KINPUT2) return false;
  conversionOwner = getOwner (toAtom (_name));
  if (conversionOwner == None)
  { 
    return false;
  }

  if (properties.get ("InputStyle")==0)
  {
    fprintf (stderr, "InputStyle is not present in properties.\n");
    return false;
  }
  inputStyle = properties["InputStyle"];

  clientWindow = id;
  /**
   * Check if we can use this '_JAPANESE_CONVERSION' thingy.
   */
  Atom            expectedAttribueType = toAtom ("_CONVERSION_ATTRIBUTE_TYPE");

  Atom            attributeType; 
  int             format;
  unsigned long   nitems;
  unsigned long   bytesafter;
  unsigned long   *data;
  unsigned char   *c_data=0;

  XGetWindowProperty (
    impl->display, conversionOwner, toAtom ("_CONVERSION_PROFILE"),
    0L, 100L, False, toAtom ("_CONVERSION_ATTRIBUTE_TYPE"),
    &attributeType, &format, &nitems, &bytesafter, &c_data);

  data = (unsigned long*) c_data;

  if (data == 0) return false;

  if (format != 32 || attributeType != expectedAttribueType)
  {
    XFree ((char*) data);
    return false;
  }

  bool isOK = false;

  for (unsigned i=0; i<nitems; i++)
  {
    int code = CODE_OF_ATTR (data[i]);
    int len = LENGTH_OF_ATTR (data[i]);
    if (len+i > nitems) break;

    switch (code)
    {
    case CONVPROF_PROTOCOL_VERSION:
      if (data[i+1] == toAtom ("PROTOCOL-2.0"))
      {
        isOK = true;
      }
    case CONVPROF_SUPPORTED_STYLES:
      break;
    }
    i+=len;
  }

  XFree (data);
  if (!isOK)
  {
    conversionOwner = None;
    return false;
  }

  /* set attributes and clientWindow */
  if (!setKinputAttributes (properties))
  {
    conversionOwner = None;
    return false;
  }
  impl->setXEventHandler (clientWindow, ClientMessage, this);

  name = _name;
  bool ret = false;
  ret = sendEvent ("CONVERSION_REQUEST", conversionOwner,
      toAtom (name), clientWindow, toAtom ("COMPOUND_TEXT"), toAtom (name), 
      toAtom ("CONVERSION_ATTRIBUTE"));
  isAscii = false;
  return ret;
}

/**
 * Stop the conversion.
 */
void
SXInputMethod::stop ()
{
  if (name == "") return;
  if  (name != ATOM_KINPUT2)
  {
    createIC ("x-ascii", SProperties());
    return;
  }
  name = "x-ascii";
  isAscii = true;
  if (conversionOwner == None)
  {
    return;
  }
  Window owner = getOwner (toAtom (ATOM_KINPUT2));
  if (owner == None || conversionOwner != owner)
  {
    conversionOwner = None;
    impl->removeXEventHandler (clientWindow, PropertyNotify);
    impl->removeXEventHandler (clientWindow, ClientMessage);
    kproperty = None;
    return;
  }
  Atom an = toAtom(ATOM_KINPUT2);
  sendEvent ("CONVERSION_END_REQUEST", conversionOwner, an, clientWindow);
  conversionOwner = None;

  impl->removeXEventHandler (clientWindow, PropertyNotify);
  impl->removeXEventHandler (clientWindow, ClientMessage);
  kproperty = None;
}

/**
 * Check if it is running and return name or null
 */
const SString&
SXInputMethod::getName()
{
  return name;
}

void
SXInputMethod::setProperties (const SProperties& props)
{
  if (name == "") return;
  if (inputStyle == "root") return;
  if (!isKInput())
  {
    XIC ic = getIC();
    if (ic ==0) return;
    if (props.get("InputSpot"))
    {
      if (inputStyle == "preedit-over-status-over")
      {
        SString spotLocation = props["InputSpot"];
        spotLocation.append ((char)0);
        int x, y;
        sscanf (spotLocation.array(), "%d,%d", &x, &y);
        XPoint spot; spot.x = x; spot.y = y;

        XVaNestedList attributes = XVaCreateNestedList (0, 
          XNSpotLocation, &spot, (char *) 0x0);

        if (attributes) 
        {
          XSetICValues(xic, XNPreeditAttributes, attributes, (char *) 0x0);
          XFree (attributes);
        }
      }
      else if (inputStyle == "preedit-over-status-under")
      {
        SString spotLocation = props["InputSpot"];
        spotLocation.append ((char)0);
        int x, y;
        sscanf (spotLocation.array(), "%d,%d", &x, &y);
        XPoint spot; spot.x = x; spot.y = y;

        SString sl = props["InputStatusLocation"];
        sl.append((char)0);
        int statusX, statusY;
        sscanf(sl.array(), "%d,%d", &statusX, &statusY);

        SString ss = props["InputStatusSize"];
        ss.append((char)0);
        int statusWidth, statusHeight;
        sscanf(ss.array(), "%d,%d", &statusWidth, &statusHeight);
        XRectangle statusArea;
        statusArea.width  = statusWidth;
        statusArea.height = statusHeight;
        statusArea.x = statusX;
        statusArea.y = statusY;

        XVaNestedList ca = XVaCreateNestedList (0, 
          XNSpotLocation, &spot, (char *) 0x0);

        XVaNestedList sa = XVaCreateNestedList (0, 
          XNArea, &statusArea, (char *) 0x0);

        if (ca && sa) 
        {
          XSetICValues(xic, 
              XNPreeditAttributes, ca, 
              XNStatusAttributes, sa, (char *) 0x0);
          XFree (ca);
          XFree (sa);
        }
      }
    }
    return;
  }
  if (conversionOwner == None) return;
  if (inputStyle != "preedit-over-status-under" 
   && inputStyle != "preedit-over-status-over")
  {
    return;
  }

  Window owner = getOwner (toAtom (name));
  if (owner == None || conversionOwner != owner 
   || !setKinputAttributes (props))
  {
    name = "";
    conversionOwner = None;
    impl->removeXEventHandler (clientWindow, PropertyNotify);
    impl->removeXEventHandler (clientWindow, ClientMessage);
    kproperty = None;
    return;
  }

  long attr = CONV_ATTR(CONVATTR_INDIRECT, 1);
  sendEvent ("CONVERSION_ATTRIBUTE_NOTIFY", conversionOwner,
     toAtom (name), clientWindow, attr, toAtom ("CONVERSION_ATTRIBUTE")); 
  return;
}

/**
 * Handle event. return false if no more call is needed.
 * @return false if we can not process this.
 */
bool
SXInputMethod::handleEvent (const XEvent& event)
{
  if (conversionOwner == None) return false;

  /* Check if we got the right event. */
  switch (event.xany.type)
  {
  case PropertyNotify:
    //fprintf (stderr,"SXInputMethod: Property Notify.\n");
    return handlePropertyNotify (event);

  /* response to conversion start */
  case ClientMessage:
    if (event.xclient.format != 32) return false;

    if (event.xclient.message_type == toAtom ("CONVERSION_END")
        && event.xclient.data.l[0] == (long) toAtom (name))
    {
      //fprintf (stderr, "SXInputMethod: conversion ended.\n");
      name = "";
      conversionOwner = None;
      impl->removeXEventHandler (clientWindow, PropertyNotify);
      impl->removeXEventHandler (clientWindow, ClientMessage);
      kproperty = None;
      return true;
    }
    /* we take notify only */
    if (event.xclient.message_type != toAtom ("CONVERSION_NOTIFY")
      || (Atom) event.xclient.data.l[0] != toAtom (name))
    {
      name = "";
      conversionOwner = None;
      impl->removeXEventHandler (clientWindow, PropertyNotify);
      impl->removeXEventHandler (clientWindow, ClientMessage);
      kproperty = None;
      fprintf (stderr, "SXInputMethod: conversion request failed.\n");
      return false;
    }
    if (event.xclient.data.l[2] == None)
    {
      name = "";
      conversionOwner = None;
      impl->removeXEventHandler (clientWindow, PropertyNotify);
      impl->removeXEventHandler (clientWindow, ClientMessage);
      kproperty = None;
      fprintf (stderr,"SXInputMethod: request can not be performed.\n");
      return true;
    }
    /* Why do I need this ? */
    kproperty = (Atom) event.xclient.data.l[2];
    //fprintf (stderr,"SXInputMethod: request OK.\n");
    impl->setXEventHandler (clientWindow, PropertyNotify, this);
    return true;
  }
  /* remove this handler */
  return false;
}

/**
 * Handle property notify event. return false if no more call is needed.
 * @return true if we want to propagate this.
 */
bool
SXInputMethod::handlePropertyNotify (const XEvent& event)
{
  if (name.size() == 0) return true;
  if (conversionOwner == None) return true;
  if (kproperty == None)
  {
    return false;
  }
  if ((long) event.xproperty.window != clientWindow 
   || event.xproperty.atom != kproperty
   || event.xproperty.state != PropertyNewValue)
  {
    return false;
  }
  Atom            proptype;
  int             propformat;
  unsigned long   propsize;
  unsigned long   rest;
  unsigned char*  propvalue=0;
 
  /* Kinput just hang some property on our window, or client window */
  XGetWindowProperty (impl->display, (Window)clientWindow, kproperty, 
    0L, 100000L, True, AnyPropertyType, 
    &proptype, &propformat, &propsize, &rest, &propvalue);

  /**
   * this happens if accumulated property change
   * appended, and we already have read the data.
   */
  if (proptype == None)
  {
    if (propvalue) XFree (propvalue);
    return true;
  }
  // Should not happen
  if (proptype != toAtom ("COMPOUND_TEXT"))
  {
    if (propvalue) XFree (propvalue);
    fprintf (stderr, "Expected COMPUND_TEXT\n");
    return true;
  }
  // Should not happen
  if (propformat != 8)
  {
    if (propvalue) XFree (propvalue);
    return true;
  }
  SString in ((char*)propvalue, propsize);
  if (propvalue) XFree (propvalue);

  SEncoder utf8Encoder("utf-8-s");
  SEncoder ctextJP ("iso-2022-x11");
  if (!ctextJP.isOK())
  {
    fprintf (stderr, "could not load encoder for iso-2022-jp\n");
    return true;
  }
  impl->sendString (id, utf8Encoder.encode (ctextJP.decode (in)));
  return true;
}

/**
 * Changes the attributes on this window by changing the window properties
 * to whatever we have in properties.
 */
bool
SXInputMethod::setKinputAttributes (const SProperties& properties)
{
  /* pad with 4 zero bytes on top for buggy kinput on alpha */
  SString xprop;
  int style = CONVARG_OVERTHESPOT;
  if (inputStyle=="preedit-root-status-root")
  {
    style = CONVARG_ROOTWINDOW;
  }
  else if (inputStyle =="preedit-under-status-under")
  {
    style = CONVARG_OFFTHESPOT;
  }
  else if (inputStyle =="preedit-over-status-over")
  {
    style = CONVARG_OVERTHESPOT;
  }
  else if (inputStyle == "preedit-over-status-under")
  {
    style = CONVARG_OVERTHESPOT;
  }
  else
  {
    fprintf (stderr, "InputStyle is bad - '%*.*s.'\n", SSARGS(inputStyle));
    return false;
  }
  xprop.append (SString ((long) CONV_ATTR(CONVATTR_INPUT_STYLE, 1)));
  xprop.append (SString((long) style));
  clientWindow = id;

  // CONVARG_NONE - not supported
  // CONVARG_SELECT_FOCUS_WINDOW - key events that happen
  //     during coversion and dont have SendEvent flags need to be
  //     ignored.
  // CONVARG_CREATE_INPUTONLY - create an invisible window in front of
  //     the real one - does not work with all window managers-
  //     click to type. - CURRENTLY YUDIT CAN DO ONLY THIS.
  xprop.append (SString ((long) CONV_ATTR(CONVATTR_EVENT_CAPTURE_METHOD, 1)));
  xprop.append (SString((long) CONVARG_SELECT_FOCUS_WINDOW));
  if (inputStyle == "preedit-over-status-over"
    && properties.get ("InputSpot") && properties.get ("InputStyle"))
  { 
      SString spotLocation = properties["InputSpot"];
      spotLocation.append((char)0);
      int x, y;
      sscanf(spotLocation.array(), "%d,%d", &x, &y);
      xprop.append(SString((long)CONV_ATTR(CONVATTR_SPOT_LOCATION, 1)));
      xprop.append(SString((long)(x << 16) | (y & 0xffff)));
      if (properties.get ("InputClientColor"))
      {
          SString col = properties["InputClientColor"];
          col.append ((char)0);
          unsigned long bg, fg;
          sscanf (col.array(), "%lu,%lu", &bg, &fg);
          SX11Color xbg = SX11Color(impl, (SS_WORD32)bg);
          SX11Color xfg = SX11Color(impl, (SS_WORD32)fg);
          xprop.append (SString ((long) CONV_ATTR(CONVATTR_COLOR, 2)));
          xprop.append (SString ((long)(xfg.getPixelValue())));
          xprop.append (SString ((long)(xbg.getPixelValue())));
      }
  }
  if (inputStyle == "preedit-under-status-under") 
  {
    xprop.append (SString ((long) CONV_ATTR(CONVATTR_FOCUS_WINDOW, 1)));
    xprop.append (SString ((long)id));
    if (properties.get ("InputWindow"))
    {
      SString pw = properties["InputWindow"];
      pw.append ((char)0);
      unsigned long w;
      sscanf (pw.array(), "%lu", &w);
      clientWindow = w;
    }
    if (properties.get ("InputStatusLocation") 
      && properties.get ("InputStatusSize"))
    {
      SString sl = properties["InputStatusLocation"];
      sl.append((char)0);
      int x, y;
      sscanf(sl.array(), "%d,%d", &x, &y);

      SString ss = properties["InputStatusSize"];
      ss.append((char)0);
      int width, height;
      sscanf(ss.array(), "%d,%d", &width, &height);

      xprop.append(SString((long)CONV_ATTR(CONVATTR_STATUS_AREA, 2)));
      xprop.append(SString((long)(x << 16) | (y & 0xffff)));
      xprop.append(SString((long)(width << 16) | (height & 0xffff)));
    }
    if (properties.get ("InputClientLocation") 
      && properties.get ("InputClientSize"))
    {
      SString sl = properties["InputClientLocation"];
      sl.append((char)0);
      int x, y;
      sscanf(sl.array(), "%d,%d", &x, &y);

      SString ss = properties["InputClientSize"];
      ss.append((char)0);
      int width, height;
      sscanf(ss.array(), "%d,%d", &width, &height);

      xprop.append(SString((long)CONV_ATTR(CONVATTR_CLIENT_AREA, 2)));
      xprop.append(SString((long)(x << 16) | (y & 0xffff)));
      xprop.append(SString((long)(width << 16) | (height & 0xffff)));
    }
    if (properties.get ("InputClientColor"))
    {
      SString col = properties["InputClientColor"];
      col.append ((char)0);
      unsigned long bg, fg;
      sscanf (col.array(), "%lu,%lu", &bg, &fg);
      SX11Color xbg = SX11Color(impl, (SS_WORD32)bg);
      SX11Color xfg = SX11Color(impl, (SS_WORD32)fg);
      xprop.append (SString ((long) CONV_ATTR(CONVATTR_COLOR, 2)));
      xprop.append (SString ((long)(xfg.getPixelValue())));
      xprop.append (SString ((long)(xbg.getPixelValue())));
    }
  }
  /* status off the spot */
  if (inputStyle == "preedit-over-status-under") 
  {
    xprop.append (SString ((long) CONV_ATTR(CONVATTR_FOCUS_WINDOW, 1)));
    xprop.append (SString ((long)id));
    if (properties.get ("InputStatusLocation") 
      && properties.get ("InputStatusSize"))
    {
      SString sl = properties["InputStatusLocation"];
      sl.append((char)0);
      int x, y;
      sscanf(sl.array(), "%d,%d", &x, &y);

      SString ss = properties["InputStatusSize"];
      ss.append((char)0);
      int width, height;
      sscanf(ss.array(), "%d,%d", &width, &height);

      xprop.append(SString((long)CONV_ATTR(CONVATTR_STATUS_AREA, 2)));
      xprop.append(SString((long)(x << 16) | (y & 0xffff)));
      xprop.append(SString((long)(width << 16) | (height & 0xffff)));
    }
    if (properties.get ("InputStatusColor"))
    {
      SString col = properties["InputStatusColor"];
      col.append ((char)0);
      unsigned long bg, fg;
      sscanf (col.array(), "%lu,%lu", &bg, &fg);
      SX11Color xbg = SX11Color(impl, (SS_WORD32)bg);
      SX11Color xfg = SX11Color(impl, (SS_WORD32)fg);
      xprop.append (SString ((long) CONV_ATTR(CONVATTR_COLOR, 2)));
      xprop.append (SString ((long)(xfg.getPixelValue())));
      xprop.append (SString ((long)(xbg.getPixelValue())));
    }
    if (properties.get ("InputClientColor"))
    {
      SString col = properties["InputClientColor"];
      col.append ((char)0);
      unsigned long bg, fg;
      sscanf (col.array(), "%lu,%lu", &bg, &fg);
      SX11Color xbg = SX11Color(impl, (SS_WORD32)bg);
      SX11Color xfg = SX11Color(impl, (SS_WORD32)fg);
      xprop.append (SString ((long) CONV_ATTR(CONVATTR_COLOR, 2)));
      xprop.append (SString ((long)(xfg.getPixelValue())));
      xprop.append (SString ((long)(xbg.getPixelValue())));
    }
    SString spotLocation = properties["InputSpot"];
    spotLocation.append((char)0);
    int x, y;
    sscanf(spotLocation.array(), "%d,%d", &x, &y);
    xprop.append(SString((long)CONV_ATTR(CONVATTR_SPOT_LOCATION, 1)));
    xprop.append(SString((long)(x << 16) | (y & 0xffff)));
  }

  if (properties.get ("LineSpacing"))
  {
    SString lsp = properties["LineSpacing"];
    lsp.append ((char)0);
    int spacing;
    sscanf (lsp.array(), "%d", &spacing);
    xprop.append (SString ((long) CONV_ATTR(CONVATTR_LINE_SPACING, 1)));
    xprop.append (SString((long)spacing));
  }

  Atom attributeAtom = toAtom ("CONVERSION_ATTRIBUTE");
  unsigned int psize = xprop.size();
  /* I have to deal here with bugs all over X11/Kinput on Alpha AXP */
  for (unsigned int i=0; i<psize; i++)
  {
    xprop.append (SString((long)0));
  }
  XChangeProperty (impl->display, (Window) clientWindow, 
     attributeAtom, attributeAtom, 
     32, PropModeReplace, 
     (unsigned char *)xprop.array(), 
     /* tricky len calculation for buggy kinput2 */
     (sizeof(long)/sizeof(SS_WORD32)) * psize  / sizeof(SS_WORD32));

  XFlush (impl->display);
  return true;
}

/**
 * Convert string to atom.
 * @param str is the string name of atom.
 */
Atom
SXInputMethod::toAtom (const SString& str)
{
  SString a=str;
  a.append ((char)0);
  return XInternAtom (impl->display, a.array(), False);
}

/**
 * Get the window that is the selection owner of atom.
 */
Window
SXInputMethod::getOwner (Atom atom)
{
  if (atom == None) return None;
  return XGetSelectionOwner (impl->display, atom);
}

/**
 * Send a client message to window. 
 * Mostly used to communicate with kinput2 Window.
 * TODO: This is the most dangerous part. If window disappears while 
 * getting the id and sending the event, X error handles needs to be implemented
 * to prevent app from exiting.
 * @param _type is the message type - atom string.
 * @param v contains all the elements in the message 
 * @return false if anything is wrong.
 */
bool
SXInputMethod::sendEvent (const SString& _type, Window window,
  long p0 ,long p1, long p2, long p3, long p4)
{
  if (window == None) return false;
  Atom type = toAtom (_type);
  if (type == None) return false;

  XEvent event;
  event.xclient.type = ClientMessage;
  event.xclient.window = window;
  event.xclient.display = impl->display;
  event.xclient.message_type = type; 
  event.xclient.format = 32;
  event.xclient.data.l[0] = p0;
  event.xclient.data.l[1] = p1;
  event.xclient.data.l[2] = p2;
  event.xclient.data.l[3] = p3;
  event.xclient.data.l[4] = p4;
  XSendEvent (impl->display, window,  False, NoEventMask, &event);
  return true;
}

/**
 * Creare an IC. If IC is ascii of none, it will work by all means.
 * If it fails it will fall back to ascii and set name and isAscii.
 * member variables in the class accordingly.
 * @param st is the name if ic in the form of x-kinput-ja_JP.eucJP
 * @param props is the input method properties.
 * @return true if we could make it work.
 */
bool
SXInputMethod::createIC (const SString& str, const SProperties& props)
{
  if (xic!=0 && xim!=0) XDestroyIC (xic);
  if (xim!=0 && xic!=0) XCloseIM (xim);

  inputStyle = "preedit-over-status-over";
  icEncoding = "";
  xic = 0; xim = 0;
   
  SStringVector nv(str, ":");
  SString sname = nv[0];
  name = sname;
  if (sname.size() > 2 && sname[1] == '-') 
  {
    /* 'x-' */
    sname.remove (0);
    sname.remove (0);
  }
  isAscii = (sname == "ascii");
  SProperties ps = props;
  if (sname == "ascii" ||  sname == "none" || sname == "utf-8")
  {
    ps.put ("InputStyle", "none");
    sname = "none";
  }

  SString zname("@im=");
  zname.append (sname);
  zname.append ((char)0);

  SString slocale = SString ((nv.size() > 1) ? SString(nv[1]) : SString(""));
  bool localeOK = false;

#ifdef HAVE_LOCALE
  SString zlocale = slocale;
  if (zlocale.size() == 0) zlocale = "C";
  zlocale.append ((char)0);
  if (!setlocale (LC_ALL, zlocale.array()))
  {
    setlocale (LC_ALL, "C");
    fprintf (stderr, "Locale %s is not supported by C library.\n", 
        zlocale.array());
  }
  else if (!XSupportsLocale()) 
  {
    fprintf (stderr, "Locale %s is not supported by X.\n", 
         zlocale.array());
  }
  else
  {
    localeOK = true;
    //fprintf (stderr, "set locale %s.\n", zlocale.array());
  }
#else
  fprintf (stderr, "No locale support on this machine.\n");
  isAscii = true;
  name = "x-ascii";
  return false;
#endif
  icEncoding = "";
  SStringVector vl(slocale, ".", true);
  if (vl.size() == 2)
  {
    icEncoding = vl[1];
    icEncoding.lower();
    if (icEncoding == "eucjp")
    {
       icEncoding = "euc-jp";
    }
    else if (icEncoding == "euckr")
    {
       icEncoding = "euc-kr";
    }
    else if (icEncoding == "gb2312")
    {
       icEncoding = "gb-18030";
    }
    else if (icEncoding == "gb18030")
    {
       icEncoding = "gb-18030";
    }
    else if (icEncoding == "gb-2312")
    {
       icEncoding = "gb-18030";
    }
    else if (icEncoding == "big5")
    {
       icEncoding = "big-5";
    }
    else if (icEncoding == "utf8")
    {
       icEncoding = "";
    }
    else if (icEncoding == "iso8859-1")
    {
       icEncoding = "iso-8859-1";
    }
    else if (icEncoding == "iso8859-2")
    {
       icEncoding = "iso-8859-2";
    }
    else if (icEncoding == "iso8859-3")
    {
       icEncoding = "iso-8859-3";
    }
    else if (icEncoding == "iso8859-4")
    {
       icEncoding = "iso-8859-4";
    }
    else if (icEncoding == "iso8859-5")
    {
       icEncoding = "iso-8859-5";
    }
    else if (icEncoding == "iso8859-6")
    {
       icEncoding = "iso-8859-6";
    }
    else if (icEncoding == "iso8859-7")
    {
       icEncoding = "iso-8859-7";
    }
    else if (icEncoding == "iso8859-8")
    {
       icEncoding = "iso-8859-8";
    }
    else if (icEncoding == "iso8859-9")
    {
       icEncoding = "iso-8859-9";
    }
    else if (icEncoding == "iso8859e")
    {
       icEncoding = "iso-8859e";
    }
    else if (icEncoding == "iso8859-15")
    {
       icEncoding = "iso-8859-15";
    }
    else if (icEncoding == "iso8859-16")
    {
       icEncoding = "iso-8859-16";
    }
    else if (icEncoding == "utf-8")
    {
       icEncoding = "";
    }
  }
  
  char * p = localeOK?XSetLocaleModifiers(zname.array()):0;
  if (localeOK && (p==0 || *p == 0))
  {
    fprintf (stderr, "setting modifiers '%s' failed.\n", zname.array());
  }
  xim = (p!=0 && *p) ? XOpenIM(impl->display, 0, 0, 0) : 0;
  bool status = (xim!=0);
  /* hmm. */
  if (!status)
  {
    fprintf (stderr, "openim '%s' failed.\n", zname.array());
    icEncoding = "";
    localeOK = false;
#ifdef HAVE_LOCALE
    setlocale (LC_ALL, "C");
    if (!setlocale (LC_ALL, "C"))
    {
      setlocale (LC_ALL, "C");
      fprintf (stderr, "Locale C is not supported by C library.\n"); 
    }
    else if (!XSupportsLocale()) 
    {
      fprintf (stderr, "Locale C is not supported by X.\n"); 
    }
    else
    {
      localeOK = true;
      //fprintf (stderr, "set locale %s.\n", zlocale.array());
    }
#endif
    isAscii = true;
    name = "x-ascii";
    zname = "@im=none";
    zname.append ((char)0);
    p = localeOK?XSetLocaleModifiers(zname.array()):0;
    xim = (p && *p)?XOpenIM (impl->display, 0, 0, 0):0;
    if (xim == 0)
    {
      fprintf (stderr, "Can not open any xim.\n");
      return false;
    }
    ps.put ("InputStyle", "none");
  }
#ifdef HAVE_SET_IM_VALUES
  //fprintf (stderr, "Setting CB.\n");
  XIMCallback  imCallback;
  imCallback.client_data = (char*)&xim;
  imCallback.callback = (XIMProc) destroyIMCB;
  XSetIMValues (xim, XNDestroyCallback , &imCallback , (char *) 0x0);
#endif
  if (ps.get ("InputStyle") == 0) ps.put ("InputStyle", "none");
  SString s = ps["InputStyle"];
  statusAreaOK = false;

  unsigned long code = XIMPreeditNone | XIMStatusNone;
  if (s=="preedit-root-status-root")
  {
    code = XIMPreeditNothing  | XIMStatusNothing;
  }
  else if (s =="preedit-under-status-under")
  {
    code = XIMPreeditArea  | XIMStatusArea;
    statusAreaOK = true;
  }
  else if (s =="preedit-over-status-under")
  {
    code = XIMPreeditPosition | XIMStatusArea;
    statusAreaOK = true;
  }
  else if (s == "preedit-over-status-over")
  {
    code = XIMPreeditPosition | XIMStatusNothing;
  }
  else if (s == "none")
  {
    //code = XIMPreeditNone | XIMStatusNone;
    /* This one will accept compositions */
    code = XIMPreeditNothing | XIMStatusNothing;
  }
  else
  {
    fprintf (stderr, "InputStyle is bad - %*.*s.\n", SSARGS(s));
    XCloseIM(xim);
    xim = 0;
    return false;
  }
  inputStyle = s;

  XIMStyles* xim_styles;
  if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, (char *) 0x0)
      || xim_styles==0 || xim_styles->count_styles==0)
  {
      fprintf (stderr, "input method doesn't support any style\n");
      XCloseIM(xim);
      xim = 0;
      return false;
  }
  int i;
  bool hasrootstyle = false;
  bool hasoverthespot = false;
  for (i=0; i<xim_styles->count_styles; i++)
  {
    if (xim_styles->supported_styles[i] == code) break;
    if (xim_styles->supported_styles[i] == 
     (XIMPreeditNothing  | XIMStatusNothing))
    {
       hasrootstyle = true;
    }
    else if (xim_styles->supported_styles[i] == 
     (XIMPreeditPosition  | XIMStatusNothing))
    {
       hasoverthespot = true;
    }
  }
  bool nostyle =  (i == xim_styles->count_styles);
  if (nostyle && hasoverthespot && inputStyle != "None" && inputStyle != "none")
  {
    //fprintf (stderr, "Reverting to preedit-over-status-over.\n");
    inputStyle = "preedit-over-status-over";
    code = XIMPreeditPosition  | XIMStatusNothing;
    nostyle = false;
    statusAreaOK = false;
  }
  if (hasrootstyle && nostyle && inputStyle != "None" && inputStyle != "none")
  {
    //fprintf (stderr, "Reverting to preedit-root-status-root.\n");
    inputStyle = "preedit-root-status-root";
    code = XIMPreeditNothing  | XIMStatusNothing;
    nostyle = false;
    statusAreaOK = false;
  }
  /* Try no precomposition style */
  if (nostyle && inputStyle == "none")
  {
    for (i=0; i<xim_styles->count_styles; i++)
    {
       if (xim_styles->supported_styles[i] 
          == (XIMPreeditNone|XIMStatusNone))
       {
          code = XIMPreeditNone|XIMStatusNone;
          inputStyle = "preedit-root-status-root";
          nostyle = false;
          statusAreaOK = false;
          break;
       }
    }
  }
  XFree(xim_styles);
  if (nostyle)
  {
    fprintf (stderr, 
        "Style=%*.*s in %*.*s not found. Style negotiation failed.\n",  
        SSARGS(inputStyle), SSARGS(name));
    statusAreaOK = false;
    XCloseIM(xim);
    xim = 0;
    return false;
  }

  char **missing_charset_list;
  int missing_charset_count;
  char *def_string;

  /* Why? */
  if (fontSet == 0)
  {
    fontSet = XCreateFontSet(impl->display, "*",
              &missing_charset_list,
              &missing_charset_count,
              &def_string);
  }
  /* over the spot */
  if (inputStyle == "preedit-over-status-over")
  {
    XPoint spot = {1, 1};
    /* Why the heck do we need fontset? */
    int screen = DefaultScreen (impl->display);

    XVaNestedList attributes = XVaCreateNestedList (0, 
      XNForeground , BlackPixel (impl->display, screen),
      XNBackground , WhitePixel (impl->display, screen),
      XNSpotLocation, &spot,
      XNFontSet, fontSet,
      (char *) 0x0);
    if (attributes==0)
    {
      XCloseIM(xim);
      xim = 0;
      fprintf (stderr, "Can not create XVaNestedList.\n");
      return false;
    }
    xic = XCreateIC(xim, 
       XNInputStyle, code,
       XNClientWindow, (Window) id,
       XNPreeditAttributes, attributes,
       (char *) 0x0);
    XFree (attributes);
    //Bool preedit_state = 1;
    //if (xic) XSetICValues (xic, XNPreeditState, preedit_state, (char *) 0x0);
    if (props.get ("InputClientColor"))
    {
      SString col = props["InputClientColor"];
      col.append ((char)0);
      unsigned long bg, fg;
      sscanf (col.array(), "%lu,%lu", &bg, &fg);
      SX11Color xbg = SX11Color(impl, (SS_WORD32)bg);
      SX11Color xfg = SX11Color(impl, (SS_WORD32)fg);
      XVaNestedList atts = XVaCreateNestedList (0, 
       XNForeground, xfg.getPixelValue(),
       XNBackground, xbg.getPixelValue(),
       (char *) 0x0);
      if (atts) 
      {
        XSetICValues(xic, XNPreeditAttributes, atts, (char *) 0x0);
        XFree (atts);
      }
    }
  }
  else if (inputStyle == "preedit-under-status-under")
  {
    SString sl = props["InputStatusLocation"];
    sl.append((char)0);
    int statusX, statusY;
    sscanf(sl.array(), "%d,%d", &statusX, &statusY);

    SString ss = props["InputStatusSize"];
    ss.append((char)0);
    int statusWidth, statusHeight;
    sscanf(ss.array(), "%d,%d", &statusWidth, &statusHeight);
    XRectangle statusArea;
    statusArea.width  = statusWidth;
    statusArea.height = statusHeight;
    statusArea.x = statusX;
    statusArea.y = statusY;

    SString col = props["InputStatusColor"];
    col.append ((char)0);
    unsigned long sbg, sfg;
    sscanf (col.array(), "%lu,%lu", &sbg, &sfg);
    SX11Color sxbg = SX11Color(impl, (SS_WORD32)sbg);
    SX11Color sxfg = SX11Color(impl, (SS_WORD32)sfg);

    sl = props["InputClientLocation"];
    sl.append((char)0);
    int clientX, clientY;
    sscanf(sl.array(), "%d,%d", &clientX, &clientY);

    ss = props["InputClientSize"];
    ss.append((char)0);
    int clientWidth, clientHeight;
    sscanf(ss.array(), "%d,%d", &clientWidth, &clientHeight);

    XRectangle clientArea;
    clientArea.width  = clientWidth;
    clientArea.height = clientHeight;
    clientArea.x = clientX;
    clientArea.y = clientY;

    col = props["InputClientColor"];
    col.append ((char)0);
    unsigned long cbg, cfg;
    sscanf (col.array(), "%lu,%lu", &cbg, &cfg);
    SX11Color cxbg = SX11Color(impl, (SS_WORD32)cbg);
    SX11Color cxfg = SX11Color(impl, (SS_WORD32)cfg);

    XVaNestedList sa = XVaCreateNestedList (0, 
      XNForeground , sxfg.getPixelValue(),
      XNBackground , sxbg.getPixelValue(),
      XNFontSet, fontSet,
      XNArea, &statusArea,
      (char *) 0x0);

    XVaNestedList ca = XVaCreateNestedList (0, 
      XNForeground , cxfg.getPixelValue(),
      XNBackground , cxbg.getPixelValue(),
      XNFontSet, fontSet,
      XNArea, &clientArea,
      (char *) 0x0);

    if (sa==0 || ca==0)
    {
      XCloseIM(xim);
      xim = 0;
      fprintf (stderr, "Can not create XVaNestedList.\n");
      return false;
    }
    SString pw = props["InputWindow"];
    pw.append ((char)0);
    unsigned long w;
    sscanf (pw.array(), "%lu", &w);
    xic = XCreateIC(xim, XNInputStyle, code,
       XNClientWindow, (Window) w,
       XNFocusWindow, (Window) id,
       XNPreeditAttributes, ca,
       XNStatusAttributes, sa,
       (char *) 0x0);
    XFree (ca);
    XFree (sa);
  }
  else if (inputStyle == "preedit-over-status-under")
  {
    SString sl = props["InputStatusLocation"];
    sl.append((char)0);
    int statusX, statusY;
    sscanf(sl.array(), "%d,%d", &statusX, &statusY);

    SString ss = props["InputStatusSize"];
    ss.append((char)0);
    int statusWidth, statusHeight;
    sscanf(ss.array(), "%d,%d", &statusWidth, &statusHeight);
    XRectangle statusArea;
    statusArea.width  = statusWidth;
    statusArea.height = statusHeight;
    statusArea.x = statusX;

    SString col = props["InputStatusColor"];
    col.append ((char)0);
    unsigned long sbg, sfg;
    sscanf (col.array(), "%lu,%lu", &sbg, &sfg);
    SX11Color sxbg = SX11Color(impl, (SS_WORD32)sbg);
    SX11Color sxfg = SX11Color(impl, (SS_WORD32)sfg);

    col = props["InputClientColor"];
    col.append ((char)0);
    unsigned long cbg, cfg;
    sscanf (col.array(), "%lu,%lu", &cbg, &cfg);
    SX11Color cxbg = SX11Color(impl, (SS_WORD32)cbg);
    SX11Color cxfg = SX11Color(impl, (SS_WORD32)cfg);

    XVaNestedList sa = XVaCreateNestedList (0, 
      XNForeground , sxfg.getPixelValue(),
      XNBackground , sxbg.getPixelValue(),
      XNFontSet, fontSet,
      XNArea, &statusArea,
      (char *) 0x0);

    XPoint spot = {1, 1};
    XVaNestedList ca = XVaCreateNestedList (0, 
      XNForeground , cxfg.getPixelValue(),
      XNBackground , cxbg.getPixelValue(),
      XNSpotLocation, &spot,
      XNFontSet, fontSet,
      (char *) 0x0);

    if (sa==0 || ca==0)
    {
      XCloseIM(xim);
      xim = 0;
      fprintf (stderr, "Can not create XVaNestedList.\n");
      return false;
    }
    /* I can do this only in the same window */
    xic = XCreateIC(xim, XNInputStyle, code,
       XNClientWindow, (Window) id,
       XNFocusWindow, (Window) id,
       XNPreeditAttributes, ca,
       XNStatusAttributes, sa,
       (char *) 0x0);
    XFree (ca);
    XFree (sa);
  }
  else /* preedit-root-status-root */
  {
    xic = XCreateIC(xim, XNInputStyle, code,
       XNClientWindow, (Window) id,
       XNFocusWindow, (Window) id,
       (char *) 0x0);
  }
  if (xic==0) 
  {
    XCloseIM(xim);
    xim = 0;
    fprintf (stderr, "Could not create XIC from XIM\n");
    return false;
  }
  /* is it int and not long? */
  int  xim_ev_mask = 0;

  XGetICValues (xic , XNFilterEvents , &xim_ev_mask , NULL);
  xim_ev_mask |= KeyPressMask|KeyReleaseMask;
  XSetICValues (xic , XNFilterEvents , &xim_ev_mask , NULL);

  //XSetICValues(xic, XNResetState, XIMPreserveState, (char *) 0x0);
  eventMask |= xim_ev_mask;

  XSelectInput (impl->display, (Window) id, eventMask);
  XSetICFocus (xic);
  /* is it good ? */
  setProperties (props);
  //XSetInputFocus (impl->display, (Window) id, RevertToNone, CurrentTime);
 
  return status;
}
/**
 * I tested it - never worked - gaspar
 */
void destroyIMCB (XIM xim, XPointer p0, XPointer p1)
{
  fprintf (stderr, "X Input server crashed.\n");
  *((XIM*)p0) = 0;
}

const SString&
SXInputMethod::getICEncoding () const
{
  return icEncoding;
}

XIC
SXInputMethod::getIC ()
{
  if (name == ATOM_KINPUT2) return 0;
  if ((xic == 0 || xim == 0) && !isAsciiInput())
  {
    createIC ("x-ascii", SProperties());
fprintf (stderr, "x-ascii %lx", (unsigned long) xic);
  }
  return xic;
}
