/* +-------------------------------------------------------------------+ */
/* | Copyright 1993, David Koblas (koblas@netcom.com)                  | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <sys/time.h>

extern XImage *NewXImage(Display*,Visual*,int,int,int);

typedef void	(*func_t)(Widget, void *, XEvent *);

typedef struct li_s {
	/*
	**  Destroy request info
	*/
	void		*data;
	func_t		func;

	/*
	**  Info for watch cursor
	*/
	int		watchCount, computingCount;
	Widget		shell, parent;
	Window		win;
	Display		*dpy;
	struct li_s	*next;
} LocalInfo;

#define	DO_MAP(l)	((l)->watchCount + (l)->computingCount > 0)

#include "bitmaps/wait1.xbm"
#include "bitmaps/wait2.xbm"
#include "bitmaps/wait3.xbm"
#include "bitmaps/wait4.xbm"

static Atom		delwin = None, protocols = None;
static LocalInfo	*head = NULL;

static int		currentCursor = 0;
static Cursor		watchCursor = None;

static struct {
	Cursor		cursor;
	int		width, height;
	unsigned char	*bits;
} cursorInfo[] = {
	{ None, wait1_width, wait1_height, wait1_bits },
	{ None, wait2_width, wait2_height, wait2_bits },
	{ None, wait3_width, wait3_height, wait3_bits },
	{ None, wait4_width, wait4_height, wait4_bits },
};

static void initCursors(Widget w)
{
	static int	inited = False;
	Display		*dpy = XtDisplay(w);
	Screen		*screen = XtScreen(w);
	GC		gc = None;
	Pixmap		pix, mask;
	int		i, width, height;
	XImage		*src, *msk;
	int		x, y;
	XColor		xcols[2];

	if (inited)
		return;
	inited = True;

	xcols[0].pixel = BlackPixelOfScreen(screen);
	xcols[1].pixel = WhitePixelOfScreen(screen);

	XQueryColors(dpy, DefaultColormapOfScreen(screen), xcols, XtNumber(xcols));

	for (i = 0; i < XtNumber(cursorInfo); i++) {
		width = cursorInfo[i].width;
		height = cursorInfo[i].height;
		pix = XCreatePixmapFromBitmapData(dpy, RootWindowOfScreen(screen),
				(char*)cursorInfo[i].bits, width, height, True, False, 1);
		src = XGetImage(dpy, pix, 0, 0, width, height, AllPlanes, ZPixmap);
		msk = NewXImage(dpy, NULL, 1, width, height);
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++) {
				Boolean	flg = (Boolean)XGetPixel(src, x, y);

				if (!flg && x > 0)
					flg = XGetPixel(src, x - 1, y);
				if (!flg && x < width - 1)
					flg = XGetPixel(src, x + 1, y);
				if (!flg && y > 0)
					flg = XGetPixel(src, x, y - 1);
				if (!flg && y < height - 1)
					flg = XGetPixel(src, x, y + 1);
				XPutPixel(msk, x, y, flg);
			}
		}

		mask = XCreatePixmap(dpy, pix, width, height, 1);
		if (gc == None)
			gc = XCreateGC(dpy, mask, 0, 0);
		XPutImage(dpy, mask, gc, msk, 0, 0, 0, 0, width, height);

		XDestroyImage(src);
		XDestroyImage(msk);

		cursorInfo[i].cursor = XCreatePixmapCursor(dpy, pix, mask, 
				&xcols[0], &xcols[1], width / 2, height / 2);
		XFreePixmap(dpy, pix);
		XFreePixmap(dpy, mask);
	}
	if (gc != None)
		XFreeGC(dpy, gc);
}

static void destroyCallback(Widget w, XtPointer data, XtPointer junk)
{
	LocalInfo	*l = (LocalInfo *)data;
	LocalInfo	*cur, **pp;

	for (cur = *(pp = &head); cur != NULL && cur != l; cur = *(pp = &cur->next));

	if (cur == NULL)
		return;

	*pp = cur->next;
	
	XtFree((XtPointer)data);
}

static void handler(Widget w, XtPointer lArg, XEvent *eventArg, Boolean *junk)
{
	XClientMessageEvent	*event = (XClientMessageEvent*)eventArg;
	LocalInfo		*l = (LocalInfo *)lArg;
	if (event->type != ClientMessage)
		return;

	if (event->message_type == protocols && event->data.l[0] == delwin)
		l->func(w, l->data, eventArg);
}

