/*
 * VOGLE driver for X11 under the DEC window manager.
 *
 *	ok so we admit that we don't know heaps about X11, but come
 * on guys, this shouldn't be neccessary (wild comment from DEC about
 * why our standard X11 driver won't work is welcome.)
 */
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "vogl.h"

#define LARGEX11R2	"courier12f.snf"
#define SMALLX11R2	"courier10f.snf"

#define LARGEX11R3	"-adobe-courier-medium-r-normal--24-240-75-75-m-150-iso8859-1"
#define SMALLX11R3	"-adobe-courier-medium-r-normal--10-100-75-75-m-60-iso8859-1"

#define MIN(x,y)	((x) < (y) ? (x) : (y))
#define	CMAPSIZE	256
#define	EV_MASK		KeyPressMask|ButtonReleaseMask|ExposureMask|ButtonPressMask

static	Window		winder;
static	Display		*display;
static	int		screen;
static	unsigned long	carray[CMAPSIZE];
static	Colormap	colormap;

static	Drawable	theDrawable;
static	GC		theGC;
static	Pixmap		bbuf;		/* Back buffer pixmap */
static	int		back_used;	/* Have we backbuffered ? */

static	XFontStruct	*font_id;
XEvent			event;

static	int		size;
static	unsigned long	colour;
static	unsigned int	h, w;

/*
 * DECX11_init
 *
 *	initialises X11 display.
 */
int DECX11_init(void)
{
	int		i;
	int		x, y, prefx, prefy, prefxs, prefys;
	unsigned int	bw, depth, mask;
	Window		rootw, childw, topwinder, *kiddies;
	char		*av[2], name[50];

	XSetWindowAttributes    theWindowAttributes;
        XSizeHints              theSizeHints;
        unsigned long           theWindowMask;
	XWMHints                theWMHints;


	av[0] = "vogl.X11";
	av[1] = (char *)NULL;

	if ((display = XOpenDisplay((char *)NULL)) == (Display *)NULL) {
		fprintf(stderr,"vogl: DECX11_init: can't connect to X server\n");
		exit(1);
	}

	winder = DefaultRootWindow(display);
	screen = DefaultScreen(display);
	vdevice.depth = DefaultDepth(display, screen);
	colormap = DefaultColormap(display, screen);

	/*
	 * Set our standard colors...
	 */
	if (vdevice.depth == 1) {
		/*
		 * Black and white - anything that's not black is white.
		 */
		carray[0] = BlackPixel(display, screen);
		for (i = 1; i < CMAPSIZE; i++)
			carray[i] = WhitePixel(display, screen);
	} else {
		/*
		 * Color, try to get our colors close to what's in the
		 * default colormap.
		 */
		DECX11_mapcolor(0, 0, 0, 0);
		DECX11_mapcolor(1, 255, 0, 0);
		DECX11_mapcolor(2, 0, 255, 0);
		DECX11_mapcolor(3, 255, 255, 0);
		DECX11_mapcolor(4, 0, 0, 255);
		DECX11_mapcolor(5, 255, 0, 255);
		DECX11_mapcolor(6, 0, 255, 255);
		DECX11_mapcolor(7, 255, 255, 255);
	}

	getprefposandsize(&prefx, &prefy, &prefxs, &prefys);

	XQueryPointer(display, winder, &rootw, &childw, &x, &y, &x, &y, &mask);

	if (childw == None)
		childw = rootw;

	/*
	 * there is something very weird about dec's window manager
	 * as expressed on a dec station, to get the details for the window
	 * we are actually in we have to get the root window of the childw
	 * which gives us the root window of the real window stack, and then
	 * we use XQueryPointer to find the real child window.
	 */

	XQueryTree(display, childw, &rootw, &rootw, &kiddies, &i);

	topwinder = winder;
	winder = kiddies[0];
	XQueryPointer(display, winder, &rootw, &childw, &x, &y, &x, &y, &mask);

	XGetGeometry(display, childw, &rootw, &x, &y, &w, &h, &bw, &depth);

	if (prefx > -1) {
	        x = prefx;
	        y = prefy;
	}

	if (prefxs > -1) {
	        w = prefxs;
	        h = prefys;
	}

	x += bw;
	y += bw;

	w -= 2 * bw;
	h -= 2 * bw;

	theWindowAttributes.override_redirect = False;

        /*theWindowMask = CWBackPixel|CWBorderPixel|CWOverrideRedirect;*/

        theWindowMask = CWOverrideRedirect;

        winder = XCreateWindow(display,
                                topwinder,
                                x, y,
                                w, h,
                                bw,
                                (int)depth,
                                InputOutput,
                                CopyFromParent,
                                theWindowMask,
                                &theWindowAttributes
                        );

        theWMHints.initial_state = NormalState;
        theWMHints.flags = StateHint;
        XSetWMHints(display, winder, &theWMHints);
 
        theSizeHints.flags = PPosition|PSize;
        theSizeHints.x = x;
        theSizeHints.y = y;
        theSizeHints.width = w;
        theSizeHints.height = h;
 
        XSetNormalHints(display, winder, &theSizeHints);

	sprintf(name, "vogl %d", getpid());

	XSetStandardProperties(display,
		winder,
		name,
		name,
		None,
		av,
		1,
		&theSizeHints
	);

	XSelectInput(display, winder, EV_MASK);

	theDrawable = (Drawable)winder;

	/*
	 *  Let VOGLE know about the window size.
	 */
	vdevice.sizeX = vdevice.sizeY = MIN(h, w) - 1;
	vdevice.sizeSx = w - 1;
	vdevice.sizeSy = h - 1;

	/*
	 * Create Graphics Context and Drawable
	 */
	theGC = XDefaultGC(display, screen);
	theDrawable = (Drawable)winder;
	DECX11_color(0);

	XMapRaised(display, winder);
	XFlush(display);

	/*
	 * Wait for Exposure event.
	 */
	do {
		XNextEvent(display, &event);
	} while (event.type != Expose);

	if (prefx == -1 && prefxs == -1)
		XSetInputFocus(display, winder, RevertToParent, CurrentTime);

	back_used = 0;

	return(1);
}

