/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial
portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP OR SUN MICROSYSTEMS, INC. BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/

#include "iiimpIM.h"
#include "iiimpIC.h"
#include "iiimpAuxP.h"
#include <stdio.h>
#include <unistd.h>
#include <locale.h>
#include <langinfo.h>
#include <ctype.h>
#include "trace_message.h"
#include "iiimpSwitcher.h"

#include "XIM.h"		/* for XNMultiLingualInput */

static XIMStyle im_styles[] = {
	XIMPreeditCallbacks |XIMStatusCallbacks,
	XIMPreeditCallbacks | XIMStatusArea,
	XIMPreeditCallbacks | XIMStatusNothing,
	XIMPreeditPosition  | XIMStatusCallbacks,
	XIMPreeditPosition  | XIMStatusArea,
	XIMPreeditPosition  | XIMStatusNothing,
	XIMPreeditArea      | XIMStatusCallbacks,
	XIMPreeditArea      | XIMStatusArea,
	XIMPreeditArea      | XIMStatusNothing,
	XIMPreeditNothing   | XIMStatusCallbacks,
	XIMPreeditNothing   | XIMStatusArea,
	XIMPreeditNothing   | XIMStatusNothing,
	XIMPreeditCallbacks | XIMStatusNone,
	XIMPreeditPosition  | XIMStatusNone,
	XIMPreeditArea      | XIMStatusNone,
	XIMPreeditNothing   | XIMStatusNone,
	XIMPreeditNone	| XIMStatusCallbacks,
	XIMPreeditNone      | XIMStatusArea,
	XIMPreeditNone      | XIMStatusNothing,
	XIMPreeditNone      | XIMStatusNone,
};

/* For Interface for libX11 */
extern Status IIIMP_OpenIM_SWITCH(
    XIM xim,
    XLCd lcd,
    Display *dpy
);

static Status IIIMP_CloseIM(
    XIM xim
);

extern char* IIIMP_SetIMValues(
    XIM xim,
    XIMArg* arg
);
extern char* IIIMP_GetIMValues(
    XIM xim,
    XIMArg* arg
);

static XIC IIIMP_CreateIC(
    XIM xim,
    XIMArg* arg
);
extern int _Ximp_ctstombs(
    XIM	xim,
    char *from,
    int	from_len,
    char *to,
    int	to_len,
    Status *state
);
extern int _Ximp_ctstowcs(
    XIM	xim,
    char *from,
    int from_len,
    wchar_t *to,
    int to_len,
    Status *state
);

extern IIIMF_status IMCreateHandle(XimCommon im);

static XIMMethods
get_iiimp_im_methods()
{
  static XIMMethodsRec imm;
  if (!imm.close) {
    imm.close = IIIMP_CloseIM;
    imm.set_values = IIIMP_SetIMValues;
    imm.get_values = IIIMP_GetIMValues;
    imm.create_ic = IIIMP_CreateIC;
    imm.ctstombs = _Ximp_ctstombs;
    imm.ctstowcs = _Ximp_ctstowcs;
  }
  return &imm;
}


static Status SWITCH_CloseIM(
    XIM xim
);

static XIMMethods
get_switch_im_methods()
{
  static XIMMethodsRec imm;
  if (!imm.close) {
    imm.close = SWITCH_CloseIM;
  }
  return &imm;
}

/********************************************************************************
                    IM Language and X locale name
 ********************************************************************************/

