/* # skkinput (Simple Kana-Kanji Input)
 *
 * This file is part of skkinput.
 * Copyright (C) 2002
 * Takashi SAKAMOTO (PXG01715@nifty.ne.jp)
 *
 * 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, 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 skkinput; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */
//#include "AfxWin.h"
//#include "local.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include "XIMServerP.h"
#include "XIMProtocol.h"
#include "skkkey.h"

#define offset(field)  XtOffsetOf(XIMServerRec, ximServer.field)
#define goffset(field) XtOffsetOf(WidgetRec, core.field)

static XtResource	srXIMServerResources [] = {
	{	XtNwidth,		XtCWidth,		XtRDimension,	sizeof (Dimension),
		goffset (width),			XtRImmediate,	(XtPointer) 1, },
	{	XtNheight,		XtCHeight,		XtRDimension,	sizeof (Dimension),
		goffset (height),			XtRImmediate,	(XtPointer) 1, },
	{	XtNmappedWhenManaged,	XtCMappedWhenManaged,	XtRBoolean,	sizeof (Boolean),
		goffset (mapped_when_managed),	XtRImmediate,	(XtPointer)False, },
	{	XtNlispMachine,	XtCLispMachine,	XtRPointer,		sizeof (XtPointer),
		offset (m_pLispMachine),	XtRImmediate,	(XtPointer) 0, },
	{	XtNsupportedLocales,		XtCSupportedLocales,
		XtRString,					sizeof (String),
		offset (m_strLocales),		XtRImmediate,
		(XtPointer) "ja_JP.SJIS,ja_JP.eucJP,ja_JP.EUC,ja_JP,japanese,japan,ja", },
	{	XtNconversionStartKey,			XtCConversionStartKey,
		XtRString,						sizeof (String),
		offset (m_strConvStartKeys),	XtRImmediate,
		(XtPointer) "Shift<Key>space,Cntrl<Key>Kanji,Cntrl<Key>Henkan_Mode", },
	{	XtNdestroyCallback,			XtCCallback,
		XtRCallback,				sizeof (XtCallbackList),
		offset (m_lstCbkDestroy),	XtRCallback,		(XtPointer) NULL, },
	{	XtNsetupInputWindowNotify,	XtCCallback,
		XtRCallback,				sizeof (caddr_t),
		offset (m_setupInputWindowCallback),	XtRCallback,	(caddr_t)NULL },
	{	XtNserverCloseNotify,		XtCCallback,
		XtRCallback,				sizeof (caddr_t),
		offset (m_serverCloseCallback),			XtRCallback,	(caddr_t)NULL },
} ;

#undef offset
#undef goffset

/* parseStr.c */
extern	int			parseXrLikeKeyStrings (unsigned char*, struct XrLikeKey*) ;

static	void		ximServer_onInitialize	(Widget, Widget, ArgList, Cardinal*) ;
static	void		ximServer_onRealize		(Widget, XtValueMask*, XSetWindowAttributes*) ;
static	Boolean		ximServer_onSetValues	(Widget, Widget, Widget, ArgList, Cardinal*) ;
static	void		ximServer_onDestroy		(Widget) ;

static	void		ximServer_onClientMessage		(Widget, XEvent*, String*, Cardinal*) ;
static	void		ximServer_onSelectionRequest	(Widget, XEvent*, String*, Cardinal*) ;
static	void		ximServer_onSelectionClear		(Widget, XEvent*, String*, Cardinal*) ;
static	void		ximServer_onClientDestroy		(Widget, XtPointer, XtPointer) ;

static	Boolean		ximServer_setServerProfile		(Widget) ;
static void			ximServer_getStartConversionKey	(Widget) ;

static	Widget		ximServer_newClient				(Widget, Window) ;
static	Widget		ximServer_findClient			(Widget, Window) ;
static	void		ximServer_removeClient			(Widget, Widget) ;
static	void		ximServer_destroyAllClient		(Widget) ;
static	Boolean		ximServer_isValidWindow			(Display*, Window) ;

