/* Copyright (C) 2002 Mickael Marchand <marchand@kde.org>

	 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 of the License, 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 this program; see the file COPYING.  If not, write to
	 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
	 Boston, MA 02111-1307, USA.
	 */
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

#include "xvim.h"
static int	got_x_error;

/*
 * Another X Error handler, just used to check for errors.
 */
/* ARGSUSED */
static int x_error_check(Display *, XErrorEvent *)
{
	got_x_error = TRUE;
	return 0;
}

XVim::XVim() {
	registryProperty = None;
	commProperty = None;
	commWindow = None;
	got_x_error = FALSE;
}

XVim::~XVim() {

}

//    Display	*dpy;			/* Where to send. */
//    char	*name;			/* Where to send. */
//    char	*cmd;			/* What to send. */
//    int		asKeys;			/* Interpret as keystrokes or expr ? */
//    int		*code;			/* Return code. 0 => OK */
char * XVim::sendToVim(Display *dpy, const char *name, const char *cmd, int asKeys,int *code)
{
	Window	    w;
	Atom	    *plist;
	XErrorHandler   old_handler;
	char	    *property, staticSpace[STATIC_SPACE];
	int		    length;
	int		    res;
	static int	    serial = 0;	/* Running count of sent commands.
															 * Used to give each command a
															 * different serial number. */
	XEvent	    event;
	XPropertyEvent  *e = (XPropertyEvent *)&event;
	time_t	    start;
	char	    *result;
	char	    *loosename = NULL;

	if (commProperty == None && dpy != NULL)
	{
		if (SendInit(dpy) < 0) {
			*code = -1;
			return NULL;
		}
	}

	/*
	 * Bind the server name to a communication window.
	 *
	 * Find any survivor with a serialno attached to the name if the
	 * original registrant of the wanted name is no longer present.
	 *
	 * Delete any lingering names from dead editors.
	 */

	old_handler = XSetErrorHandler(x_error_check);
	while (TRUE)
	{
		got_x_error = FALSE;
		w = LookupName(dpy, name, 0, &loosename);
		/* Check that the window is hot */
		if (w != None)
		{
			plist = XListProperties(dpy, w, &res);
			XSync(dpy, False);
			if (plist != NULL)
				XFree(plist);
			if (got_x_error)
			{
				LookupName(dpy, loosename ? loosename : name, /*DELETE=*/TRUE, NULL);
				continue;
			}
		}
		break;
	}
	if (w == None)
	{
		fprintf(stderr, "no registered server named %s\n", name);
		*code=-1;
		return NULL;
	}
	else if (loosename != NULL)
		name = loosename;

	/*
	 * Send the command to target interpreter by appending it to the
	 * comm window in the communication window.
	 */

	length = strlen(name) + strlen(cmd) + 10;
	if (length <= STATIC_SPACE)
		property = staticSpace;
	else
		property = (char *) malloc((unsigned) length);

	serial++;
	sprintf(property, "%c%c%c-n %s%c-s %s", 0, asKeys ? 'k' : 'c', 0, name, 0, cmd);
	if (name == loosename)
		free(loosename);
	if (!asKeys)
	{
		/* Add a back reference to our comm window */
		sprintf(property + length, "%c-r %x %d", 0, (uint) commWindow, serial);
		length += strlen(property + length + 1) + 1;
	}

	res = AppendPropCarefully(dpy, w, commProperty, property, length + 1);
	if (length > STATIC_SPACE)
		free(property);
	if (res < 0)
	{
		fprintf(stderr, "Failed to send command to the destination program\n");
		*code=-1;
		return NULL;
	}

	if (asKeys) /* There is no answer for this - Keys are sent async */
		return NULL;

	/*
	 * Enter a loop processing X events & pooling chars until we see the result
	 */

	time(&start);
	while ((time((time_t *) 0) - start) < 60)
	{
		/* Look out for the answer */
#ifndef HAVE_SELECT
		struct pollfd   fds;

		fds.fd = ConnectionNumber(dpy);
		fds.events = POLLIN;
		if (poll(&fds, 1, SEND_MSEC_POLL) < 0)
			break;
#else
		fd_set	    fds;
		struct timeval  tv;

		tv.tv_sec = 0;
		tv.tv_usec =  SEND_MSEC_POLL * 1000;
		FD_ZERO(&fds);
		FD_SET(ConnectionNumber(dpy), &fds);
		if (select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv) < 0)
			break;
#endif
		while (XEventsQueued(dpy, QueuedAfterReading) > 0)
		{
			XNextEvent(dpy, &event);
			if (event.type == PropertyNotify && e->window == commWindow)
				if ((result = SendEventProc(dpy, &event, serial, code)) != NULL)
					return result;
		}
	}
	*code=-1;
	return NULL;
}