static im_locale_pair g_locale_list[] = {
  {(char*)"ja", (char*)"ja"},
  {(char*)"ja", (char*)"ja_JP.EUC-JP"},
  {(char*)"ja", (char*)"ja_JP.eucJP"},
  {(char*)"ja", (char*)"ja_JP.PCK"},
  {(char*)"ja", (char*)"ja_JP.UTF-8"},
  {(char*)"ko", (char*)"ko"},
  {(char*)"ko", (char*)"ko.UTF-8"},
  {(char*)"ko", (char*)"ko_KR.UTF-8"},
  {(char*)"ko", (char*)"ko_KR.EUC-KR"},
  {(char*)"ko", (char*)"ko_KR.EUC"},
  {(char*)"zh_CN", (char*)"zh.UTF-8"},
  {(char*)"zh_CN", (char*)"zh_CN.UTF-8"},
  {(char*)"zh_CN", (char*)"zh_CN.EUC"},
  {(char*)"zh_CN", (char*)"zh_CN.GB18030"},
  {(char*)"zh_CN", (char*)"zh_CN"},
  {(char*)"zh_CN", (char*)"zh"},
  {(char*)"zh_CN", (char*)"zh.GBK"},
  {(char*)"zh_CN", (char*)"zh_CN.GBK"},
  {(char*)"zh_TW", (char*)"zh_TW.UTF-8"},
  {(char*)"zh_TW", (char*)"zh_TW.BIG5"},
  {(char*)"zh_TW", (char*)"zh_TW.EUC-TW"},
  {(char*)"zh_TW", (char*)"zh_TW.EUC"},
  {(char*)"zh_TW", (char*)"zh_TW"},
  {(char*)"zh_HK", (char*)"zh_HK.BIG5HK"},
  {(char*)"zh_HK", (char*)"zh_HK.UTF-8"},
  {(char*)"zh_HK", (char*)"zh_HK"},
  {(char*)"th_TH", (char*)"th"},
  {(char*)"th_TH", (char*)"th_TH"},
  {(char*)"th_TH", (char*)"th_TH.UTF-8"},
  {(char*)"th_TH", (char*)"th_TH.TIS620"},
  {(char*)"th_TH", (char*)"th_TH.ISO8859-11"},
  {0, 0}
};

#define IMLIBDIR IIIMLIBDIR

#define	iscomment(ch)	((ch) == '\0' || (ch) == '#')

static int
parseLine(
    char *line,
    char **argv,
    size_t argsize
)
{
    int argc = 0;
    char *p = line;

    while (argc < argsize) {
	while (isspace(*p)) {
	    ++p;
	}
	if (iscomment(*p)){
	    break;
	}
	argv[argc++] = p;
	while (!isspace(*p)) {
	    ++p;
	}
	if (iscomment(*p)) {
	    break;
	}
	*p++ = '\0';
    }
    return argc;
}

static void
parseAliasFile(
    XimCommon im,
    FILE *fp
)
{
    char buf[BUFSIZ];
    size_t alias_num = 0;
    im_locale_pair *lang_alias = 0;

    while (fgets(buf, BUFSIZ, fp) != NULL) {
	char *p = buf;
	int n;
	char *args[2];
	while (isspace(*p))
	    ++p;
	if (iscomment(*p))
	    continue;

	n = parseLine(p, args, 2);
	if (n == 2) {
	    if (alias_num == 0)
		lang_alias = Xmalloc(sizeof(im_locale_pair));
	    else
		lang_alias = Xrealloc((void*)lang_alias,
				      sizeof(im_locale_pair) * (alias_num + 1));
	    if (!lang_alias)
		return;
	    lang_alias[alias_num].alias = strdup(args[0]);
	    lang_alias[alias_num].canonical = strdup(args[1]);
	    alias_num++;
	}
    }
    /* null terminate */
    lang_alias = Xrealloc((void*)lang_alias,
			  sizeof(im_locale_pair) * (alias_num + 1));
    if (!lang_alias)
	return;
    lang_alias[alias_num].alias = 0;
    lang_alias[alias_num].canonical = 0;

    XIM_IIIMP(im, lang_alias) = lang_alias;
    return;
}

static void
free_langalias(
    XimCommon im
)
{
    im_locale_pair *p1, *p;

    p1 = XIM_IIIMP(im, lang_alias);
    if (p1) {
	for (p = p1; p->canonical; p++) {
	    if (p->alias) Xfree(p->alias);
	    if (p->canonical) Xfree(p->canonical);
	}
	Xfree(p1);
    }
}

