// An X input method.
//

#include <stdio.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Ximd/IMdkit.h>
#include <X11/Ximd/Xi18n.h>
#include <IC.h>
	void DestroyIC(IMChangeICStruct *);
	void CreateIC(IMChangeICStruct *);
	void CreateIC(IMChangeICStruct *);
	void SetIC(IMChangeICStruct *);
	void GetIC(IMChangeICStruct *);
#include <convert.h>

// { constants and predeclared arrays

#	define BUFSIZE 100 // big enough for hostname and lookahead buffers.
#	define MYPORT 5432
#	define DEFAULT_LOCALE "" // "C" can lead to core dump in client on close

	/* Supported Inputstyles */
	static XIMStyle Styles[] = {
		/* Problems with the Ximdkit for some of these: 
		XIMPreeditArea|XIMStatusArea,
		XIMPreeditCallbacks|XIMStatusCallbacks,
		XIMPreeditPosition|XIMStatusArea,
		XIMPreeditPosition|XIMStatusNothing,
		/* */
		XIMPreeditNothing|XIMStatusNothing,
		0
	};

	/* Trigger Keys List */
	static XIMTriggerKey Trigger_Keys[] = {
		{XK_space, ShiftMask, ShiftMask}, // traditional to trigger on S-<space>
		{0L, 0L, 0L}
	};

	static XIMEncoding Encodings[] = { // standard default
		"COMPOUND_TEXT",
		"UTF-8",
		NULL
	};

	char *myIMName = "keypress";
// constants }

// { global variables

	Display *myDisplay;
	Window myRoot, myWindow;
	XIMStyles myXIMStyle;
	XIMTriggerKeys myXIMTriggerKeys;
	XIMEncodings myXIMEncodings;
	XIMS myIMS;

// global variables }

// { string-conversion routines

int UTF8toUCS2string(unsigned const char *utf8String, wchar_t *UCS2string,
		int MaxLength) {
	// returns number of characters converted
	wchar_t Answer;
	int returnVal = 0;
	while (*utf8String) {
		if (*utf8String & 0200) {
			if (*utf8String & 040) { // 3-byte code
				Answer = ((*utf8String++) & 017) << 12; // upper 4 bits
				Answer |= ((*utf8String++) & 077) << 6; // next 6 bits
				Answer |= ((*utf8String++) & 077); // next 6 bits
			} else { // 2-byte code
				Answer = ((*utf8String++) & 037) << 6; // upper 5 bits
				Answer |= ((*utf8String++) & 077); // next 6 bits
			}
		} else {
			Answer = (*utf8String++);
		}
		*UCS2string++ = Answer;
		returnVal += 1;
		if (returnVal >= MaxLength) return MaxLength;
	} // while (*utf8String)
	*UCS2string = 0;
	return(returnVal);
} /* UTF8toUCS2string */

// string-conversion routines }

void initializeDisplay() {
	char *displayName;
	// get myDisplay
	displayName = getenv("DISPLAY");
	if (displayName == NULL) displayName = "unix:0";
	myDisplay = XOpenDisplay(displayName);
	if (!myDisplay) {
		fprintf(stderr, "Cannot open %s\n", displayName);
		exit(1);
	}
	myRoot = DefaultRootWindow(myDisplay);
	myWindow = XCreateSimpleWindow(myDisplay, myRoot, 40, 40, 50, 50,
		0, WhitePixel(myDisplay, DefaultScreen(myDisplay)),
		WhitePixel(myDisplay, DefaultScreen(myDisplay)));
	if (myWindow == (Window) NULL) {
		fprintf(stderr, "Can't create myWindow\n");
		exit(1);
	}
	XSelectInput(myDisplay, myWindow, ButtonPressMask);
	// XMapWindow(myDisplay, myWindow); // not necessary for proper function
	XSync(myDisplay, 0);
} // initializeDisplay

/* the following from sampleIM.c */
int IsKey(XIMS ims, IMForwardEventStruct *call_data, XIMTriggerKey *trigger) {
    char strbuf[BUFSIZE];
    KeySym keysym;
    int i;
    int modifier;
    int modifier_mask;
    XKeyEvent *kev;

    memset(strbuf, 0, BUFSIZE);
    kev = (XKeyEvent*)&call_data->event;
    XLookupString(kev, strbuf, BUFSIZE, &keysym, NULL);
    for (i = 0; trigger[i].keysym != 0; i++) {
	modifier      = trigger[i].modifier;
	modifier_mask = trigger[i].modifier_mask;
	if (((KeySym)trigger[i].keysym == keysym)
	    && ((kev->state & modifier_mask) == modifier))
	  return True;
    }
    return False;
} // IsKey