/*
 * SendInit --
 *	This procedure is called to initialize the
 *	communication channels for sending commands and
 *	receiving results.
 */

int XVim::SendInit(Display *dpy) {
	XErrorHandler old_handler;

	/*
	 * Create the window used for communication, and set up an
	 * event handler for it.
	 */
	old_handler = XSetErrorHandler(x_error_check);
	got_x_error = FALSE;

	commProperty = XInternAtom(dpy, "Comm", False);
	/* Change this back to "InterpRegistry" to talk to tk processes */
	registryProperty = XInternAtom(dpy, "VimRegistry", False);

	if (commWindow == None)
	{
		commWindow = XCreateSimpleWindow(dpy, XDefaultRootWindow(dpy),
				getpid(), 0, 10, 10, 0,
				WhitePixel(dpy, DefaultScreen(dpy)),
				WhitePixel(dpy, DefaultScreen(dpy)));
		XSelectInput(dpy, commWindow, PropertyChangeMask);
	}

	XSync(dpy, False);
	(void) XSetErrorHandler(old_handler);

	return got_x_error ? -1 : 0;
}


/*
 * LookupName --
 *	Given an interpreter name, see if the name exists in
 *	the interpreter registry for a particular display.
 *
 * Results:
 *	If the given name is registered, return the ID of
 *	the window associated with the name.  If the name
 *	isn't registered, then return 0.
 */
//    Display *dpy;	/* Display whose registry to check. */
//    char *name;		/* Name of an interpreter. */
//    int delete;		/* If non-zero, delete info about name. */
//    char **loose;	/* Do another search matching -999 if not found
/*			   Return result here if a match is found */
Window XVim::LookupName(Display *dpy,const char* name, int del, char** loose)
{
	unsigned char   *regProp, *entry;
	unsigned char   *p;
	int		    result, actualFormat;
	unsigned long   numItems, bytesAfter;
	Atom	    actualType;
	Window	    returnValue;

	/*
	 * Read the registry property.
	 */

	regProp = NULL;
	result = XGetWindowProperty(dpy, RootWindow(dpy, 0), registryProperty, 0,
			MAX_PROP_WORDS, False, XA_STRING, &actualType, &actualFormat, &numItems, &bytesAfter,
			&regProp);

	if (actualType == None)
		return 0;

	/*
	 * If the property is improperly formed, then delete it.
	 */

	if ((result != Success) || (actualFormat != 8) || (actualType != XA_STRING))
	{
		if (regProp != NULL)
			XFree(regProp);
		XDeleteProperty(dpy, RootWindow(dpy, 0), registryProperty);
		return 0;
	}

	/*
	 * Scan the property for the desired name.
	 */

	returnValue = None;
	entry = NULL;	/* Not needed, but eliminates compiler warning. */
	for (p = regProp; (unsigned)(p - regProp) < numItems; )
	{
		entry = p;
		while ((*p != 0) && (!isspace(*p)))
			p++;
		if ((*p != 0) && (strcasecmp(name, (char *)(p + 1)) == 0))
		{
			sscanf((const char*)entry, "%x", (uint*) &returnValue);
			break;
		}
		while (*p != 0)
			p++;
		p++;
	}

	if (loose != NULL && returnValue == None && !IsSerialName(name))
	{
		for (p = regProp; (unsigned)(p - regProp) < numItems; )
		{
			entry = p;
			while ((*p != 0) && (!isspace(*p)))
				p++;
			if ((*p != 0) && IsSerialName((char*)(p + 1))
					&& (strncmp(name, (char*)(p + 1), strlen(name)) == 0))
			{
				sscanf((const char*)entry, "%x", (uint*) &returnValue);
				*loose = strdup((char*)(p + 1));
				break;
			}
			while (*p != 0)
				p++;
			p++;
		}
	}

	/*
	 * Delete the property, if that is desired (copy down the
	 * remainder of the registry property to overlay the deleted
	 * info, then rewrite the property).
	 */

	if ((del) && (returnValue != None))
	{
		int count;

		while (*p != 0)
			p++;
		p++;
		count = numItems - (p-regProp);
		if (count > 0)
			memcpy(entry, p, count);
		XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING,
				8, PropModeReplace, regProp, (int) (numItems - (p-entry)));
		XSync(dpy, False);
	}

	XFree(regProp);
	return returnValue;
}