static char*
getIMLangName(
    XimCommon im,
    const char *src_name
)
{
    im_locale_pair *p;

    if (!XIM_IIIMP(im, lang_alias)) {
	/*  open 'alias' file*/
	FILE *fp;
	const char *dir = getenv ("IIIMFHOME");
	const char *base = "language.alias";

	if (dir == NULL)
	    dir = IMLIBDIR;

	{
	    size_t dir_len = strlen (dir);
	    size_t base_len = strlen (base);
	    size_t name_len = dir_len + 1 + base_len + 1;

	    char *file_name = Xmalloc(name_len + 1);
	    if (file_name) {
		strncpy(file_name, dir, dir_len);
		file_name[dir_len] = '/';
		file_name[dir_len + 1] = 0;
		strncat(file_name, base, base_len);
		file_name[dir_len + 1 + base_len] = 0;

		if ((fp = fopen (file_name, "r"))) {
		    parseAliasFile(im, fp);
		    fclose(fp);
		}
                Xfree(file_name);
	    }
	}
    }

    p = (XIM_IIIMP(im, lang_alias) ? XIM_IIIMP(im, lang_alias) : g_locale_list);

    for (; p->canonical != 0; p++)
	if (!strcmp(p->alias, src_name))
	    return p->canonical;

    return (char*)0;
}

char*
im_canonicalize_langname(
    XimCommon im
)
{
    char *canonical, *p;
    const char *localename = setlocale(LC_CTYPE, NULL);

    canonical = getIMLangName(im, localename);
    if (canonical) return strdup(canonical);

    canonical = strdup(localename);
    if (!canonical) return NULL;
    p = strchr(canonical, '.');
    if (p) *p = '\0';

    return canonical;
}

IIIMCF_language
get_IM_language(
    XimCommon im
)
{
    int i, n;
    const char *ll;
    char *lang;
    char *p;
    IIIMCF_language *plangs;
    IIIMF_status st;

    if (XIM_IIIMP(im, primary_locale)) {
	lang = strdup(XIM_IIIMP(im, primary_locale));
    } else {
	lang = im_canonicalize_langname(im);
    }

    if (!lang) return NULL;

    if (iiimcf_get_supported_languages(XIM_IIIMP(im, handle), &n, &plangs)
	!= IIIMF_STATUS_SUCCESS)
	return NULL;
    for (i = 0; i < n; i++) {
	st = iiimcf_get_language_id(plangs[i], &ll);
	if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	if (strcmp(lang, ll) == 0)
	    return plangs[i];
    }
    /* try to find the supported language again with no charset locale. */
    p = strchr (lang, '@');
    if (p)
	*p = 0;
    p = strchr (lang, '.');
    if (p)
	*p = 0;

    for (i = 0; i < n; i++) {
	st = iiimcf_get_language_id(plangs[i], &ll);
	if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	if (strcmp(lang, ll) == 0)
	    return plangs[i];
    }
    /* finally try to take care of the locale, such as en */
    p = strchr (lang, '_');
    if (p)
	*p = 0;

    for (i = 0; i < n; i++) {
	st = iiimcf_get_language_id(plangs[i], &ll);
	if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	if (strcmp(lang, ll) == 0)
	    return plangs[i];
    }
    free(lang);

      /*
	still cannot find a match. Just take one for "en" as
	a default.
      */
    for (i = 0; i < n; i++) {
	st = iiimcf_get_language_id (plangs[i], &ll);
	if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	if (strncmp (ll, "en", 2) == 0)
	    return plangs[i];
    }

    return NULL;
}

/********************************************************************************
                               main part
 ********************************************************************************/

static int inited = 0;

static void
initialize()
{
    if (inited == 0)
	iiimcf_initialize(IIIMCF_ATTR_NULL);
    inited++;
}

static void
uninitialize()
{
    if (inited == 0) return;
    inited--;
    if (inited == 0)
	iiimcf_finalize();
}

static Status
IIIMP_CloseIM(
    XIM xim
)
{
    CommonCloseIM(xim);
    SWITCH_CloseIM(xim);
    
    return True;
}

static XIC
IIIMP_CreateIC(
    XIM xim,
    XIMArg *arg
)
{
    return (XIC)CreateIC((XimCommon)xim, arg);
}