static XtActionsRec		srXIMServerActionsTable [] = {
	{	"client-message",				ximServer_onClientMessage, },
	{	"selection-request",			ximServer_onSelectionRequest, },
	{	"selection-clear",				ximServer_onSelectionClear, },
} ;

static char	sstrXIMServerTranslations []	= 
"<Message>:                            client-message()\n\
 <SelReq>:                             selection-request()\n\
 <SelClr>:                             selection-clear()" ;

XIMServerClassRec	ximServerClassRec = {
    {	/* core fields 			*/
		/* superclass			*/	&widgetClassRec,
		/* class_name			*/	"XIMServer",
		/* size					*/	sizeof (XIMServerRec),
		/* class_initialize		*/	NULL,
		/* class_part_initialize*/	NULL,
		/* class_inited			*/	FALSE,
		/* initialize			*/	ximServer_onInitialize,
		/* initialize_hook		*/	NULL,
		/* realize				*/	ximServer_onRealize,
		/* actions				*/	srXIMServerActionsTable,
		/* num_actions			*/	XtNumber (srXIMServerActionsTable),
		/* resources			*/	srXIMServerResources,
		/* num_resources		*/	XtNumber (srXIMServerResources),
		/* xrm_class			*/	NULLQUARK,
		/* compress_motion		*/	TRUE,
		/* compress_exposure	*/	TRUE,
		/* compress_enterleave	*/	TRUE,
		/* visible_interest		*/	FALSE,
		/* destroy				*/	ximServer_onDestroy,
		/* resize				*/	NULL,
		/* expose				*/	NULL,
		/* set_values			*/	ximServer_onSetValues,
		/* set_values_hook		*/	NULL,
		/* set_values_almost	*/	XtInheritSetValuesAlmost,
		/* get_values_hook		*/	NULL,
		/* accept_focus			*/	NULL,
		/* version				*/	XtVersion,
		/* callback_private		*/	NULL,
		/* tm_table				*/	sstrXIMServerTranslations,
		/* query_geometry		*/	XtInheritQueryGeometry,
    }, {
		/* dummy				*/	0,
	}
} ;

WidgetClass ximServerWidgetClass = (WidgetClass)&ximServerClassRec ;


void
ximServer_onInitialize (
	register Widget		greq,
	register Widget		gnew,
	register ArgList	args,
	register Cardinal*	num_args)
{
	register XIMServerWidget	wgThis		= (XIMServerWidget) gnew ;
	register Display*			pDisplay	= XtDisplay (gnew) ;

	TVarbuffer_Initialize (&wgThis->ximServer.m_vbufClient, sizeof (Widget)) ;
#define MakeAtom(s)	XInternAtom(pDisplay,s,False)
	wgThis->ximServer.m_atServer		= MakeAtom ("@server=" XIM_SERVER_NAME) ;
	wgThis->ximServer.m_atCompoundText	= MakeAtom ("COMPOUND_TEXT") ;
	wgThis->ximServer.m_atLocales		= MakeAtom ("LOCALES") ;
	wgThis->ximServer.m_atTransport		= MakeAtom ("TRANSPORT") ;
	wgThis->ximServer.m_atXConnect		= MakeAtom ("_XIM_XCONNECT") ;
	wgThis->ximServer.m_atSkkComm		= MakeAtom ("_SKKINPUT_COMM") ;
#undef MakeAtom
	wgThis->ximServer.m_lstHotKeyTrigger	= NULL ;
	wgThis->ximServer.m_nHotKeyTrigger		= 0 ;
	return ;
}