IMForwardEventStruct lastCallStruct;

wchar_t queue[BUFSIZE]; // queue of staged UCS-2 characters
wchar_t *queueFront, *queueBack; // add new ones at queueFront+1; emit ones
	// at queueBack.  empty == (queueFront+1 == queueBack)

void handleForward(XIMS ims, IMForwardEventStruct *call_data) {
	char strbuf[BUFSIZE];
	XKeyEvent *kev;
	KeySym keysym;
	/* This code modified (raphael) from IC.c */
	lastCallStruct = *call_data;
    if (call_data->event.type != KeyPress) {
        fprintf(stderr, "bogus event type, ignored\n");
    	return;
    }
	/* figure out what key was given to us */
	memset(strbuf, 0, BUFSIZE);
	kev = (XKeyEvent *) &call_data->event;
	XLookupString(kev, strbuf, BUFSIZE, &keysym, NULL);
	if (strbuf[0] == 0) {
		fprintf(stderr, "Got a non-printing keysym 0x%lx\n", keysym);
		queueFront += 1;
		*queueFront = keysym;
		return; // no need to handle special chars any further.
	}
	fprintf(stderr, "Got printing character [%s]\n", strbuf);
	keypressConvert(strbuf[0]);
	return;
} // handleForward

void initializeQueue() {
	queueBack = queue;
	queueFront = queueBack - 1;
} // initializeQueue

void enqueue(const char *string) {
	// place on queue for output, but don't commit
	int length, room, index;
	room = BUFSIZE-(queueFront+1-queueBack);
	length = UTF8toUCS2string(string, queueFront+1, room);
	// fprintf(stderr, "enqueueing %d chars at slot %d\n", length,
	//		queueFront+1-queue);
	if (length == (size_t) room) {
		fprintf(stderr,
			"Did not complete conversion; only %d chars converted\n",
				length);
		exit(1);
	}
	for (index = 0; index < length; index += 1) { // convert from UTF to keysym
		queueFront += 1;
		*queueFront = (*queueFront == '\b') ?  XK_BackSpace :
			(0x01000000 | *queueFront);
	}
} // enqueue

void emit(){
	// emit one character from queue if not empty.
	IMCommitStruct result_data;
	// fprintf(stderr, "emitting 1 char at slot %d\n", queueBack-queue);
	memcpy(&result_data, &lastCallStruct, sizeof(result_data));
		// initialize common fields
	result_data.flag = XimLookupKeySym;
	result_data.commit_string = "";
	result_data.keysym = *queueBack;
	fprintf(stderr, "committing 0x%lx\n", result_data.keysym);
	IMCommitString(myIMS, (XPointer)&result_data);
	queueBack += 1;
	if (queueFront+1 == queueBack) { // empty; reset to start
		// fprintf(stderr, "resetting queue\n");
		initializeQueue();
		return;
	}
} // emit

int myProtocolHandler(XIMS ims, XPointer call_data) {
	// fprintf(stdout, "myProtocolHandler called\n");
	switch (((IMProtocol *) call_data)->major_code) {
		case XIM_OPEN:
			fprintf(stderr, "XIM_OPEN; lang %s; connection %d\n",
				((IMOpenStruct *) call_data)->lang.name,
				((IMOpenStruct *) call_data)->connect_id);
			initializeConversion();
			initializeQueue();
			break;
		case XIM_CLOSE:
			fprintf(stderr, "XIM_CLOSE; connection %d\n",
				((IMCloseStruct *) call_data)->connect_id);
			break;
		case XIM_CREATE_IC: {
			IMChangeICStruct *theParams = (IMChangeICStruct *) call_data;
			fprintf(stderr, "XIM_CREATE_IC: icid %d\n", theParams->icid);
			CreateIC((IMChangeICStruct *)call_data);
			break;
		}
		case XIM_DESTROY_IC:
			fprintf(stderr, "XIM_DESTROY_IC.\n");
			DestroyIC((IMChangeICStruct *)call_data);
			break;
		case XIM_SET_IC_VALUES: {
			IMChangeICStruct *theParams = (IMChangeICStruct *) call_data;
			fprintf(stderr, "XIM_SET_IC_VALUES: icid %d\n", theParams->icid);
			SetIC((IMChangeICStruct *)call_data);
			while (queueBack < queueFront+1)
				emit(); // we can send one more character
			break;
		}
		case XIM_GET_IC_VALUES: {
			IMChangeICStruct *theParams = (IMChangeICStruct *) call_data;
			fprintf(stderr, "XIM_GET_IC_VALUES: icid %d\n", theParams->icid);
			GetIC((IMChangeICStruct *)call_data);
			break;
		}
		case XIM_FORWARD_EVENT:
			fprintf(stderr, "XIM_FORWARD_EVENT\n");
			handleForward(ims, (IMForwardEventStruct*) call_data);
			while (queueBack < queueFront+1)
				emit(); // send one character, if reasonable
			break;
		case XIM_SET_IC_FOCUS:
			fprintf(stderr, "XIM_SET_IC_FOCUS\n"); break;
		case XIM_UNSET_IC_FOCUS:
			fprintf(stderr, "XIM_UNSET_IC_FOCUS\n"); break;
		case XIM_RESET_IC:
			fprintf(stderr, "XIM_RESET_IC_FOCUS\n"); break;
		case XIM_TRIGGER_NOTIFY: {
			IMTriggerNotifyStruct *theParams =
				(IMTriggerNotifyStruct *) call_data;
			fprintf(stderr, "XIM_TRIGGER_NOTIFY: key index %ld, flag %ld\n",
				theParams->key_index, theParams->flag);
			initializeQueue();
			break;
		}
		case XIM_PREEDIT_START_REPLY:
			fprintf(stderr, "XIM_PREEDIT_START_REPLY\n"); break;
		case XIM_PREEDIT_CARET_REPLY:
			fprintf(stderr, "XIM_PREEDIT_CARET_REPLY\n"); break;
		default:
			fprintf(stderr, "Unknown IMDKit Protocol message type\n"); break;
	} // switch			
	return(True);
} // myProtocolHandler