char*
IIIMP_SetIMValues(
    XIM xim,
    XIMArg *arg
)
{
    XIMArg *p;
    char *return_name = NULL;
    XimCommon im = (XimCommon)xim;

    if (!xim) return arg->name;	/* nothing to do */

    for (p = arg; p->name != NULL; p++) {
	if (!strcmp(p->name, "engineInterfaceName")) {
	    XIM_IIIMP(im, engine_name) = (char*)p->value;
	} else if (!strcmp(p->name, "applicationType")) {
	    IMChangeClientType(im, (char*) p->value);
	} else if (!strcmp(p->name, "defaultFontName")) {
	    XIM_IIIMP(im, default_font_name) = (char*)p->value;
	} else if (!strcmp(p->name, "primaryLocale")) {
	    XIM_IIIMP(im, primary_locale) = (char*)p->value;
	} else if (!strcmp(p->name, XNDestroyCallback)) {
	    im->core.destroy_callback.client_data = 
		((XIMCallback *)p->value)->client_data;
	    im->core.destroy_callback.callback = 
		((XIMCallback *)p->value)->callback;
	} else {
	    return_name = arg->name;
	    break;
	}
    }

    return return_name;
}

extern void UpdateIMCharacterSubset(XimCommon);

char*
IIIMP_GetIMValues(
    XIM xim,
    XIMArg *arg
)
{
    XIMArg *p;
    int i;
    XimCommon im = (XimCommon)xim;

    if (!xim) return arg->name;	/* nothing to do */

    for (p = arg; p->name != NULL; p++) {
	TRACE_MESSAGE('v', ("  %s\n", p->name));
	if (!strcmp(p->name, XNQueryInputStyle)) {
	    XIMStyles **value;
	    XIMStyles *styles;
	    size_t count = sizeof(im_styles)/sizeof(im_styles[0]);
	    if ((styles = (XIMStyles*)
		 Xmalloc(sizeof(XIMStyles) +
			 sizeof(XIMStyle) * count)) == NULL) {
		break;
	    }
	    styles->count_styles = count;
	    styles->supported_styles = (XIMStyle *)(&styles[1]);

	    for (i = 0; i < (int)(styles->count_styles); i++) {
		styles->supported_styles[i] = im_styles[i];
	    }
	    value = (XIMStyles **)p->value;
	    *value = styles;
	} else if (!strcmp(p->name, XNMultiLingualInput)) {
	    *((Bool*)(p->value)) = ((XimCommon)xim)->isUnicode;
	} else if (!strcmp(p->name, XNQueryExtensionVersion)) {
	    *((int*)(p->value)) = XIIIMP_MULTILINGUAL_EXTENSION_VERSION;
	} else if (!strcmp(p->name, XNQueryUnicodeCharacterSubset)) {
	    XIMUnicodeCharacterSubsets **value;
	    XIMUnicodeCharacterSubsets *sub_sets;
	    XIMUnicodeCharacterSubsets *im_subset;
	    size_t count = 0;

	    /* 
	       when GIMLET is present, we don't let applications
	       have their own choice windows, based on XLC_LOCALE
	    */
	    if (im_switcher_active(im))
	      return p->name;

	    /* RECONSIDER!!! */
	    UpdateIMCharacterSubset(im);

	    if (!im->unicode_char_subsets) break;

	    im_subset = im->unicode_char_subsets;

	    count = im_subset->count_subsets;

	    if ((sub_sets = (XIMUnicodeCharacterSubsets*)
		 Xmalloc(sizeof(XIMUnicodeCharacterSubsets) +
			 sizeof(XIMUnicodeCharacterSubset) * count)) == NULL) {
		break;
	    }
	    sub_sets->count_subsets = count;
	    sub_sets->supported_subsets =
		(XIMUnicodeCharacterSubset*)(&sub_sets[1]);

	    for (i = 0; i < (int)(sub_sets->count_subsets); i++) {
		sub_sets->supported_subsets[i] = im_subset->supported_subsets[i];
	    }
	    value = (XIMUnicodeCharacterSubsets **)p->value;
	    *value = sub_sets;
      
	} else {
	    break;
	}
    }
    return p->name;
}

XIM
_IIIMP_OpenIM(
    XLCd lcd,
    Display *dpy,
    XrmDatabase rdb,
    char *res_name,
    char *res_class
)
{
    XimCommon im = 0;

    im = Xmalloc(sizeof(XimCommonRec));
    if (!im) goto Set_Error;
    memset(im, 0, sizeof(XimCommonRec));

    if (!CommonOpenIM((XIM)im, lcd, dpy, rdb, res_name, res_class))
	goto Set_Error;

    im->methods = get_iiimp_im_methods();

    if (!IIIMP_OpenIM_SWITCH((XIM)im, lcd, dpy)) goto Set_Error;
    return (XIM)im;

Set_Error:
    if (im) Xfree(im);
    return 0;
}

