/* writex11.c */

#ifdef SUPPORT_X11

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "tile.h"

/* These variables describe the root window */
static Display	*display;	/* X11 display */
static int	screen;		/* screen of that display */
static int	depth;		/* bits per pixel of that screen */
static Window	root;		/* root window */
static Colormap colormap;	/* color map used for root window */
static int	hroot, wroot;	/* height & width of root window */
static GC	gc;		/* graphic context */

/* Forward declarations for some static functions */
static void initx11(void);
static Pixmap img2pixmap(unsigned char *img, unsigned width, unsigned height, rgb_t palette[], unsigned ncolors);
static void forgetold(void);
static void remembernew(Pixmap pixmap);


#define FAIL(why)	{ fprintf(stderr, "fractile: %s\n", (why)); exit(2); }

/* This function opens a connection to the X server, and initializes some
 * static variables describing the root window.
 */
static void initx11(void)
{

	/* open a connection to the X server */
	display = XOpenDisplay("");
	if (!display)
		FAIL("Could not contact X server");

	/* initialize the variables */
	screen = DefaultScreen(display);
	depth = DefaultDepth(display, screen);
	colormap = DefaultColormap(display, screen);
	root = RootWindow(display, screen);
	wroot = DisplayWidth(display, screen);
	hroot = DisplayHeight(display, screen);
	gc = DefaultGC(display, screen);
	if (!gc)
		FAIL("No default GC");
}

/* This function returns an X11 pixmap containing the texture image. */
static Pixmap img2pixmap(unsigned char *img, unsigned width, unsigned height, rgb_t palette[], unsigned ncolors)
{
	unsigned wpix, hpix;	/* size of the pixmap */
	Pixmap	 pixmap;	/* the pixmap itself */
	long	 pixel[256];	/* table, converts img[] pixel to X11 pixel */
	XColor	 color;
	int	 x, y;

	/* Choose a size for the pixmap.  This is normally the same size as
	 * the image, but if the image is larger than the root window then
	 * it is clipped to fit.
	 */
	wpix = (width<=wroot) ? width : wroot;
	hpix = (height<=hroot) ? height : hroot;

	/* create the pixmap */
	pixmap = XCreatePixmap(display, root, wpix, hpix, depth);
	if (!pixmap)
		FAIL("Couldn't create pixmap");

	/* Allocate the colors */
	for (x = 0; x < ncolors; x++)
	{
		if (palette[x].red == 255 && palette[x].green == 255 && palette[x].blue == 255)
		{
			pixel[x] = WhitePixel(display, screen);
		}
		else if (palette[x].red == 0 && palette[x].green == 0 && palette[x].blue == 0)
		{
			pixel[x] = BlackPixel(display, screen);
		}
		else
		{
			color.red = palette[x].red << 8;
			color.green = palette[x].green << 8;
			color.blue = palette[x].blue << 8;
			color.flags = DoRed|DoGreen|DoBlue;
			if (!XAllocColor(display, colormap, &color))
				FAIL("Color map full");
			pixel[x] = color.pixel;
		}
	}

	/* Fill the pixmap with color info */
	for (x = 0; x < wpix; x++)
	{
		for (y = 0; y < hpix; y++)
		{
			XSetForeground(display, gc, pixel[img[y * width + x]]);
			XDrawPoint(display, pixmap, gc, x, y);
		}
	}

	/* return the pixmap */
	return pixmap;
}

/* Forget the old background image (left-over from a previous process) */
static void forgetold(void)
{
	Atom		prop, type;
	int		format;
	unsigned long	length;
	unsigned long	dummy;
	XID		*valptr;

	/* Look for an "_XSETROOT_ID" atom.  If it doesn't exist, then there
	 * is no old image to forget about.
	 */
	prop = XInternAtom(display, "_XSETROOT_ID", True);
	if (prop == None)
		return;

	/* Get the root window's value of that property */
	if (XGetWindowProperty(display, root, prop, 0L, 1L, True, 
		AnyPropertyType, &type, &format, &length, 
		&dummy, (unsigned char **)&valptr) == Success)
	{
		/* If the value is a pixmap, then free its resources */
		if (type==XA_PIXMAP && format==32 && length==1 && valptr)
		{
			XSetWindowBackground(display, root, BlackPixel(display, screen));
			XClearWindow(display, root);
			XKillClient(display, *valptr);
			XDeleteProperty(display, root, prop);
		}

		if (valptr) XFree((char *)valptr);
	}
}

/* This function arranges for the background pixmap to be retained after
 * "fractile" exits.  It also sets the "_XSETROOT_ID" property so that some
 * other process can come along later and free it.
 */
static void remembernew(Pixmap pixmap)
{
	Atom          prop;

	/* Create an Atom to be used to label the _XSETROOT_ID property */
	prop = XInternAtom(display, "_XSETROOT_ID", False);
	if (!prop)
		FAIL("Couldn't create _XSETROOT_ID atom");

	/* Store the pixmap's ID as the property value */
	XChangeProperty(display, root, prop, XA_PIXMAP, 32, PropModeReplace,
		  (unsigned char *)&pixmap, 1);

	/* arrange for the pixmap to be retained upon exit */
	XSetCloseDownMode(display, RetainPermanent);
}


void WriteX11(char *filename,		/* filename (ignored) */
	unsigned char *img,		/* image data, 8 bits per pixel */
	unsigned width, unsigned height,/* size of image */
	rgb_t palette[], unsigned ncolors)/* palette, and palette size */
{
	Pixmap	pixmap;

	/* Initialize the connection to the X server. */
	initx11();

	/* If there was a previous background image, free it now */
	forgetold();

	/* Convert the image to a pixmap */
	pixmap = img2pixmap(img, width, height, palette, ncolors);

	/* Use the pixmap as the background of the root window. */
	XSetWindowBackgroundPixmap(display, root, pixmap);

	/* Redraw the root window's background */
	XClearWindow(display, root);

	/* Prevent colors from being freed automatically */
	remembernew(pixmap);

	/* Flush all pending requests (IMPORTANT!) and close the display. */
	XCloseDisplay(display);
}
#endif /* defined(SUPPORT_X11) */