void
ximServer_onRealize (
	register Widget					gw,
	register XtValueMask*			pValueMask,
	register XSetWindowAttributes*	pXSWA)
{
	register Display*			pDisplay	= XtDisplay (gw) ;
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register CoreWidgetClass	super	= (CoreWidgetClass) XtClass (gw)->core_class.superclass ;

	(*super->core_class.realize)(gw, pValueMask, pXSWA) ;

	XSetSelectionOwner (pDisplay, wgThis->ximServer.m_atServer, XtWindow (gw), CurrentTime) ;
	if (XGetSelectionOwner (pDisplay, wgThis->ximServer.m_atServer) != XtWindow (gw)) {
#if defined (DEBUG)
		fprintf (stderr, "ximServer_onRealize() : failed.\n") ;
#endif
		XtDestroyWidget (gw) ;
		return ;
	}
	ximServer_setServerProfile (gw) ;
	ximServer_getStartConversionKey	(gw) ;
#if defined (DEBUG)
	fprintf (stderr, "ximServer_onRealize() : done.\n") ;
#endif
	return ;
}

Boolean
ximServer_onSetValues (
	register Widget		gwCurrent,
	register Widget		gwRequest,
	register Widget		gwNeww,
	register ArgList	args,
	register Cardinal*	num_args)
{
	return	False ;
}

void
ximServer_onDestroy (
	register Widget		gw)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;

	XtCallCallbacks (gw, XtNdestroyCallback, 0) ;
	ximServer_destroyAllClient (gw) ;

	if (wgThis->ximServer.m_lstHotKeyTrigger != NULL) {
		free (wgThis->ximServer.m_lstHotKeyTrigger) ;
		wgThis->ximServer.m_lstHotKeyTrigger	= NULL ;
	}
	return ;
}

void
ximServer_onSelectionClear (
	register Widget		gw,
	register XEvent*	pXEvent,
	register String*	params,
	register Cardinal*	num_params)
{
	register XIMServerWidget		wgThis	= (XIMServerWidget) gw ;
	register XSelectionClearEvent*	pEvent	= &pXEvent->xselectionclear ;

#if defined (DEBUG)
	fprintf (stderr, "ximServer_onSelectionClear ()\n") ;
#endif
	if (pEvent->selection != wgThis->ximServer.m_atServer)
		return ;

	XtDestroyWidget (gw) ;
	return ;
}