Status
IIIMP_OpenIM_SWITCH(
    XIM xim,
    XLCd lcd,
    Display *dpy
)
{
    XimCommon im = (XimCommon)xim;
    XIMIIimpIM im_private = 0;
    char *mod, buf[BUFSIZE];
    int mod_len;

    initialize();

    im_private = Xmalloc(sizeof(XIMIIimpIMRec));
    if (!im_private) goto Set_Error;
    memset(im_private, 0, sizeof (XIMIIimpIMRec));
    im->iiimp_impart = im_private;

    XIM_IIIMP(im, cb_window) = None;
    XIM_IIIMP(im, switch_methods) = get_switch_im_methods();

    buf[0] = '\0';
    mod_len = 0;

    im->core.im_name = 0;
    if ((lcd->core->modifiers) && (*lcd->core->modifiers)) {
#define	MODIFIER "@im="
	mod = strstr(lcd->core->modifiers, MODIFIER);
	if (mod) {
	    mod += strlen(MODIFIER);

	    if (!strncmp(mod, "iiimp/", 6) || !strncmp(mod, "IIIMP/", 6)) {
		mod += 6;

		if (index(mod, ':')) {
		    while (*mod && *mod != '@' && mod_len < BUFSIZE - 1) {
			buf[mod_len++] = *mod++;
		    }
		    buf[mod_len] = '\0';
		    im->core.im_name = Xmalloc(mod_len+1);
		    if (!im->core.im_name) goto Set_Error;

		    strcpy(im->core.im_name, buf);
		}
	    }
	}
#undef MODIFIER
    }

    if (IMCreateHandle(im) != IIIMF_STATUS_SUCCESS)
	goto Set_Error;


#if 0 /* The following is required for asych. message handling.  */
    XIM_IIIMP(im, cb_window) =
	XCreateSimpleWindow(im->core.display,
			    DefaultRootWindow(im->core.display),
			    0, 0, 1, 1, 0, 0, 0);
    if (None != XIM_IIIMP(im, cb_window)) {
	_XRegisterFilterByType(im->core.display, XIM_IIIMP(im, cb_window),
			       ClientMessage, ClientMessage,
			       IMCBFilter, (XPointer)im);
    }
#endif

    return True;

Set_Error:
    if (im_private) {
	Xfree(im_private);
	im->iiimp_impart = NULL;
    }
    IIIMP_CloseIM((XIM)im);
    return False;
}

/* Switching */
static Status
SWITCH_CloseIM(
    XIM xim
)
{
    XimCommon im = (XimCommon)xim;

    IIimpFreeAllAuxData();

    if (im->core.im_name)
	Xfree(im->core.im_name);

    if (!im->iiimp_impart) return True;

#if 0 /* The following is required for asych. message handling.  */
    if (None != XIM_IIIMP(im, cb_window)) {
	_XUnregisterFilter(im->core.display, XIM_IIIMP(im, cb_window),
			   IMCBFilter, (XPointer)im);
	XDestroyWindow(im->core.display, XIM_IIIMP(im, cb_window));
    }
#endif
    if (XIM_IIIMP(im, handle)) {
	iiimcf_destroy_handle(XIM_IIIMP(im, handle));
    }
    IM_free_langlist(im);
    free_langalias(im);
    if (XIM_IIIMP(im, pkev_on)) {
	Xfree(XIM_IIIMP(im, pkev_on));
    }
    if (XIM_IIIMP(im, pkev_off)) {
	Xfree(XIM_IIIMP(im, pkev_off));
    }

    Xfree(im->iiimp_impart);
    im->iiimp_impart = 0;
    uninitialize();
    im_switcher_shutdown (im);
    return True;
}


/* only used for pluggin iiimp into
   X consortium based libX11
*/
Bool
_XInitIIIMP(
    XLCd lcd
)
{
    if (lcd == (XLCd)NULL)
	return False;

    lcd->methods->open_im = _IIIMP_OpenIM;
    lcd->methods->register_callback = NULL;
    lcd->methods->unregister_callback = NULL;

    return True;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
