/*
 * A simple window, with trivial mouse interaction - Mark Moraes.
 */
#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xos.h>
#include <X11/cursorfont.h>
#include <X11/Xutil.h>

#define APP_NAME		"xskel"
#define APP_CLASS		"XSkel"

#ifndef OUTPUT_ONLY
# define RightButton		Button3
# define MiddleButton		Button2
# define LeftButton		Button1
# define RightButtonMask	Button3Mask
# define MiddleButtonMask	Button2Mask
# define LeftButtonMask		Button1Mask
#endif	/* OUTPUT_ONLY */

/*
 * If MAXARGS isn't enough to hold all the arguments you pass any Xt
 * procedure, the program aborts
 */
#define MAXARGS 32
static int nargs;
static Arg wargs[MAXARGS];
#define startargs()		nargs = 0
#define setarg(name, value)	\
	if (nargs < MAXARGS) \
		XtSetArg(wargs[nargs], name, value), nargs++; \
	else \
		abort()

static Cursor WorkingCursor;
static Display *dpy;
static Window win;
static GC gc;
static GC fillgc;
static GC cleargc;

static int running = 0;

/* Defaults */
static int defaultWidth = 512;
static int defaultHeight = 512;
static int defaultScale = 1;

static int Width, Height;
static int Scale;
static Pixel fg, bg;
static char *progname;

static char gray4_bits[] = {
   0x00, 0x07, 0x02};
#define gray4_height 3
#define gray4_width 3

static Pixmap graymap;

/* Application Resources - no particular widget */
static XtResource application_resources[] = {
	{"name", "Name", XtRString, sizeof(char *),
		(Cardinal)&progname, XtRString, APP_NAME},
	{"width", "Width", XtRInt, sizeof(int),
		(Cardinal)&Width, XtRInt, (caddr_t) &defaultWidth},
	{"height", "Height", XtRInt, sizeof(int),
		(Cardinal)&Height, XtRInt, (caddr_t) &defaultHeight},
	{"foreground", "Foreground", XtRPixel, sizeof(Pixel),
		(Cardinal)&fg, XtRString, (caddr_t) "Black"},
	{"background", "Background", XtRPixel, sizeof(Pixel),
		(Cardinal)&bg, XtRString, (caddr_t) "White"},
	{"scale", "Scale", XtRInt, sizeof(int),
		(Cardinal)&Scale, XtRInt, (caddr_t) &defaultScale},
};

/*
 *  Command line options table. The command line is parsed for these,
 *  and it sets/overrides the appropriate values in the resource
 *  database
 */
static XrmOptionDescRec optionDescList[] = {
{"-width",	"*width",	XrmoptionSepArg, 	(caddr_t) NULL},
{"-height",	"*height",	XrmoptionSepArg,	(caddr_t) NULL},
{"-fg",		"*foreground",	XrmoptionSepArg,	(caddr_t) NULL},
{"-bg",		"*background",	XrmoptionSepArg,	(caddr_t) NULL},
{"-scale",	"*scale",	XrmoptionSepArg,	(caddr_t) NULL},
};