void
ximServer_onSelectionRequest (
	register Widget		gw,
	register XEvent*	pXEvent,
	register String*	params,
	register Cardinal*	num_params)
{
	register XIMServerWidget			wgThis		= (XIMServerWidget) gw ;
	register XSelectionRequestEvent*	pEvent		= &pXEvent->xselectionrequest ;
	register Display*					pDisplay	= XtDisplay (gw) ;
	register unsigned char*		pValue ;
	register int				iLength ;
	register int				iFormat ;
	XEvent				xev ;
	Atom				atTarget ;
	Atom				atTargets [2] ;

#if defined (DEBUG)
	{
		char*	pSelection	= XGetAtomName (pDisplay, pEvent->selection) ;
		char*	pAtomName	= XGetAtomName (pDisplay, pEvent->target) ;

		fprintf (stderr, "ximServer_onSelectionRequest: ") ;
		if (pSelection != NULL) {
			fprintf (stderr, "Selection: \"%s\", ", pSelection) ;
			XFree (pSelection) ;
		}
		if (pAtomName != NULL) {
			fprintf (stderr, "Target: \"%s\"\n", pAtomName) ;
			XFree (pAtomName) ;
		}
	}
#endif
	if (pEvent->selection != wgThis->ximServer.m_atServer)
		return ;

	pValue	= NULL ;
	iLength	= 0 ;
	if (pEvent->target == XInternAtom (pDisplay, "TARGETS", False)){
		atTargets [0]	= wgThis->ximServer.m_atLocales ;
		atTargets [1]	= wgThis->ximServer.m_atTransport ;
		pValue			= (unsigned char*)&atTargets ;
		iLength		= 2 ;
		iFormat		= 32 ;
		atTarget		= XA_ATOM ;
	} else if (pEvent->target == wgThis->ximServer.m_atLocales){
		static char*	sstrLocales	= NULL ;
		static int		snstrLocales ;

		if (sstrLocales == NULL) {
			static const char	sstrAtLocal []	= "@locale=" ;
			register int	nNeed ;
			nNeed		= strlen (wgThis->ximServer.m_strLocales) + sizeof (sstrAtLocal) / sizeof (sstrAtLocal [0]) + 1 ;
			sstrLocales	= malloc (nNeed + 1) ;
			assert (sstrLocales != NULL) ;
			strcpy (sstrLocales, sstrAtLocal) ;
			strcat (sstrLocales, wgThis->ximServer.m_strLocales) ;
			snstrLocales	= strlen (sstrLocales) ;
		}
		pValue		= sstrLocales ;
		iLength		= snstrLocales ;
		iFormat		= 8 ;
		atTarget	= pEvent->target ;
	} else if (pEvent->target == wgThis->ximServer.m_atTransport){
		static char	rchTransport []	= "@transport=X/" ;
		pValue		= rchTransport ;
		iLength		= sizeof (rchTransport) / sizeof (rchTransport [0]) ;
		iFormat		= 8 ;
		atTarget	= pEvent->target ;
	} else {
#if defined (DEBUG)
		fprintf (stderr, "ximServer_onSelectionRequest: invalid request\n") ;
#endif
		return ;
	}
	if (pValue == NULL || iLength <= 0)
		return ;

	XChangeProperty (pDisplay, pEvent->requestor, pEvent->property, atTarget, iFormat, PropModeReplace, pValue, iLength) ;
	xev.xselection.type			= SelectionNotify ;
	xev.xselection.selection	= pEvent->selection ;
	xev.xselection.target		= atTarget ;
	xev.xselection.property		= pEvent->property ;
	xev.xselection.requestor	= pEvent->requestor ;
	xev.xselection.send_event	= True ;
	xev.xselection.time			= CurrentTime ;
	xev.xselection.display		= pDisplay ;
	XSendEvent (pDisplay, pEvent->requestor, False, 0L, &xev) ;
	return ;
}

void
ximServer_onClientMessage (
	register Widget		gw,
	register XEvent*	pXEvent,
	register String*	params,
	register Cardinal*	num_params)
{
	register XIMServerWidget		wgThis	= (XIMServerWidget) gw ;
	register XClientMessageEvent*	pEvent	= (XClientMessageEvent*)&pXEvent->xclient ;
	register Widget					wgClient ;
	register Window					wndClient ;
	XEvent							xev ;

	if (pEvent->display      != XtDisplay (gw) || 
		pEvent->window       != XtWindow  (gw) ||
		pEvent->message_type != wgThis->ximServer.m_atXConnect ||
		pEvent->format       != 32)
		return ;

	wndClient	= (Window) pEvent->data.l [0] ;
#if defined (DEBUG)
	fprintf (stderr, "ximServer_onConversionRequest (window:%lx)\n", wndClient) ;
#endif
	if (!ximServer_isValidWindow (XtDisplay (gw), wndClient))
		return ;
	wgClient		= ximServer_findClient (gw, wndClient) ;
	if (wgClient == NULL) {
		wgClient	= ximServer_newClient (gw, wndClient) ;
	}
	assert (wgClient != NULL) ;
	xev.xclient.type			= ClientMessage ;
	xev.xclient.window			= wndClient ;
	xev.xclient.message_type	= wgThis->ximServer.m_atXConnect ;
	xev.xclient.format			= 32 ;
	xev.xclient.data.l [0]		= XtWindow (wgClient) ;
	xev.xclient.data.l [1]		= XIM_SERVERMAJOR_TRANSPORT_VERSION ;
	xev.xclient.data.l [2]		= XIM_SERVERMINOR_TRANSPORT_VERSION ;
	xev.xclient.data.l [3]		= XTRANSPORT_DIVIDING_SIZE ;
	XSendEvent (XtDisplay (gw), wndClient, False, NoEventMask, &xev) ;
	return ;
}