/*
 * DECX11_exit
 *
 *	cleans up before returning the window to normal.
 */
int DECX11_exit(void)
{
	XFreeGC(display, theGC);

	if (back_used) 
		XFreePixmap(display, bbuf);

	XUnmapWindow(display, winder);

	XDestroyWindow(display, winder);

	return(1);
}

/*
 * DECX11_draw
 *
 *	draws a line from the current graphics position to (x, y).
 *
 * Note: (0, 0) is defined as the top left of the window in X (easy
 * to forget).
 */
int DECX11_draw(
  int x,
  int y)
{
	XDrawLine(display,
		theDrawable,
		theGC,
		vdevice.cpVx, vdevice.sizeSy - vdevice.cpVy,
		x, vdevice.sizeSy - y
	);

	XFlush(display);
}

/*
 * DECX11_getkey
 *
 *	grab a character from the keyboard - blocks until one is there.
 */
int DECX11_getkey(void)
{
	char	c;

	do {
		XNextEvent(display, &event);
		if (event.type == KeyPress) {
			if (XLookupString(&event, &c, 1, NULL, NULL) > 0)
				return((int)c);
			else
				return(0);
		}
	} while (event.type != KeyPress);
}

/*
 * DECX11_checkkey
 *
 *	Check if there has been a keyboard key pressed.
 *	and return it if there is.
 */
int DECX11_checkkey(void)
{
	char	c;
	int	i;

	if (!XCheckWindowEvent(display, winder, KeyPressMask, &event))
		return(0);

	if (event.type == KeyPress)
		if (XLookupString(&event, &c, 1, NULL, NULL) > 0)
			return((int)c);

	return(0);
}

/*
 * DECX11_locator
 *
 *	return the window location of the cursor, plus which mouse button,
 * if any, is been pressed.
 */
int DECX11_locator(
  int *wx,
  int *wy)
{
	Window	rootw, childw;
	int	x, y, mask;

	XQueryPointer(display, winder, &rootw, &childw, &x, &y, wx, wy, &mask);

	*wy = (int)vdevice.sizeSy - *wy;

	return(mask >> 8);
}

/*
 * DECX11_clear
 *
 * Clear the screen (or current buffer )to current colour
 */
int DECX11_clear(void)
{
	XSetBackground(display, theGC, colour);
	XFillRectangle(display,
		theDrawable,
		theGC,
		vdevice.minVx,
		vdevice.minVy, 
		(unsigned int)vdevice.maxVx,
		(unsigned int)vdevice.maxVy
	);
}

/*
 * DECX11_color
 *
 *	set the current drawing color index.
 */
int DECX11_color(int ind)
{
	colour = carray[ind];
	XSetForeground(display, theGC, colour);
}

/*
 * DECX11_mapcolor
 *
 *	change index i in the color map to the appropriate r, g, b, value.
 */
int DECX11_mapcolor(
  int i,
  int r,
  int g,
  int b)
{
	int	stat;
	XColor	tmp;

	if (i >= CMAPSIZE)
		return(-1);


	/*
	 * For Black and White.
	 * If the index is 0 and r,g,b != 0 then we are remapping black.
	 * If the index != 0 and r,g,b == 0 then we make it black.
	 */
	if (vdevice.depth == 1) {
		if (i == 0 && (r != 0 || g != 0 || b != 0)) 
			carray[i] = WhitePixel(display, screen);
		else if (i != 0 && r == 0 && g == 0 && b == 0)
			carray[i] = BlackPixel(display, screen);
	} else {
		tmp.red = (unsigned short)(r / 255.0 * 65535);
		tmp.green = (unsigned short)(g / 255.0 * 65535);
		tmp.blue = (unsigned short)(b / 255.0 * 65535);
		tmp.flags = 0;
		tmp.pixel = (unsigned long)i;

		if ((stat = XAllocColor(display, colormap, &tmp)) == 0) {
			fprintf(stderr, "XAllocColor failed (status = %d)\n", stat);
			exit(1);
		}
		carray[i] = tmp.pixel;
	}

	XFlush(display);
	return(0);
}
	