main(argc, argv)
int argc;
char **argv;
{
	Widget toplevel;
	Widget w;
	XGCValues gcv;
#ifndef OUTPUT_ONLY
	void RepaintCanvas();
	void RecordMapStatus();
	void MouseInput();
#else
	XEvent ev;
#endif

	/*
 	 * Create the top level Widget that represents encloses the
	 * application.
	 */
	toplevel = XtInitialize(argv[0], APP_CLASS,
		optionDescList, XtNumber(optionDescList), (Cardinal *) &argc,
		argv);

	XtGetApplicationResources(toplevel, (caddr_t) 0, application_resources,
		XtNumber(application_resources), (ArgList) NULL, (Cardinal)0);


	if (argc != 1) {
		(void) fprintf(stderr, "Usage: %s [Xt options]\n", argv[0]);
		exit(-1);
	}

	/*
	 * Create a simple Core class widget which we'll use for the actual
	 * game.  A Core class widget is basically just a window, with a
	 * simple Xt "wrapper" around it.
	 */
	startargs();
	setarg(XtNwidth, Width);
	setarg(XtNheight, Height);
	w = XtCreateManagedWidget(argv[0], widgetClass, toplevel, 
		wargs, XtNumber(wargs));

#ifndef OUTPUT_ONLY
	/*
	 * Set the procedures for various X Windows actions - exposure events
	 * which arrive when a window needs to be redrawn. The map event lets
	 * you know that the window is now on the screen so you can actually
	 * do stuff. The ButtonPress event lets you know that a mouse button
	 * was pressed.
	 */
	XtAddEventHandler(w, (EventMask) ExposureMask, False, 
	 RepaintCanvas, "redraw_data");
	XtAddEventHandler(w, (EventMask) StructureNotifyMask, False, 
	 RecordMapStatus, "map_data");
	/* One day, we'll use the translation manager here */
	XtAddEventHandler(w, 
	 (EventMask) ButtonPressMask | ButtonMotionMask, False,
	 MouseInput, "input_data");
#endif

	/*
	 * Create the windows, and set their attributes according to the Widget
	 * data.
	 */
	XtRealizeWidget(toplevel);
	
	/* We need these for the raw Xlib calls */
	win = XtWindow(w);
	dpy = XtDisplay(w);

	WorkingCursor = XCreateFontCursor(dpy, XC_top_left_arrow);
	XDefineCursor(dpy, win, WorkingCursor);

	/*
	 *  make the GC stuff here - one for copy, one for invert. Remember
	 *  to change the both appropriately
	 */
	gcv.foreground = fg;
	gcv.background = bg;
	gcv.function = GXcopy;
	gc = XCreateGC(dpy, win, GCForeground | GCBackground 
	 | GCFunction, &gcv);
	/* create a dithered background pixmap */
	graymap = XCreatePixmapFromBitmapData(dpy, win, gray4_bits,
					      gray4_height, gray4_width,
					      fg, bg, 1);
	gcv.stipple = graymap;
	gcv.fill_style = FillOpaqueStippled;
	fillgc = XCreateGC(dpy, win, GCForeground | GCBackground 
	 | GCFunction | GCFillStyle | GCStipple, &gcv);
	gcv.foreground = bg;
	cleargc = XCreateGC(dpy, win, GCForeground | GCBackground 
	 | GCFunction, &gcv);

	
	/*
	 *  Now process the events.
	 */
#ifndef OUTPUT_ONLY
	XtMainLoop();
#else
	/* Wait for first exposure event so we know window has been mapped */
	write(2, "waiting for window map\n", 23);
	do {
		XNextEvent(dpy, &ev);
	} while(XPending(dpy));
	XFillRectangle(dpy, win, fillgc, 0, 0, Width, Height);
	XSync(dpy, True);
	write(2, "ready for data\n", 15);
	gobble();
	XSync(dpy, True);
	printf("All done!\n");
	fflush(stdout);
	while(1)
		pause();
#endif
}



static
calcrowcol(addr, rowp, colp)
int *rowp;
int *colp;
{
	*rowp = addr / Width;
	*colp = addr % Width;
}

int
fillmem(lo, hi, clear)
{
	int plo = lo / Scale;
	int phi = hi / Scale;
	int srow, scol, erow, ecol;
	int i;
	GC tgc = clear ? cleargc : gc;

	calcrowcol(plo, &srow, &scol);
	calcrowcol(phi, &erow, &ecol);

	if (srow >= Height) {
		char buf[200];
		sprintf(buf, "Real estate slump.\nlo=%d, hi=%d, srow=%d\n",
			lo,hi,srow);
		write(2, buf, strlen(buf));
return -1;
		XSync(dpy, True);
		while (1)
			pause();
	}
	if (erow >= Height) {
		erow = Height - 1;
	}
	if (srow == erow) {
		XDrawLine(dpy, win, tgc, scol, srow, ecol, srow);
	} else if (srow > erow) {
		abort();
	} else {
		int j = scol;
		for(i = srow; i < erow; i++) {
			XDrawLine(dpy, win, tgc, j, i, Width - 1, i);
			j = 0;
		}
		XDrawLine(dpy, win, tgc, 0, i, ecol, i);
	}
	return 0;
}

#ifndef OUTPUT_ONLY

static int isMapped = 0;

/*ARGSUSED*/
void RepaintCanvas(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
	if (!isMapped)
		return;
	/*
	 * Redraw the array
	 */
	if (ev && ev->xexpose.count == 0) {
		XEvent event;
		/* Skip all excess redraws */
		while (XCheckTypedEvent(dpy, Expose, &event))
			;
	}

	XClearWindow(dpy, ev->xany.window);
		
#ifdef WINDOWDEBUG
	(void) printf("repaint\n");
#endif
	XSync(dpy, True);
}