void
ximServer_onClientDestroy (
	register Widget		gw,
	register XtPointer	closure,
	register XtPointer	call_data)
{
	register Widget		wgServer	= (Widget) closure ;
	ximServer_removeClient (wgServer, call_data) ;
	return ;
}

Boolean
ximServer_setServerProfile (
	register Widget		gw)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register Display*		pDisplay ;
	register Atom			atServers ;
	register Atom			atServer ;
	register int			iOperation ;
	register Boolean		fNoRegistration ;
	unsigned long			lData ;

	assert (gw != NULL) ;

	pDisplay		= XtDisplay (gw) ;
	atServers		= XInternAtom (pDisplay, "XIM_SERVERS", False) ;
	atServer		= wgThis->ximServer.m_atServer ;
	iOperation		= PropModePrepend ;
	fNoRegistration	= False ;

	{
		Atom		atType ;
		int			iFormat ;
		unsigned long	ulItems ;
		unsigned long	ulBytesAfter ;
		unsigned char*	pValue ;

		/* まず、何か属性が設定されているかどうかを読む。*/
		if (XGetWindowProperty (pDisplay, DefaultRootWindow (pDisplay), atServers, 0L, 1024L, False, AnyPropertyType, &atType, &iFormat, &ulItems, &ulBytesAfter, &pValue) == Success){
			if (atType != XA_ATOM || iFormat != 32){
				iOperation	= PropModeReplace ;
			} else {
				Atom*	pAtom	= (Atom*)pValue ;
				while (ulItems > 0){
					if (*pAtom == wgThis->ximServer.m_atServer){
						fNoRegistration	= True ;
						break ;
					}
					pAtom	++ ;
					ulItems	-- ;
				}
			}
			if (pValue)
				XFree (pValue) ;
		}
	}
	lData = wgThis->ximServer.m_atServer ;
	if (!fNoRegistration){
		XChangeProperty (pDisplay, DefaultRootWindow (pDisplay), atServers, XA_ATOM, 32, iOperation, (unsigned char *)&lData, 1) ;
	} else {
		XChangeProperty (pDisplay, DefaultRootWindow (pDisplay), atServers, XA_ATOM, 32, PropModeAppend, (unsigned char*)&lData, 0) ;
	}
	return	True ;
}

/*
 * Resource を読んで、変換開始/終了を切り替えるキーを見切る関数…。
 *-----
 * なのだが、実のところ機能していない。
 */
static void
ximServer_getStartConversionKey (
	register Widget		gw)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register int				num ;
	register struct XrLikeKey*	pStartKeys ;

	/* まず、何個用意しないといけないのかを数える。*/
	num = parseXrLikeKeyStrings (wgThis->ximServer.m_strConvStartKeys, NULL) ;
	if( num <= 0 ){
		wgThis->ximServer.m_lstHotKeyTrigger	= NULL ;
		wgThis->ximServer.m_nHotKeyTrigger		= 0 ;
		return ;
	}
	
	/* メモリを確保する。*/
	pStartKeys = malloc (sizeof (struct XrLikeKey) * num) ;
	if (pStartKeys == NULL ){
		wgThis->ximServer.m_lstHotKeyTrigger	= NULL ;
		wgThis->ximServer.m_nHotKeyTrigger		= 0 ;
		return ;
	}
	/* 今度は実際にキーを読み、確保する。*/
	num	= parseXrLikeKeyStrings (wgThis->ximServer.m_strConvStartKeys, pStartKeys) ;
	/* 登録する。*/
	wgThis->ximServer.m_lstHotKeyTrigger	= pStartKeys ;
	wgThis->ximServer.m_nHotKeyTrigger		= num ;
	return ;
}