/*
 * DECX11_font
 *
 *   Set up a hardware font. Return 1 on success 0 otherwise.
 *
 */
int DECX11_font(char *fontfile)
{
	XGCValues	xgcvals;

	if (font_id != (XFontStruct *)NULL)
		XFreeFont(display, font_id);

	if (strcmp(fontfile, "small") == 0) {
		if ((font_id = XLoadQueryFont(display, SMALLX11R2)) == (XFontStruct *)NULL) {		/* X11 R2 */
			if ((font_id = XLoadQueryFont(display, SMALLX11R3)) == (XFontStruct *)NULL)	 	/* X11 R3 */
				return(0);
			else
				fontfile = SMALLX11R3;
		} else
			fontfile = SMALLX11R2;
	} else if (strcmp(fontfile, "large") == 0) {
		if ((font_id = XLoadQueryFont(display, LARGEX11R2)) == (XFontStruct *)NULL) {		/* X11 R2 */
			if ((font_id = XLoadQueryFont(display, LARGEX11R3)) == (XFontStruct *)NULL)	 	/* X11 R3 */
				return(0);
			else
				fontfile = LARGEX11R3;
		} else
			fontfile = LARGEX11R2;
	} else if ((font_id = XLoadQueryFont(display, fontfile)) == (XFontStruct *)NULL)
		return(0);

	vdevice.hheight = font_id->max_bounds.ascent + font_id->max_bounds.descent;
	vdevice.hwidth = font_id->max_bounds.width;

	xgcvals.font = XLoadFont(display, fontfile);
	XChangeGC(display, theGC, GCFont, &xgcvals);

	return(1);
}

/* 
 * DECX11_char
 *
 *	 outputs one char - is more complicated for other devices
 */
int DECX11_char(char c)
{
	char	*s = " ";

	s[0] = c;
	XDrawString(display, theDrawable, theGC, vdevice.cpVx, (int)(vdevice.sizeSy - vdevice.cpVy), s, 1);
	XFlush(display);
}

/*
 * DECX11_string
 *
 *	Display a string at the current drawing position.
 */
int DECX11_string(char s[])
{
	XDrawString(display, theDrawable, theGC, vdevice.cpVx, (int)(vdevice.sizeSy - vdevice.cpVy), s, strlen(s));
	XSync(display, 0);
}

/*
 * DECX11_fill
 *
 *	fill a polygon
 */
int DECX11_fill(
  int n,
  int x[],
  int y[])
{
	XPoint	plist[128];
	int	i;

	if (n > 128)
		verror("vogl: more than 128 points in a polygon");

	for (i = 0; i < n; i++) {
		plist[i].x = x[i];
		plist[i].y = vdevice.sizeSy - y[i];
	}

	XFillPolygon(display, theDrawable, theGC, plist, n, Nonconvex, CoordModeOrigin);

	vdevice.cpVx = x[n-1];
	vdevice.cpVy = y[n-1];

	XFlush(display);
}

#define	GC_COPY_MASK	~0

/*
 * DECX11_backbuf
 *
 *	Set up double buffering by allocating the back buffer and
 *	setting drawing into it.
 */
int DECX11_backbuf(void)
{
	if (!back_used)
		bbuf = XCreatePixmap(display,
			(Drawable)winder,
			(unsigned int)vdevice.sizeSx + 1,
			(unsigned int)vdevice.sizeSy + 1,
			(unsigned int)vdevice.depth
		);

	theDrawable = (Drawable)bbuf;

	back_used = 1;

	return(1);
}

/*
 * DECX11_swapbuf
 *
 *	Swap the back and from buffers. (Really, just copy the
 *	back buffer to the screen).
 */
int DECX11_swapbuf(void)
{
	XCopyArea(display,
		theDrawable,
		winder,
		theGC,
		0, 0,
		(unsigned int)vdevice.sizeSx + 1,
		(unsigned int)vdevice.sizeSy + 1,
		0, 0
	);

	XSync(display, 0);	/* Not XFlush */
}

/*
 * DECX11_frontbuf
 *
 *	Make sure we draw to the screen.
 */
int DECX11_frontbuf(void)
{
	theDrawable = (Drawable)winder;
}

/*
 * the device entry
 */
static DevEntry DECX11dev = {
	"decX11",
	"large",
	"small",
	DECX11_backbuf,
	DECX11_char,
	DECX11_checkkey,
	DECX11_clear,
	DECX11_color,
	DECX11_draw,
	DECX11_exit,
	DECX11_fill,
	DECX11_font,
	DECX11_frontbuf,
	DECX11_getkey,
	DECX11_init,
	DECX11_locator,
	DECX11_mapcolor,
	noop,
	noop,
	DECX11_string,
	DECX11_swapbuf,
	noop
};

/*
 * _DECX11_devcpy
 *
 *	copy the decX11 device into vdevice.dev.
 */
int _DECX11_devcpy(void)
{
	vdevice.dev = DECX11dev;
}