static void add(Widget w, LocalInfo *l)
{
	unsigned long 		valuemask;
	XSetWindowAttributes	attributes;
	/*
	**  Set up the shell destroy info
	*/
	XtAddRawEventHandler(w, 0, True, handler, (XtPointer)l);
	XChangeProperty(XtDisplay(w), XtWindow(w), protocols, 
			XA_ATOM, 32, PropModeReplace, 
			(unsigned char *) &delwin, 1);

	/* 
	**  Now set up watch cursor information
	**
	**  Ignore device events while the busy cursor is displayed.
	*/
	valuemask = CWDontPropagate | CWCursor;
	attributes.do_not_propagate_mask =  (KeyPressMask | KeyReleaseMask |
			ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
	if (watchCursor == None)
		watchCursor = XCreateFontCursor(XtDisplay(w), XC_watch);
	attributes.cursor = watchCursor;

	/*
	** The window will be as big as the display screen, and clipped by
	** its own parent window, so we never have to worry about resizing
	*/
	l->win = XCreateWindow(XtDisplay(w), XtWindow(w), 0, 0,
			WidthOfScreen(XtScreen(w)), HeightOfScreen(XtScreen(w)), 
			(unsigned int) 0, 0, InputOnly,
			CopyFromParent, valuemask, &attributes);

	if (DO_MAP(l))
		XMapRaised(l->dpy, l->win);
}


static void realize(Widget w, XtPointer ldataArg, XEvent *event, Boolean *junk)
{
	if (event->type == MapNotify) {
		XtRemoveEventHandler(w, StructureNotifyMask, False, 
					realize, ldataArg);
		add(w, (LocalInfo*)ldataArg);
	}
}

void AddDestroyCallback(Widget w, func_t func, void *data)
{
	LocalInfo 	*l;

	if (delwin == None)
		delwin = XInternAtom(XtDisplay(w), "WM_DELETE_WINDOW", FALSE);
	if (protocols == None)
		protocols = XInternAtom(XtDisplay(w), "WM_PROTOCOLS", FALSE);

	initCursors(w);

	if (delwin == None || protocols == None)
		return;

	l = XtNew(LocalInfo);
	l->data  = data;
	l->func  = func;
	l->win   = None;
	l->dpy   = XtDisplay(w);
	l->watchCount = 0;
	l->computingCount = 0;
	l->shell = w;
	l->parent      = None;

	if (!XtIsRealized(w))
		XtAddEventHandler(w, StructureNotifyMask, False, realize, (XtPointer)l);
	else
		add(w, l);

	XtAddCallback(w, XtNdestroyCallback, destroyCallback, (XtPointer)l);

	l->next = head;
	head = l;
}

static Boolean setBusy(LocalInfo *cur, Boolean isWatch, Boolean flag)
{
	Boolean		doFlush = False;

	if (isWatch) {
		if (flag) {
			cur->watchCount++;
		} else {
			if (cur->watchCount == 0)
				return False;
			cur->watchCount--;
		}
	} else {
		if (flag) {
			cur->computingCount++;
		} else {
			if (cur->computingCount == 0)
				return False;
			cur->computingCount--;
		}
	}

	if (cur->win == None)
		return False;

	if (cur->watchCount == 0 && cur->computingCount == 0) {
		XUnmapWindow(cur->dpy, cur->win);
		return False;
	}

	if (cur->watchCount == 1) {
		if (isWatch) {
			XMapRaised(cur->dpy, cur->win);
			XDefineCursor(cur->dpy, cur->win, watchCursor);
			doFlush = True;
		}
	} else if (cur->computingCount == 1) {
		if (!isWatch) {
			XMapRaised(cur->dpy, cur->win);
			XDefineCursor(cur->dpy, cur->win, cursorInfo[currentCursor].cursor);
			doFlush = True;
		}
	}

	return doFlush;
}

void StateSetBusyWatch(Boolean flag)
{
	LocalInfo	*cur;
	Boolean		doFlush = False;

	for (cur = head; cur != NULL; cur = cur->next) 
		doFlush |= setBusy(cur, True, flag);

	if (doFlush)
		XFlush(head->dpy);
}

void StateSetBusy(Boolean flag)
{
	LocalInfo	*cur;
	Boolean		doFlush = False;

	for (cur = head; cur != NULL; cur = cur->next) 
		doFlush |= setBusy(cur, False, flag);

	if (doFlush)
		XFlush(head->dpy);
}

void StateShellBusy(Widget w, Boolean flag)
{
	LocalInfo	*cur;
	Boolean		doFlush = False;

	if (w == None)
		return;

	while (!XtIsShell(w))
		w = XtParent(w);

	for (cur = head; cur != NULL; cur = cur->next) {
		if (cur->shell == w) {
			doFlush |= setBusy(cur, True, flag);
		} else if (cur->parent == w) {
			StateShellBusy(cur->shell, flag);
		}
	}
	if (doFlush)
		XFlush(head->dpy);
}

void StateAddParent(Widget w, Widget parent)
{
	LocalInfo	*cur;

	if (w == None || parent == None)
		return;

	while (!XtIsShell(parent))
		parent = XtParent(parent);
	while (!XtIsShell(w))
		w = XtParent(w);

	for (cur = head; cur != NULL && cur->shell != w; cur = cur->next)
		;
	if (cur == NULL)
		return;

	cur->parent = parent;
}

void StateTimeStep()
{
	static Boolean		inited = False;
	static struct timeval	lastTime;
	struct timeval		nowTime;
	Boolean			done = False, need = False;
	Cursor			c;
	LocalInfo		*cur;

	for (cur = head; cur != NULL; cur = cur->next)
		if (cur->watchCount == 0 && cur->computingCount != 0 && cur->win != None)
			need = True;

	if (!need)
		return;

	gettimeofday(&nowTime, NULL);
	if (inited) {
		long	ds, dus;

		dus = nowTime.tv_usec - lastTime.tv_usec;
		ds  = nowTime.tv_sec  - lastTime.tv_sec;
		if (dus < 0) {
			ds--;
			dus += 1000000;
		}
		if (ds == 0 && dus < 200000)
			return;
	} else {
		inited = True;
	}
	lastTime = nowTime;
	

	if (++currentCursor == XtNumber(cursorInfo))
		currentCursor = 0;

	c = cursorInfo[currentCursor].cursor;

	for (cur = head; cur != NULL; cur = cur->next) {
		if (cur->watchCount == 0 && cur->computingCount != 0 && cur->win != None) {
			XDefineCursor(cur->dpy, cur->win, c);
			done = True;
		}
	}

	if (done)
		XFlush(head->dpy);
}