Widget
ximServer_newClient (
	register Widget		gw,
	register Window		wndClient)
{
	register XIMServerWidget	wgThis		= (XIMServerWidget) gw ;
	Arg							rArg [8] ;
	Widget						wgClient ;
	register int				nArg ;

	nArg	= 0 ;
	XtSetArg (rArg [nArg], XtNmappedWhenManaged, False) ;	nArg ++ ;
	XtSetArg (rArg [nArg], XtNclientWindow, wndClient) ;	nArg ++ ;
	XtSetArg (rArg [nArg], XtNprotocolWidget, gw) ; nArg ++ ;
	wgClient	= XtCreateWidget ("ximClient", ximClientWidgetClass, gw, rArg, nArg) ;
	if (wgClient == NULL)
		return	NULL ;
	if (! TVarbuffer_Add (&wgThis->ximServer.m_vbufClient, &wgClient, 1)) {
		XtDestroyWidget (wgClient) ;
		return	NULL ;
	}
	XtAddCallback (wgClient, XtNdestroyCallback, ximServer_onClientDestroy, gw) ;
	XtRealizeWidget (wgClient) ;
	return	wgClient ;
}

void
ximServer_removeClient (
	register Widget		gw,
	register Widget		gwClient)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register Widget*			pClient ;
	register int				i, nClient ;

	pClient	= TVarbuffer_GetBuffer (&wgThis->ximServer.m_vbufClient) ;
	nClient	= TVarbuffer_GetUsage  (&wgThis->ximServer.m_vbufClient) ;
	for (i = 0 ; i < nClient ; i ++) {
		if (*pClient == gwClient) {
			*pClient	= *(pClient + nClient - 1) ;
			TVarbuffer_Sub (&wgThis->ximServer.m_vbufClient, 1) ;
			XtRemoveAllCallbacks (gwClient, XtNdestroyCallback) ;
			return ;
		}
		pClient	++ ;
	}
	return ;
}

Widget
ximServer_findClient (
	register Widget		gw,
	register Window		wndClient)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register Widget*			pClient ;
	register int				nClient ;

	pClient	= TVarbuffer_GetBuffer (&wgThis->ximServer.m_vbufClient) ;
	nClient	= TVarbuffer_GetUsage  (&wgThis->ximServer.m_vbufClient) ;
	while (nClient -- > 0) {
		if (XtWindow (*pClient) == wndClient)
			return	*pClient ;
		pClient	++ ;
	}
	return	NULL ;
}

void
ximServer_destroyAllClient (
	register Widget		gw)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;
	register Widget*			pClient ;
	register int				nClient ;

	pClient	= TVarbuffer_GetBuffer (&wgThis->ximServer.m_vbufClient) ;
	nClient	= TVarbuffer_GetUsage  (&wgThis->ximServer.m_vbufClient) ;
	/*	remoteClient が呼び出されないよう注意する。*/
	while (nClient -- > 0) {
		XtRemoveAllCallbacks (*pClient, XtNdestroyCallback) ;
		XtDestroyWidget (*pClient ++) ;
	}
	TVarbuffer_Uninitialize (&wgThis->ximServer.m_vbufClient) ;
	return ;
}

Boolean
ximServer_isValidWindow (
	register Display*	pDisplay,
	register Window		wnd)
{
	XWindowAttributes	xwa ;

	/*	ここで XError を一度止めておいてから、XGetWindowAttributes して、
	 *	その返り値を見る。今は XErrorHandler の操作部分がない…。
	 */
	return	XGetWindowAttributes (pDisplay, wnd, &xwa) ;
}

int
XIMServer_GetConversionStartKey (
	register Widget						gw,
	register const struct XrLikeKey**	ppHotKey)
{
	register XIMServerWidget	wgThis	= (XIMServerWidget) gw ;

	assert (gw != NULL) ;

	if (XtClass (gw) != ximServerWidgetClass)
		return	0 ;

	if (ppHotKey != NULL) 
		*ppHotKey	= wgThis->ximServer.m_lstHotKeyTrigger ;

	return	wgThis->ximServer.m_nHotKeyTrigger ;
}