/*ARGSUSED*/
void RecordMapStatus(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
	if (ev->type == MapNotify) {
#ifdef WINDOWDEBUG
		(void) printf("window mapped\n");
#endif
		isMapped = TRUE;
	} else if (ev->type = ConfigureNotify) {
#ifdef WINDOWDEBUG
		(void) printf("window resized\n");
#endif
	}
}


/*ARGSUSED*/
void MouseInput(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
#ifdef WINDOWDEBUG
	(void) printf("Input to canvas - %d (0x%x)\n", ev->xany.type, ev->xany.type);
#endif
	switch (ev->xany.type) {
	case ButtonPress:
		if (ev->xbutton.button == LeftButton) {
			/* Turn on cell */
			setcell((int) (ev->xbutton.x / CELLWIDTH),
				(int) (ev->xbutton.y / CELLHEIGHT), gc);
		} else if (ev->xbutton.button == MiddleButton) {
			/* Toggle simulation on/off */
			running = !running;
		}
		break;
	case MotionNotify:
		if (ev->xmotion.state & LeftButtonMask) 
			/* Turn on cell */
			setcell((int) (ev->xmotion.x / CELLWIDTH),
				(int) (ev->xmotion.y / CELLHEIGHT), gc);
		break;
	default:
		(void) printf("Got an event of type %d (%x).\n",
		       ev->xany.type, ev->xany.type);
		break;
	}
}
#endif

xgobble()
{
	char buf[128];
	int nlines = 0;
	int lo, hi;
	int ret;
	
	while(fgets(buf, sizeof buf, stdin) != NULL) {
		++nlines;
		if (buf[0] == '\n') {
			XSync(dpy, True);
			continue;
		}
		if ((ret = sscanf(buf, " %d %d", &lo, &hi)) != 2) {
			fprintf(stderr, "yuck.  %d: %s", nlines, buf);
			fprintf(stderr, "ret = %d, lo = %d, hi = %d\n",
				ret, lo, hi);
			exit(1);
		}
		fillmem(lo, hi, 0);
	}
}

gobble()
{
	char buf[128];
	int nlines = 0;
	int lo, hi, n, ah, at;
	int base = 0;
	int nmsg = 0;
	FILE *fp;
	
#if 0
	for (n = 0 ; n < Height ; ++n)
		fillmem(n*Width, (n+1)*Width - 1, 0);
#endif

	while(fgets(buf, sizeof buf, stdin) != NULL) {
		++nlines;
		switch (buf[0]) {
		case '+':
			if (sscanf(buf, "%*[+] %*d %d 0x%x", &n, &lo) == 2) {
				lo -= base;
				lo  = lo * Scale;
				n   = n * Scale;
				/* printf("+ %x %d\n", lo, n); */
				if (fillmem(lo, lo+n-1, 0)) {
				  printf("Mem problem on input line %d: %s",
					 nlines,buf);
				  fflush(stdout);
				}
			}
			break;
		case '-':
			if (sscanf(buf, "- %d 0x%x", &n, &lo) == 2) {
				lo -= base;
				lo  = lo * Scale;
				n   = n * Scale;
				/* printf("- %x %d\n", lo, n); */
				if (fillmem(lo, lo+n-1, 1)) {
				  printf("Mem problem on input line %d: %s",
					 nlines,buf);
				  fflush(stdout);
				}
			}
			break;
		case 'a':
			if (sscanf(buf,"%*[a-z] %d %*[a-z] %d",&ah, &at) != 2){
				abort();
			}
			break;
		case 'h':	/* heapstart or heapend */
			if (strncmp(buf, "heapstart", 9) == 0)
				sscanf(buf, "%*[a-z] 0x%x", &base);
			break;
		case 'w':
			if (sscanf(buf, "%*[a-z] %d", &Scale) != 1)
				abort();
			printf("Scale is %d\n", Scale);
			break;
		case 'n':
			break;
		case '\n':
			XSync(dpy, True);
			printf("%d\n", ++nmsg);
			fflush(stdout);
#if 0
			if ((fp = fopen("/dev/tty", "r")) != NULL) {
				fprintf(stdout, "Boundary!\n");
				fflush(stdout);
				fgets(buf, sizeof buf, fp);
				fclose(fp);
			}
#endif
			break;
		}
	}
}