//    Display	   *dpy;
//    XEvent	    *eventPtr;		/* Information about event. */
//    int		    expected;		/* The one were waiting for */
//    int		    *code;              /* Return code. 0 => OK */
char * XVim::SendEventProc(Display* dpy, XEvent *eventPtr, int expected,int* code)
{
	unsigned char   *propInfo;
	unsigned char   *p;
	int		    result, actualFormat;
	int		    retCode;
	unsigned long   numItems, bytesAfter;
	Atom	    actualType;

	if ((eventPtr->xproperty.atom != commProperty) || (eventPtr->xproperty.state != PropertyNewValue))
		return NULL;

	/*
	 * Read the comm property and delete it.
	 */

	propInfo = NULL;
	result = XGetWindowProperty(dpy, commWindow, commProperty, 0,
			MAX_PROP_WORDS, True, XA_STRING, &actualType, &actualFormat, &numItems, &bytesAfter,
			&propInfo);

	/*
	 * If the property doesn't exist or is improperly formed
	 * then ignore it.
	 */

	if ((result != Success) || (actualType != XA_STRING) || (actualFormat != 8))
	{
		if (propInfo != NULL)
			XFree(propInfo);
		return NULL;
	}

	/*
	 * Several commands and results could arrive in the property at
	 * one time;  each iteration through the outer loop handles a
	 * single command or result.
	 */

	for (p = propInfo; (unsigned)(p - propInfo) < numItems; )
	{
		/*
		 * Ignore leading NULs; each command or result starts with a
		 * NUL so that no matter how badly formed a preceding command
		 * is, we'll be able to tell that a new command/result is
		 * starting.
		 */

		if (*p == 0)
		{
			p++;
			continue;
		}

		if ((*p == 'r') && (p[1] == 0))
		{
			int	    serial, gotSerial;
			unsigned char  *res=NULL;

			/*
			 * This is a reply to some command that we sent out.  Iterate
			 * over all of its options.  Stop when we reach the end of the
			 * property or something that doesn't look like an option.
			 */

			p += 2;
			gotSerial = 0;
			retCode = 0;
			while (((unsigned)(p-propInfo) < numItems) && (*p == '-'))
			{
				switch (p[1])
				{
					case 'r':
						if (p[2] == ' ')
							res = p + 3;
						break;
					case 's':
						if (sscanf((const char*)(p + 2), " %d", &serial) == 1)
							gotSerial = 1;
						break;
					case 'c':
						if (sscanf((const char*)(p + 2), " %d", &retCode) != 1)
							retCode = 0;
						break;
				}
				while (*p != 0)
					p++;
				p++;
			}

			if (!gotSerial)
				continue;

			if (code != NULL)
				*code = retCode;
			return serial == expected ? strdup((const char*)res) : NULL;
		}
		else
		{
			/*
			 * Didn't recognize this thing.  Just skip through the next
			 * null character and try again.
			 * Also, throw away commands that we cant process anyway.
			 */

			while (*p != 0)
				p++;
			p++;
		}
	}
	XFree(propInfo);
	return NULL;
}

/*
 * AppendPropCarefully --
 *
 *	Append a given property to a given window, but set up
 *	an X error handler so that if the append fails this
 *	procedure can return an error code rather than having
 *	Xlib panic.
 *
 *  Return:
 *	0 on OK - -1 on error
 *--------------------------------------------------------------
 */
//    Display *dpy;		/* Display on which to operate. */
//    Window window;		/* Window whose property is to
	/*			 * be modified. */
//    Atom property;		/* Name of property. */
//    char *value;		/* Characters  to append to property. */
//    int  length;		/* How much to append */
int XVim:: AppendPropCarefully(Display *dpy, Window window, Atom property,char * value,int length)
{
	XErrorHandler old_handler;

	old_handler = XSetErrorHandler(x_error_check);
	got_x_error = FALSE;
	XChangeProperty(dpy, window, property, XA_STRING, 8, PropModeAppend, (const unsigned char*)value, length);
	XSync(dpy, False);
	(void) XSetErrorHandler(old_handler);
	return got_x_error ? -1 : 0;
}

/*
 * Check if "str" looks like it had a serial number appended.
 * Actually just checks if the name ends in a digit.
 */
int XVim::IsSerialName(const char *str)
{
	int len = strlen(str);

	return (len > 1 && isdigit(str[len - 1]));
}