void initializeXIM() {
	char hostname[BUFSIZE], transport[BUFSIZE];
	long myFilterMask = KeyPressMask;
	char *loc;
	myXIMStyle.count_styles = sizeof(Styles)/sizeof(XIMStyle) - 1;
	myXIMStyle.supported_styles = Styles;
	myXIMTriggerKeys.count_keys =
		sizeof(Trigger_Keys)/sizeof(XIMTriggerKey) - 1;
	myXIMTriggerKeys.keylist = Trigger_Keys;
	myXIMEncodings.count_encodings =
		sizeof(Encodings)/sizeof(XIMEncoding) - 1;
	myXIMEncodings.supported_encodings = Encodings;
	gethostname(hostname, BUFSIZE);
	// sprintf(transport, "tcp/%s:%d", hostname, MYPORT);
	// sprintf(transport, "local/%s:/tmp/.ximsock", hostname);
	sprintf(transport, "X/");
	loc = "C";
	fprintf(stdout, "locale is %s\n", loc);
	myIMS = IMOpenIM(
		myDisplay,
		IMModifiers, "Xi18n",
		IMServerWindow, myWindow,
		IMServerName, myIMName,
		IMLocale, loc,
		IMServerTransport, transport,
		IMInputStyles, &myXIMStyle,
		IMProtocolHandler, myProtocolHandler,
		IMOnKeysList, &myXIMTriggerKeys,
		IMOffKeysList, &myXIMTriggerKeys, // static event flow: register OFF
		IMEncodingList, &myXIMEncodings,
		IMFilterEventMask, myFilterMask,
		NULL);
	if (myIMS == (XIMS) NULL) {
		fprintf(stderr, "Can't Open Input Method Service named %s address %s\n",
			myIMName, transport);
		exit(1);
	}
	XFlush(myDisplay);
} // initializeXIM

void loop() {
	for (;;) {
		XEvent theEvent;
		// fprintf(stderr, "at top of loop\n");
		XNextEvent(myDisplay, &theEvent);
		if (XFilterEvent(&theEvent, None) == True) {
			fprintf(stderr, "filter says true; event type %d\n",
				theEvent.type);
			continue;
		}
		switch (theEvent.type) {
			case SelectionRequest:
				fprintf(stdout, "Selection Request\n");
				break;
			case ClientMessage:
				fprintf(stdout, "Client message\n");
				break;
			case DestroyNotify:
			case ButtonPress:
				XDestroyWindow(theEvent.xbutton.display, myWindow);
				exit(0);
			default:
				fprintf(stdout, "Got unknown local event; ignoring\n");
		}
	}
} // loop

void _XimdXTransOpenCOTSServer() {
	fprintf(stdout, "_XimdXTransOpenCOTSServer called\n");
	exit(1);
}

void _XimdXTransAccept() {
	fprintf(stdout, "_XimdXTransAccept called\n");
	exit(1);
}

void _XimdXTransCreateListener() {
	fprintf(stdout, "_XimdXTransCreateListener called\n");
	exit(1);
}

int main(argc, argv) {
	initializeDisplay();
	initializeXIM();
	loop();
	return(0);
} // main


