#include <librnd/rnd_config.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/core/hidlib.h>
#include <librnd/core/globalconst.h>
#include <librnd/core/compat_misc.h>
#include <librnd/hid/grid.h>
#include <librnd/plugins/lib_hid_common/lib_hid_common.h>
#include <librnd/plugins/lib_hid_common/clip.h>

#if 0
#	define render_trace rnd_trace
#else
	static void render_trace(const char *fmt, ...) {}
#endif

#include "render.h"

xrnd_ctx_t xrnd;

/* save rnd_render and restore at the end: we may get an async expose draw
   while it is set to an exporter */
#define XRND_SAVE_HID() \
do { \
	hid_save = rnd_render; \
	rnd_render = xrnd.hid; \
} while(0)

#define XRND_RESTORE_HID() \
 rnd_render = hid_save


/* How big the viewport can be relative to the design size.  */
#define MAX_ZOOM_SCALE	10

typedef struct rnd_hid_gc_s {
	rnd_core_gc_t core_gc;
	rnd_hid_t *me_pointer;
	Pixel color;
	rnd_color_t pcolor;
	int width;
	rnd_cap_style_t cap;
	char xor_set;
	char erase;
} rnd_hid_gc_s;


#define use_mask() ((!xrnd.direct) && ((xrnd.drawing_mode == RND_HID_COMP_POSITIVE) || (xrnd.drawing_mode == RND_HID_COMP_POSITIVE_XOR) || (xrnd.drawing_mode == RND_HID_COMP_NEGATIVE)))

static void zoom_to(double factor, rnd_coord_t x, rnd_coord_t y);
static void zoom_win(rnd_hid_t *hid, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, int setch);
static void zoom_by(double factor, rnd_coord_t x, rnd_coord_t y);

/* Px converts view->design, Vx converts pcb->view */

RND_INLINE int Vx(rnd_coord_t x)
{
	int rv = (x - xrnd.view_left_x) / xrnd.view_zoom + 0.5;
	if (rnd_conf.editor.view.flip_x)
		rv = xrnd.view_width - rv;
	return rv;
}

RND_INLINE int Vy(rnd_coord_t y)
{
	int rv = (y - xrnd.view_top_y) / xrnd.view_zoom + 0.5;
	if (rnd_conf.editor.view.flip_y)
		rv = xrnd.view_height - rv;
	return rv;
}

RND_INLINE int Vz(rnd_coord_t z)
{
	return z / xrnd.view_zoom + 0.5;
}

RND_INLINE int Vw(rnd_coord_t w)
{
	return w < 0 ? -w : w / xrnd.view_zoom + 0.5;
}

RND_INLINE rnd_coord_t Px(int x)
{
	if (rnd_conf.editor.view.flip_x)
		x = xrnd.view_width - x;
	return x * xrnd.view_zoom + xrnd.view_left_x;
}

RND_INLINE rnd_coord_t Py(int y)
{
	if (rnd_conf.editor.view.flip_y)
		y = xrnd.view_height - y;
	return y * xrnd.view_zoom + xrnd.view_top_y;
}

rnd_coord_t xrnd_px(int x)
{
	return Px(x);
}

rnd_coord_t xrnd_py(int y)
{
	return Py(y);
}

void xrnd_coords_to_design(int vx, int vy, rnd_coord_t * px, rnd_coord_t * py)
{
	*px = Px(vx);
	*py = Py(vy);
}

Pixel xrnd_parse_color(const rnd_color_t *value)
{
	XColor color;
	color.pixel = 0;
	color.red = (unsigned)value->r << 8;
	color.green = (unsigned)value->g << 8;
	color.blue = (unsigned)value->b << 8;
	color.flags = DoRed | DoBlue | DoGreen;
	if (XAllocColor(xrnd.display, xrnd.colormap, &color))
		return color.pixel;
	return 0;
}

double xrnd_benchmark(rnd_hid_t *hid)
{
	int i = 0;
	time_t start, end;
	rnd_hid_expose_ctx_t ctx;
	Drawable save_main;


	save_main = xrnd.main_pixmap;
	xrnd.main_pixmap = xrnd.window;

	ctx.design = xrnd.dsg;
	ctx.view.X1 = xrnd.dsg->dwg.X1;
	ctx.view.Y1 = xrnd.dsg->dwg.Y1;
	ctx.view.X2 = xrnd.dsg->dwg.X2;
	ctx.view.Y2 = xrnd.dsg->dwg.X2;

	xrnd.pixmap = xrnd.window;
	XSync(xrnd.display, 0);
	time(&start);
	do {
		XFillRectangle(xrnd.display, xrnd.pixmap, xrnd.bg_gc, 0, 0, xrnd.view_width, xrnd.view_height);
		rnd_app.expose_main(hid, &ctx, NULL);
		XSync(xrnd.display, 0);
		time(&end);
		i++;
	}
	while (end - start < 10);

	xrnd.main_pixmap = save_main;
	return i/10.0;
}

/* ----------------------------------------------------------------------
 * redraws the background image
 */

typedef struct xrnd_pixmap_s {
	const rnd_pixmap_t *pxm;

	/* cache */
	int w_scaled, h_scaled;
	XImage *img_scaled;
	Pixmap pm_scaled;
	Pixmap mask_scaled;
	char *img_data;

#ifdef RND_HAVE_XRENDER
	Picture p_img_scaled;
	Picture p_mask_scaled;
#endif
	GC mask_gc, img_gc;
	unsigned inited:1;
	unsigned flip_x:1;
	unsigned flip_y:1;
} xrnd_pixmap_t;

static void xrnd_draw_pixmap_(rnd_design_t *hidlib, xrnd_pixmap_t *lpm, rnd_coord_t ox, rnd_coord_t oy, rnd_coord_t dw, rnd_coord_t dh)
{
	int w, h, sx3, done = 0;

	w = dw / xrnd.view_zoom;
	h = dh / xrnd.view_zoom;

	/* in flip view start coords need to be flipped too to preserve original area on screen */
	if (rnd_conf.editor.view.flip_y)
		oy += dh;
	if (rnd_conf.editor.view.flip_x)
		ox += dw;

	if ((w != lpm->w_scaled) || (h != lpm->h_scaled) || (lpm->flip_x != rnd_conf.editor.view.flip_x) || (lpm->flip_y != rnd_conf.editor.view.flip_y)) {
		int x, y, nret;
		double xscale, yscale;
		static int vis_inited = 0;
		static XVisualInfo vinfot, *vinfo;
		static Visual *vis;
		static enum { CLRSPC_MISC, CLRSPC_RGB565, CLRSPC_RGB888 } color_space;


		if (!vis_inited) {
			vis = DefaultVisual(xrnd.display, DefaultScreen(xrnd.display));
			vinfot.visualid = XVisualIDFromVisual(vis);
			vinfo = XGetVisualInfo(xrnd.display, VisualIDMask, &vinfot, &nret);
			vis_inited = 1;
			color_space = CLRSPC_MISC;

			if ((vinfo->class == TrueColor) && (vinfo->depth == 16) && (vinfo->red_mask == 0xf800) && (vinfo->green_mask == 0x07e0) && (vinfo->blue_mask == 0x001f))
				color_space = CLRSPC_RGB565;

			if ((vinfo->class == TrueColor) && (vinfo->depth == 24) && (vinfo->red_mask == 0xff0000) && (vinfo->green_mask == 0x00ff00) && (vinfo->blue_mask == 0x0000ff))
				color_space = CLRSPC_RGB888;
		}

		if (!lpm->inited) {
			lpm->img_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
			if (lpm->pxm->has_transp)
				lpm->mask_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
			lpm->inited = 1;
		}

		if (lpm->img_scaled != NULL)
			XDestroyImage(lpm->img_scaled);
		if (lpm->mask_scaled != 0)
			XFreePixmap(xrnd.display, lpm->mask_scaled);
		if (lpm->pm_scaled != 0)
			XFreePixmap(xrnd.display, lpm->pm_scaled);

		lpm->img_data = malloc(w*h*4);
		lpm->img_scaled = XCreateImage(xrnd.display, vis, vinfo->depth, ZPixmap, 0, lpm->img_data, w, h, 32, 0);
		lpm->mask_scaled = XCreatePixmap(xrnd.display, xrnd.window, w, h, 1);
		lpm->pm_scaled = XCreatePixmap(xrnd.display, xrnd.window, w, h, 24);
		lpm->w_scaled = w;
		lpm->h_scaled = h;
		lpm->flip_x = rnd_conf.editor.view.flip_x;
		lpm->flip_y = rnd_conf.editor.view.flip_y;

		xscale = (double)lpm->pxm->sx / w;
		yscale = (double)lpm->pxm->sy / h;

		sx3 = lpm->pxm->sx * 3;
		for (y = 0; y < h; y++) {
			XColor pix;
			unsigned char *row;
			int ir;

			if (lpm->flip_y)
				ir = (h-y-1) * yscale;
			else
				ir = y * yscale;

			row = lpm->pxm->p + ir * sx3;
			pix.flags = DoRed | DoGreen | DoBlue;

			for (x = 0; x < w; x++) {
				unsigned long pp;
				int tr = 0, ic;
				unsigned int r, g, b;

				if (lpm->flip_x)
					ic = (w - x - 1) * xscale;
				else
					ic = x * xscale;

				if ((ir < 0) || (ir >= lpm->pxm->sy) || (ic < 0) || (ic >= lpm->pxm->sx))
					tr = 1;
				else {
					ic = ic * 3;
					r = row[ic]; g = row[ic+1]; b = row[ic+2];
					if (lpm->pxm->has_transp && (r == lpm->pxm->tr) && (g == lpm->pxm->tg) && (b == lpm->pxm->tb))
						tr = 1;
					else {
						switch (color_space) {
							case CLRSPC_MISC:
								pix.red = r << 8;
								pix.green = g << 8;
								pix.blue = b << 8;
								XAllocColor(xrnd.display, xrnd.colormap, &pix);
								pp = pix.pixel;
								break;
							case CLRSPC_RGB565:
								pp = (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3);
								break;
							case CLRSPC_RGB888:
								pp = (r << 16) | (g << 8) | (b);
								break;
						}
					}

					if (!tr) {
						XDrawPoint(xrnd.display, lpm->mask_scaled, xrnd.bset_gc, x, y);
						XPutPixel(lpm->img_scaled, x, y, pp);
					}
					else
						XDrawPoint(xrnd.display, lpm->mask_scaled, xrnd.bclear_gc, x, y);
				}
			}

			if (lpm->pxm->has_transp) {
				lpm->pm_scaled = XCreatePixmap(xrnd.display, xrnd.window, w, h, 24);
				XPutImage(xrnd.display, lpm->pm_scaled, xrnd.bg_gc, lpm->img_scaled, 0, 0, 0, 0, w, h);
			}
			else
				lpm->pm_scaled = 0;
		}

#ifdef RND_HAVE_XRENDER
		if (xrnd.use_xrender) {
			if (lpm->p_img_scaled != 0)
				XRenderFreePicture(xrnd.display, lpm->p_img_scaled);
			if (lpm->p_mask_scaled != 0)
				XRenderFreePicture(xrnd.display, lpm->p_mask_scaled);
			lpm->p_img_scaled = XRenderCreatePicture(xrnd.display, lpm->pm_scaled, XRenderFindVisualFormat(xrnd.display, DefaultVisual(xrnd.display, xrnd.screen)), 0, 0);
			if (lpm->pxm->has_transp)
				lpm->p_mask_scaled = XRenderCreatePicture(xrnd.display, lpm->mask_scaled, XRenderFindVisualFormat(xrnd.display, DefaultVisual(xrnd.display, xrnd.screen)), 0, 0);
			else
				lpm->p_mask_scaled = 0;
		}
#endif
	}

#ifdef RND_HAVE_XRENDER
	if (xrnd.use_xrender) {
		XRenderPictureAttributes pa;
/*		fprintf(stderr, "clip xrender\n");*/
		pa.clip_mask = xrnd.mask_bitmap;
		XRenderChangePicture(xrnd.display, lpm->p_img_scaled, CPClipMask, &pa);
		XRenderComposite(xrnd.display, PictOpOver, lpm->p_img_scaled, lpm->p_mask_scaled, xrnd.main_picture, 0, 0, 0, 0, Vx(ox), Vy(ox), w, h);
		done = 1;
	}
#endif

	if (!done) {
		if (lpm->pxm->has_transp) {
			XSetClipMask(xrnd.display, xrnd.pxm_clip_gc, lpm->mask_scaled);
			XSetClipOrigin(xrnd.display, xrnd.pxm_clip_gc, Vx(ox), Vy(oy));
			XCopyArea(xrnd.display, lpm->pm_scaled, xrnd.main_pixmap, xrnd.pxm_clip_gc, 0, 0, w, h, Vx(ox), Vy(oy));
		}
		else
			XPutImage(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, lpm->img_scaled, 0, 0, Vx(ox), Vy(oy), w, h);
	}
}

void xrnd_draw_pixmap(rnd_hid_t *hid, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t sx, rnd_coord_t sy, rnd_pixmap_t *pixmap)
{
	if (pixmap->hid_data == NULL) {
		xrnd_pixmap_t *lpm = calloc(sizeof(xrnd_pixmap_t), 1);
		lpm->pxm = pixmap;
		pixmap->hid_data = lpm;
	}
	if (pixmap->hid_data != NULL) {
		double rsx, rsy, ca = cos(pixmap->tr_rot / RND_RAD_TO_DEG), sa = sin(pixmap->tr_rot / RND_RAD_TO_DEG);

		if (ca < 0) ca = -ca;
		if (sa < 0) sa = -sa;

		rsx = (double)sx * ca + (double)sy * sa;
		rsy = (double)sy * ca + (double)sx * sa;
		xrnd_draw_pixmap_(xrnd.dsg, pixmap->hid_data, cx - rsx/2, cy - rsy/2, rsx, rsy);
	}
}

void xrnd_uninit_pixmap(rnd_hid_t *hid, rnd_pixmap_t *pixmap)
{
	xrnd_pixmap_t *lpm = pixmap->hid_data;

	if (lpm == NULL)
		return;

	if (lpm->img_scaled != NULL)
		XDestroyImage(lpm->img_scaled); /* frees lpm->img_data */
	if (lpm->mask_scaled != 0)
		XFreePixmap(xrnd.display, lpm->mask_scaled);
	if (lpm->pm_scaled != 0)
		XFreePixmap(xrnd.display, lpm->pm_scaled);

	free(lpm);
	pixmap->hid_data = NULL;
}


static void DrawBackgroundImage()
{
	static xrnd_pixmap_t lpm;

	if (!xrnd.window || (xrnd.bg_img.p == NULL))
		return;

	if (lpm.pxm == NULL)
		lpm.pxm = &xrnd.bg_img;
	xrnd_draw_pixmap_(xrnd.dsg, &lpm, 0, 0, xrnd.dsg->dwg.X2, xrnd.dsg->dwg.Y2);
}

void xrnd_load_old_bgimage(const char *fn)
{
	if (rnd_old_pixmap_load(xrnd.dsg, &xrnd.bg_img, fn) != 0)
		rnd_message(RND_MSG_ERROR, "Failed to load pixmap %s for background image\n", fn);
}

/* ---------------------------------------------------------------------- */

static void set_scroll(int horiz, int pos, int view, int min, int max)
{
	unsigned int sz = view * xrnd.view_zoom;
	if (sz > max)
		sz = max;
	if (pos > max - sz)
		pos = max - sz;
	if (pos < min)
		pos = min;

	xrnd.set_mscroll(horiz, pos, view, min, max, sz);
}

void xrnd_pan_fixup(void)
{
	if (xrnd.dsg == NULL)
		return;

	if (!rnd_conf.editor.unlimited_pan || !xrnd.view_inited) {
		if (xrnd.view_left_x > xrnd.dsg->dwg.X2 + (xrnd.view_width * xrnd.view_zoom))
			xrnd.view_left_x = xrnd.dsg->dwg.X2 + (xrnd.view_width * xrnd.view_zoom);
		if (xrnd.view_top_y > xrnd.dsg->dwg.Y2 + (xrnd.view_height * xrnd.view_zoom))
			xrnd.view_top_y = xrnd.dsg->dwg.Y2 + (xrnd.view_height * xrnd.view_zoom);
		if (xrnd.view_left_x < xrnd.dsg->dwg.X1 - (xrnd.view_width * xrnd.view_zoom))
			xrnd.view_left_x = xrnd.dsg->dwg.X1 - (xrnd.view_width * xrnd.view_zoom);
		if (xrnd.view_top_y < xrnd.dsg->dwg.Y1 - (xrnd.view_height * xrnd.view_zoom))
			xrnd.view_top_y = xrnd.dsg->dwg.Y1 - (xrnd.view_height * xrnd.view_zoom);

		if (!rnd_conf.editor.unlimited_pan) {
			set_scroll(1, xrnd.view_left_x, xrnd.view_width, xrnd.dsg->dwg.X1, xrnd.dsg->dwg.X2);
			set_scroll(0, xrnd.view_top_y, xrnd.view_height, xrnd.dsg->dwg.Y1, xrnd.dsg->dwg.Y2);
		}

		xrnd.view_inited = 1;
	}

	rnd_gui->invalidate_all(rnd_gui);
}


void xrnd_zoom_max(void)
{
	double new_zoom = rnd_dwg_get_size_x(xrnd.dsg) / xrnd.view_width;
	if (new_zoom < rnd_dwg_get_size_y(xrnd.dsg) / xrnd.view_height)
		new_zoom = rnd_dwg_get_size_y(xrnd.dsg) / xrnd.view_height;

	xrnd.view_left_x = xrnd.dsg->dwg.X1 - (xrnd.view_width * new_zoom - rnd_dwg_get_size_x(xrnd.dsg)) / 2;
	xrnd.view_top_y = xrnd.dsg->dwg.Y1 - (xrnd.view_height * new_zoom - rnd_dwg_get_size_y(xrnd.dsg)) / 2;
	xrnd.view_zoom = new_zoom;
	rnd_pixel_slop = xrnd.view_zoom;
	xrnd_pan_fixup();
}


static void zoom_to(double new_zoom, rnd_coord_t x, rnd_coord_t y)
{
	double max_zoom, xfrac, yfrac;
	rnd_coord_t cx, cy;

	if (xrnd.dsg == NULL)
		return;

	xfrac = (double) x / (double) xrnd.view_width;
	yfrac = (double) y / (double) xrnd.view_height;

	if (rnd_conf.editor.view.flip_x)
		xfrac = 1 - xfrac;
	if (rnd_conf.editor.view.flip_y)
		yfrac = 1 - yfrac;

	max_zoom = rnd_dwg_get_size_x(xrnd.dsg) / xrnd.view_width;
	if (max_zoom < rnd_dwg_get_size_y(xrnd.dsg) / xrnd.view_height)
		max_zoom = rnd_dwg_get_size_y(xrnd.dsg) / xrnd.view_height;

	max_zoom *= MAX_ZOOM_SCALE;

	if (new_zoom < 1)
		new_zoom = 1;
	if (new_zoom > max_zoom)
		new_zoom = max_zoom;

	cx = xrnd.view_left_x + xrnd.view_width * xfrac * xrnd.view_zoom;
	cy = xrnd.view_top_y + xrnd.view_height * yfrac * xrnd.view_zoom;

	if (xrnd.view_zoom != new_zoom) {
		xrnd.view_zoom = new_zoom;
		rnd_pixel_slop = xrnd.view_zoom;

		xrnd.view_left_x = cx - xrnd.view_width * xfrac * xrnd.view_zoom;
		xrnd.view_top_y = cy - xrnd.view_height * yfrac * xrnd.view_zoom;
	}
	xrnd_pan_fixup();
}

static void zoom_win(rnd_hid_t *hid, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, int setch)
{
	rnd_coord_t w = x2 - x1, h = y2 - y1;
	double new_zoom = w / xrnd.view_width;
	if (new_zoom < h / xrnd.view_height)
		new_zoom = h / xrnd.view_height;

	if (new_zoom < 1)
		new_zoom = 1;

	if (xrnd.view_zoom != new_zoom) {
		xrnd.view_zoom = new_zoom;
		rnd_pixel_slop = xrnd.view_zoom;
	}

	xrnd.view_left_x = x1;
	xrnd.view_top_y = y1;

	xrnd_pan_fixup();
	if (setch)
		rnd_hidcore_crosshair_move_to(xrnd.dsg, (x1+x2)/2, (y1+y2)/2, 0);
}

void zoom_by(double factor, rnd_coord_t x, rnd_coord_t y)
{
	zoom_to(xrnd.view_zoom * factor, x, y);
}

/* X and Y are in screen coordinates.  */
static void Pan(int mode, rnd_coord_t x, rnd_coord_t y)
{
	static rnd_coord_t ox, oy;
	static rnd_coord_t opx, opy;

	xrnd.panning = mode;

	/* On the first click, we
	   remember the coordinates where we "grabbed" the screen.  */
	if (mode == 1) {
		ox = x;
		oy = y;
		opx = xrnd.view_left_x;
		opy = xrnd.view_top_y;
	}
	/* continued drag, we calculate how far we've moved the cursor and
	   set the position accordingly.  */
	else {
		if (rnd_conf.editor.view.flip_x)
			xrnd.view_left_x = opx + (x - ox) * xrnd.view_zoom;
		else
			xrnd.view_left_x = opx - (x - ox) * xrnd.view_zoom;
		if (rnd_conf.editor.view.flip_y)
			xrnd.view_top_y = opy + (y - oy) * xrnd.view_zoom;
		else
			xrnd.view_top_y = opy - (y - oy) * xrnd.view_zoom;
		xrnd_pan_fixup();
	}
}

static void draw_crosshair(GC xor_gc, int x, int y, int view_width, int view_height)
{
	XDrawLine(xrnd.display, xrnd.window, xor_gc, 0, y, view_width, y);
	XDrawLine(xrnd.display, xrnd.window, xor_gc, x, 0, x, view_height);
}

void xrnd_show_crosshair(int show)
{
	static int showing = 0;
	static int sx, sy;
	static GC xor_gc = 0;
	Pixel crosshair_color;
	static unsigned long cross_color_packed;

	if (!xrnd.crosshair_in_window || !xrnd.window)
		return;
	if ((xor_gc == 0) || (cross_color_packed != rnd_conf.appearance.color.cross.packed)) {
		crosshair_color = xrnd_parse_color(&rnd_conf.appearance.color.cross);
		xor_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
		XSetFunction(xrnd.display, xor_gc, GXxor);
		XSetForeground(xrnd.display, xor_gc, crosshair_color);
		cross_color_packed = rnd_conf.appearance.color.cross.packed;
	}
	if (show == showing)
		return;
	if (show) {
		sx = Vx(xrnd.crosshair_x);
		sy = Vy(xrnd.crosshair_y);
	}
	else
		xrnd.need_idle_proc();
	if (!rnd_conf.editor.hide_hid_crosshair)
		draw_crosshair(xor_gc, sx, sy, xrnd.view_width, xrnd.view_height);
	showing = show;
}

void xrnd_crosshair_move_to(int pos_x, int pos_y)
{
	xrnd.crosshair_in_window = 1;
	if (xrnd.panning)
		xrnd_pan_direct(2, pos_x, pos_y);
	rnd_hidcore_crosshair_move_to(xrnd.dsg, Px(pos_x), Py(pos_y), 1);
}

void xrnd_crosshair_leave(void)
{
	xrnd.crosshair_in_window = 0;
	if (xrnd.crosshair_on && (rnd_app.draw_attached != NULL))
		rnd_app.draw_attached(xrnd.dsg, 1);
	if (rnd_app.draw_marks != NULL)
		rnd_app.draw_marks(xrnd.dsg, 1);
}

void xrnd_crosshair_enter(int ex, int ey)
{
	rnd_hidcore_crosshair_move_to(xrnd.dsg, Px(ex), Py(ey), 1);
}

void xrnd_set_crosshair(rnd_coord_t x, rnd_coord_t y, rnd_set_crosshair_t action, int update_pan, int *in_move_event)
{
	if (xrnd.crosshair_x != x || xrnd.crosshair_y != y) {
		xrnd_show_crosshair(0);
		xrnd.crosshair_x = x;
		xrnd.crosshair_y = y;
		xrnd.need_idle_proc();

		if (update_pan && (x < xrnd.view_left_x || x > xrnd.view_left_x + xrnd.view_width * xrnd.view_zoom || y < xrnd.view_top_y || y > xrnd.view_top_y + xrnd.view_height * xrnd.view_zoom)) {
			xrnd.view_left_x = x - (xrnd.view_width * xrnd.view_zoom) / 2;
			xrnd.view_top_y = y - (xrnd.view_height * xrnd.view_zoom) / 2;
			xrnd_pan_fixup();
		}
	}

	if (action == RND_SC_PAN_VIEWPORT) {
		Window root, child;
		unsigned int keys_buttons;
		int pos_x, pos_y, root_x, root_y;
		XQueryPointer(xrnd.display, xrnd.window, &root, &child, &root_x, &root_y, &pos_x, &pos_y, &keys_buttons);
		if (rnd_conf.editor.view.flip_x)
			xrnd.view_left_x = x - (xrnd.view_width - pos_x) * xrnd.view_zoom;
		else
			xrnd.view_left_x = x - pos_x * xrnd.view_zoom;
		if (rnd_conf.editor.view.flip_y)
			xrnd.view_top_y = y - (xrnd.view_height - pos_y) * xrnd.view_zoom;
		else
			xrnd.view_top_y = y - pos_y * xrnd.view_zoom;
		xrnd_pan_fixup();
		action = RND_SC_WARP_POINTER;
	}
	if (action == RND_SC_WARP_POINTER) {
		if (in_move_event != NULL)
			(*in_move_event)++;
		XWarpPointer(xrnd.display, None, xrnd.window, 0, 0, 0, 0, Vx(x), Vy(y));
		if (in_move_event != NULL)
			(*in_move_event)--;
	}
}

static void work_area_make_pixmaps(Dimension width, Dimension height)
{
	if (xrnd.main_pixmap)
		XFreePixmap(xrnd.display, xrnd.main_pixmap);
	xrnd.main_pixmap = XCreatePixmap(xrnd.display, xrnd.window, width, height, XDefaultDepth(xrnd.display, xrnd.screen));

	if (xrnd.mask_pixmap)
		XFreePixmap(xrnd.display, xrnd.mask_pixmap);
	xrnd.mask_pixmap = XCreatePixmap(xrnd.display, xrnd.window, width, height, XDefaultDepth(xrnd.display, xrnd.screen));
#ifdef RND_HAVE_XRENDER
	if (xrnd.main_picture) {
		XRenderFreePicture(xrnd.display, xrnd.main_picture);
		xrnd.main_picture = 0;
	}
	if (xrnd.mask_picture) {
		XRenderFreePicture(xrnd.display, xrnd.mask_picture);
		xrnd.mask_picture = 0;
	}
	if (xrnd.use_xrender) {
		xrnd.main_picture = XRenderCreatePicture(xrnd.display, xrnd.main_pixmap, XRenderFindVisualFormat(xrnd.display, DefaultVisual(xrnd.display, xrnd.screen)), 0, 0);
		xrnd.mask_picture = XRenderCreatePicture(xrnd.display, xrnd.mask_pixmap, XRenderFindVisualFormat(xrnd.display, DefaultVisual(xrnd.display, xrnd.screen)), 0, 0);
		if (!xrnd.main_picture || !xrnd.mask_picture) {
			xrnd.use_xrender = 0;
			rnd_message(RND_MSG_INFO, "XRENDER: failed to initialize xrender main/mask pixmap; xrender disabled\n");
		}
	}
#endif /* RND_HAVE_XRENDER */

	if (xrnd.mask_bitmap)
		XFreePixmap(xrnd.display, xrnd.mask_bitmap);
	xrnd.mask_bitmap = XCreatePixmap(xrnd.display, xrnd.window, width, height, 1);

	xrnd.pixmap = use_mask() ? xrnd.main_pixmap : xrnd.mask_pixmap;
	xrnd.pixmap_w = width;
	xrnd.pixmap_h = height;
}

void xrnd_work_area_resize(int width, int height, Pixel bgcolor)
{
	XColor color;

	xrnd_show_crosshair(0);

	xrnd.bgcolor = bgcolor;
	xrnd.view_width = width;
	xrnd.view_height = height;

	color.pixel = xrnd.bgcolor;
	XQueryColor(xrnd.display, xrnd.colormap, &color);
	xrnd.bgred = color.red;
	xrnd.bggreen = color.green;
	xrnd.bgblue = color.blue;

	if (!xrnd.window)
		return;

	work_area_make_pixmaps(xrnd.view_width, xrnd.view_height);

	zoom_by(1, 0, 0);
}

void xrnd_work_area_first_expose(Window win, int width, int height, Pixel bgcolor)
{
	xrnd.window = win;
	xrnd.view_width = width;
	xrnd.view_height = height;

	if (bgcolor == 0)
		bgcolor = xrnd_parse_color(&rnd_conf.appearance.color.background);

	xrnd.bgcolor = bgcolor;

	xrnd.offlimit_color = xrnd_parse_color(&rnd_conf.appearance.color.off_limit);
	xrnd.grid_color = xrnd_parse_color(&rnd_conf.appearance.color.grid);

	xrnd.my_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);

	xrnd.bg_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
	XSetForeground(xrnd.display, xrnd.bg_gc, xrnd.bgcolor);

	work_area_make_pixmaps(width, height);

#ifdef RND_HAVE_XRENDER
	if (xrnd.use_xrender) {
		double l_alpha = rnd_conf.appearance.layer_alpha;
		XRenderPictureAttributes pa;
		XRenderColor a = { 0, 0, 0,  0x8000};
		
		if (l_alpha < 0)
			l_alpha = 0;
		else if (l_alpha > 1)
			l_alpha = 1;
		a.alpha = (int)(l_alpha * (double)0xFFFF);

		xrnd.pale_pixmap = XCreatePixmap(xrnd.display, xrnd.window, 1, 1, 8);
		pa.repeat = True;
		xrnd.pale_picture = XRenderCreatePicture(xrnd.display, xrnd.pale_pixmap, XRenderFindStandardFormat(xrnd.display, PictStandardA8), CPRepeat, &pa);
		if (xrnd.pale_picture) {
			XRenderFillRectangle(xrnd.display, PictOpSrc, xrnd.pale_picture, &a, 0, 0, 1, 1);
		}
		else {
			xrnd.use_xrender = 0;
			rnd_message(RND_MSG_INFO, "XRENDER: failed to initialize xrender pale_picture; xrender disabled\n");
		}
	}
#endif /* RND_HAVE_XRENDER */

	xrnd.clip_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
	xrnd.pxm_clip_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
	xrnd.bset_gc = XCreateGC(xrnd.display, xrnd.mask_bitmap, 0, 0);
	XSetForeground(xrnd.display, xrnd.bset_gc, 1);
	xrnd.bclear_gc = XCreateGC(xrnd.display, xrnd.mask_bitmap, 0, 0);
	XSetForeground(xrnd.display, xrnd.bclear_gc, 0);
}

void xrnd_work_area_expose(int x, int y, int width, int height)
{
	xrnd_show_crosshair(0);
	XSetFunction(xrnd.display, xrnd.my_gc, GXcopy);
	XCopyArea(xrnd.display, xrnd.main_pixmap, xrnd.window, xrnd.my_gc, x, y, width, height, x, y);
	xrnd_show_crosshair(1);
}


void xrnd_init(Display *display, int screen, rnd_hid_t *myhid, xrnd_set_main_scroll_t set_mscroll, xrnd_need_idle_proc_t need_idle_proc)
{
	int render_event, render_error;

	xrnd.display = display;
	xrnd.screen = screen;
	xrnd.hid = myhid;
	xrnd.set_mscroll = set_mscroll;
	xrnd.need_idle_proc = need_idle_proc;

	xrnd.view_zoom = RND_MIL_TO_COORD(10);
	xrnd.crosshair_in_window = 1;
	xrnd.crosshair_on = rnd_true;

	xrnd.colormap = XDefaultColormap(xrnd.display, screen);
#ifdef RND_HAVE_XRENDER
	xrnd.use_xrender =
		XRenderQueryExtension(xrnd.display, &render_event, &render_error) &&
		XRenderFindVisualFormat(xrnd.display, DefaultVisual(xrnd.display, screen));
#ifdef RND_HAVE_XINERAMA
	/* Xinerama and XRender don't get along well */
	if (xrnd.use_xrender) {
		if (XineramaQueryExtension(xrnd.display, &render_event, &render_error) && XineramaIsActive(xrnd.display)) {
			xrnd.use_xrender = 0;
			rnd_message(RND_MSG_INFO, "XRENDER: since xinerama is also active, xrender has to be disabled\n");
		}
	}
	else
		rnd_message(RND_MSG_INFO, "XRENDER: not available\n");
#endif /* RND_HAVE_XINERAMA */
#endif /* RND_HAVE_XRENDER */


	if (xrnd.use_xrender)
		rnd_message(RND_MSG_INFO, "XRENDER: enabled\n");
}

#define THRESHOLD ((rnd_coord_t)(RND_COORD_MAX/2-1))

static void draw_grid()
{
	static XPoint *points = 0, *points3 = 0;
	static int npoints = 0, npoints3 = 0;
	rnd_coord_t grd, x1, y1, x2, y2, prevx, prevx3;
	rnd_coord_t x, y;
	int n, n3;
	static GC grid_gc = 0;

	if (!rnd_conf.editor.draw_grid)
		return;

	grd = xrnd.dsg->grid;
	if (grd <= 0)
		grd = 1;

	if (Vz(grd) < RND_MIN_GRID_DISTANCE) {
		if (!rnd_conf.editor.global_grid.sparse)
			return;
		grd *= (rnd_conf.editor.global_grid.min_dist_px / Vz(grd));
	}

	if (!grid_gc) {
		grid_gc = XCreateGC(xrnd.display, xrnd.window, 0, 0);
		XSetFunction(xrnd.display, grid_gc, GXxor);
		XSetForeground(xrnd.display, grid_gc, xrnd.grid_color);
	}
	if (rnd_conf.editor.view.flip_x) {
		x2 = rnd_grid_fit(Px(0), grd, xrnd.dsg->grid_ox);
		x1 = rnd_grid_fit(Px(xrnd.view_width), grd, xrnd.dsg->grid_ox);
		if (Vx(x2) < 0)
			x2 -= grd;
		if (Vx(x1) >= xrnd.view_width)
			x1 += grd;
	}
	else {
		x1 = rnd_grid_fit(Px(0), grd, xrnd.dsg->grid_ox);
		x2 = rnd_grid_fit(Px(xrnd.view_width), grd, xrnd.dsg->grid_ox);
		if (Vx(x1) < 0)
			x1 += grd;
		if (Vx(x2) >= xrnd.view_width)
			x2 -= grd;
	}
	if (rnd_conf.editor.view.flip_y) {
		y2 = rnd_grid_fit(Py(0), grd, xrnd.dsg->grid_oy);
		y1 = rnd_grid_fit(Py(xrnd.view_height), grd, xrnd.dsg->grid_oy);
		if (Vy(y2) < 0)
			y2 -= grd;
		if (Vy(y1) >= xrnd.view_height)
			y1 += grd;
	}
	else {
		y1 = rnd_grid_fit(Py(0), grd, xrnd.dsg->grid_oy);
		y2 = rnd_grid_fit(Py(xrnd.view_height), grd, xrnd.dsg->grid_oy);
		if (Vy(y1) < 0)
			y1 += grd;
		if (Vy(y2) >= xrnd.view_height)
			y2 -= grd;
	}

	/* one point in the center of the crossing */
	if ((x1 < -THRESHOLD) && (x2 > THRESHOLD)) {
		/* With large negative x1 and large positive x2 the expression (x2-x1)
		   would overflow and n would be very small. In that case rather divide
		   each component and combine the result. We need +2 instead of +1 because
		   each division may round down */
		n = (x2/grd) - (x1/grd) + 2;
	}
	else
		n = (x2 - x1) / grd + 1;

	if ((n+10) > npoints) {
		npoints = n + 10;
		points = (XPoint *) realloc(points, npoints * sizeof(XPoint));
	}

	if (rnd_conf.editor.cross_grid) {
		/* two points per grid crossing in x.x pattern */
		n3 = n*2;
		if ((n3+30) > npoints3) {
			npoints3 = n3 + 30;
			points3 = (XPoint *) realloc(points3, npoints3 * sizeof(XPoint));
		}
	}

	n3 = n = 0;
	prevx = 0;
	for (x = x1; x <= x2; x += grd) {
		int temp = Vx(x);
		points[n].x = temp;
		if (n) {
			points[n].x -= prevx;
			points[n].y = 0;
		}

		if (rnd_conf.editor.cross_grid) {
			points3[n3].x = temp-1;
			if (n) {
				points3[n3].x -= prevx3;
				points3[n3].y = 0;
			}
			n3++;
			points3[n3].x = 2;
			points3[n3].y = 0;
			n3++;
		}

		prevx3 = temp-1+2;
		prevx = temp;
		n++;
	}
	for (y = y1; y <= y2; y += grd) {
		int vy = Vy(y);
		points[0].y = vy;
		XDrawPoints(xrnd.display, xrnd.pixmap, grid_gc, points, n, CoordModePrevious);

		if (rnd_conf.editor.cross_grid) {
			points3[0].y = vy;
			XDrawPoints(xrnd.display, xrnd.pixmap, grid_gc, points3, n3, CoordModePrevious);
			points[0].y = vy-1;
			XDrawPoints(xrnd.display, xrnd.pixmap, grid_gc, points, n, CoordModePrevious);
			points[0].y = vy+1;
			XDrawPoints(xrnd.display, xrnd.pixmap, grid_gc, points, n, CoordModePrevious);
		}
	}
}

#undef THRESHOLD

void xrnd_redraw_main(rnd_hid_t *hid)
{
	int mx, my;
	rnd_hid_expose_ctx_t ctx;

	xrnd.pixmap = xrnd.main_pixmap;
	mx = xrnd.view_width;
	my = xrnd.view_height;
	ctx.design = xrnd.dsg;
	ctx.view.X1 = Px(0);
	ctx.view.Y1 = Py(0);
	ctx.view.X2 = Px(xrnd.view_width);
	ctx.view.Y2 = Py(xrnd.view_height);
	if (rnd_conf.editor.view.flip_x) {
		rnd_coord_t tmp = ctx.view.X1;
		ctx.view.X1 = ctx.view.X2;
		ctx.view.X2 = tmp;
	}
	if (rnd_conf.editor.view.flip_y) {
		rnd_coord_t tmp = ctx.view.Y1;
		ctx.view.Y1 = ctx.view.Y2;
		ctx.view.Y2 = tmp;
	}
	XSetForeground(xrnd.display, xrnd.bg_gc, xrnd.bgcolor);
	XFillRectangle(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, 0, 0, mx, my);

	if (ctx.view.X1 < xrnd.dsg->dwg.X1 || ctx.view.Y1 < xrnd.dsg->dwg.Y1 || ctx.view.X2 > xrnd.dsg->dwg.X2 || ctx.view.Y2 > xrnd.dsg->dwg.Y2) {
		int leftmost, rightmost, topmost, bottommost;

		leftmost = Vx(xrnd.dsg->dwg.X1);
		rightmost = Vx(xrnd.dsg->dwg.X2);
		topmost = Vy(xrnd.dsg->dwg.Y1);
		bottommost = Vy(xrnd.dsg->dwg.Y2);
		if (leftmost > rightmost) {
			int t = leftmost;
			leftmost = rightmost;
			rightmost = t;
		}
		if (topmost > bottommost) {
			int t = topmost;
			topmost = bottommost;
			bottommost = t;
		}
		if (leftmost < 0)
			leftmost = 0;
		if (topmost < 0)
			topmost = 0;
		if (rightmost > xrnd.view_width)
			rightmost = xrnd.view_width;
		if (bottommost > xrnd.view_height)
			bottommost = xrnd.view_height;

		XSetForeground(xrnd.display, xrnd.bg_gc, xrnd.offlimit_color);

		/* L T R
		   L x R
		   L B R */

		if (leftmost > 0) {
			XFillRectangle(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, 0, 0, leftmost, xrnd.view_height);
		}
		if (rightmost < xrnd.view_width) {
			XFillRectangle(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, rightmost + 1, 0, xrnd.view_width - rightmost + 1, xrnd.view_height);
		}
		if (topmost > 0) {
			XFillRectangle(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, leftmost, 0, rightmost - leftmost + 1, topmost);
		}
		if (bottommost < xrnd.view_height) {
			XFillRectangle(xrnd.display, xrnd.main_pixmap, xrnd.bg_gc, leftmost, bottommost + 1,
										 rightmost - leftmost + 1, xrnd.view_height - bottommost + 1);
		}
	}
	DrawBackgroundImage();
	ctx.coord_per_pix = xrnd.view_zoom;
	rnd_app.expose_main(hid, &ctx, NULL);
	xrnd.drawing_mode = RND_HID_COMP_POSITIVE;
	draw_grid();
	xrnd_show_crosshair(0); /* To keep the drawn / not drawn info correct */
	XSetFunction(xrnd.display, xrnd.my_gc, GXcopy);
	XCopyArea(xrnd.display, xrnd.main_pixmap, xrnd.window, xrnd.my_gc, 0, 0, xrnd.view_width, xrnd.view_height, 0, 0);
	xrnd.pixmap = xrnd.window;
	xrnd.need_redraw = 0;
	if (xrnd.crosshair_on) {
		if (rnd_app.draw_attached != NULL)
			rnd_app.draw_attached(xrnd.dsg, 1);
		if (rnd_app.draw_marks != NULL)
			rnd_app.draw_marks(xrnd.dsg, 1);
	}
}

/*** low level draw ***/

rnd_hid_gc_t xrnd_make_gc(rnd_hid_t *hid)
{
	rnd_hid_gc_t rv = (rnd_hid_gc_s *) malloc(sizeof(rnd_hid_gc_s));
	memset(rv, 0, sizeof(rnd_hid_gc_s));
	rv->me_pointer = hid;
	return rv;
}

void xrnd_destroy_gc(rnd_hid_gc_t gc)
{
	free(gc);
}

void xrnd_render_burst(rnd_hid_t *hid, rnd_burst_op_t op, const rnd_box_t *screen)
{
	rnd_gui->coord_per_pix = xrnd.view_zoom;
}

void xrnd_set_drawing_mode(rnd_hid_t *hid, rnd_composite_op_t op, rnd_bool direct, const rnd_box_t *drw_screen)
{
	xrnd.drawing_mode = op;

	xrnd.direct = direct;
	if (direct) {
		xrnd.pixmap = xrnd.main_pixmap;
		return;
	}

	switch(op) {
		case RND_HID_COMP_RESET:
			if (xrnd.mask_pixmap == 0) {
				xrnd.mask_pixmap = XCreatePixmap(xrnd.display, xrnd.window, xrnd.pixmap_w, xrnd.pixmap_h, XDefaultDepth(xrnd.display, xrnd.screen));
				xrnd.mask_bitmap = XCreatePixmap(xrnd.display, xrnd.window, xrnd.pixmap_w, xrnd.pixmap_h, 1);
			}
			xrnd.pixmap = xrnd.mask_pixmap;
			XSetForeground(xrnd.display, xrnd.my_gc, 0);
			XSetFunction(xrnd.display, xrnd.my_gc, GXcopy);
			XFillRectangle(xrnd.display, xrnd.mask_pixmap, xrnd.my_gc, 0, 0, xrnd.view_width, xrnd.view_height);
			XFillRectangle(xrnd.display, xrnd.mask_bitmap, xrnd.bclear_gc, 0, 0, xrnd.view_width, xrnd.view_height);
			xrnd.mask_gc = xrnd.bset_gc;
			break;

		case RND_HID_COMP_POSITIVE:
		case RND_HID_COMP_POSITIVE_XOR:
			xrnd.mask_gc = xrnd.bset_gc;
			break;

		case RND_HID_COMP_NEGATIVE:
			xrnd.mask_gc = xrnd.bclear_gc;
			break;

		case RND_HID_COMP_FLUSH:
			xrnd.pixmap = xrnd.main_pixmap;

			/* blit back the result */
#ifdef RND_HAVE_XRENDER
			if (xrnd.use_xrender) {
				XRenderPictureAttributes pa;

				pa.clip_mask = xrnd.mask_bitmap;
				XRenderChangePicture(xrnd.display, xrnd.main_picture, CPClipMask, &pa);
				XRenderComposite(xrnd.display, PictOpOver, xrnd.mask_picture, xrnd.pale_picture,
					xrnd.main_picture, 0, 0, 0, 0, 0, 0, xrnd.view_width, xrnd.view_height);
			}
			else
#endif /* RND_HAVE_XRENDER */
			{
				XSetClipMask(xrnd.display, xrnd.clip_gc, xrnd.mask_bitmap);
				XCopyArea(xrnd.display, xrnd.mask_pixmap, xrnd.main_pixmap, xrnd.clip_gc, 0, 0, xrnd.view_width, xrnd.view_height, 0, 0);
			}
			break;
	}
}


typedef struct xrnd_color_cache_s {
	Pixel pix;
} xrnd_color_cache_t;

void xrnd_set_color(rnd_hid_gc_t gc, const rnd_color_t *pcolor)
{
	xrnd_color_cache_t *cc;

	if (!xrnd.display)
		return;
	if ((pcolor == NULL) || (*pcolor->str == '\0'))
		pcolor = rnd_color_magenta;

	gc->pcolor = *pcolor;

	if (!xrnd.ccache_inited) {
		rnd_clrcache_init(&xrnd.ccache, sizeof(xrnd_color_cache_t), NULL);
		xrnd.ccache_inited = 1;
	}

	if (rnd_color_is_drill(pcolor)) {
		gc->color = xrnd.offlimit_color;
		gc->erase = 0;
	}
	else if ((cc = rnd_clrcache_get(&xrnd.ccache, pcolor, 0)) != NULL) {
		gc->color = cc->pix;
		gc->erase = 0;
	}
	else {
		cc = rnd_clrcache_get(&xrnd.ccache, pcolor, 1);
		cc->pix = xrnd_parse_color(pcolor);
		render_trace("xrnd_set_color `%s' %08x rgb/%d/%d/%d\n", pcolor->str, cc->pix, pcolor->r, pcolor->g, pcolor->b);
		gc->color = cc->pix;
		gc->erase = 0;
	}
}

static void set_gc(rnd_hid_gc_t gc)
{
	int cap, join, width;

	if (gc->me_pointer != xrnd.hid) {
		fprintf(stderr, "Fatal: GC from another HID passed to lesstif HID\n");
		abort();
	}

	render_trace("set_gc c%s w%#mS c%d x%d e%d\n", gc->pcolor.str, gc->width, gc->cap, gc->xor_set, gc->erase);
	switch (gc->cap) {
	case rnd_cap_square:
		cap = CapProjecting;
		join = JoinMiter;
		break;
	case rnd_cap_round:
		cap = CapRound;
		join = JoinRound;
		break;
	default:
		assert(!"unhandled cap");
		cap = CapRound;
		join = JoinRound;
	}
	if (gc->xor_set) {
		XSetFunction(xrnd.display, xrnd.my_gc, GXxor);
		XSetForeground(xrnd.display, xrnd.my_gc, gc->color ^ xrnd.bgcolor);
	}
	else if (gc->erase) {
		XSetFunction(xrnd.display, xrnd.my_gc, GXcopy);
		XSetForeground(xrnd.display, xrnd.my_gc, xrnd.offlimit_color);
	}
	else {
		XSetFunction(xrnd.display, xrnd.my_gc, GXcopy);
		XSetForeground(xrnd.display, xrnd.my_gc, gc->color);
	}
	width = Vw(gc->width);
	if (width < 0)
		width = 0;
	XSetLineAttributes(xrnd.display, xrnd.my_gc, width, LineSolid, cap, join);
	if (use_mask()) {
		XSetLineAttributes(xrnd.display, xrnd.mask_gc, width, LineSolid, cap, join);
	}
}

void xrnd_set_line_cap(rnd_hid_gc_t gc, rnd_cap_style_t style)
{
	gc->cap = style;
}

void xrnd_set_line_width(rnd_hid_gc_t gc, rnd_coord_t width)
{
	gc->width = width;
}

void xrnd_set_draw_xor(rnd_hid_gc_t gc, int xor_set)
{
	gc->xor_set = xor_set;
}

#define ISORT(a,b) if (a>b) { a^=b; b^=a; a^=b; }

void xrnd_draw_line(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	double dx1, dy1, dx2, dy2;
	int vw = Vw(gc->width);

	render_trace("draw_line %#mD-%#mD @%#mS", x1, y1, x2, y2, gc->width);
	dx1 = Vx(x1);
	dy1 = Vy(y1);
	dx2 = Vx(x2);
	dy2 = Vy(y2);

	render_trace(" = %#mD-%#mD %s\n", x1, y1, x2, y2, gc->pcolor.str);

	if (!rnd_line_clip(0, 0, xrnd.view_width, xrnd.view_height, &dx1, &dy1, &dx2, &dy2, vw))
		return;

	x1 = dx1;
	y1 = dy1;
	x2 = dx2;
	y2 = dy2;

	set_gc(gc);
	if (gc->cap == rnd_cap_square && x1 == x2 && y1 == y2) {
		XFillRectangle(xrnd.display, xrnd.pixmap, xrnd.my_gc, x1 - vw / 2, y1 - vw / 2, vw, vw);
		if (use_mask())
			XFillRectangle(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, x1 - vw / 2, y1 - vw / 2, vw, vw);
	}
	else {
		XDrawLine(xrnd.display, xrnd.pixmap, xrnd.my_gc, x1, y1, x2, y2);
		if (use_mask())
			XDrawLine(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, x1, y1, x2, y2);
	}
}

void xrnd_draw_arc(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t width, rnd_coord_t height, rnd_angle_t start_angle, rnd_angle_t delta_angle)
{

	render_trace("draw_arc %#mD %#mSx%#mS s %d d %d", cx, cy, width, height, start_angle, delta_angle);
	width = Vz(width);
	height = Vz(height);
	cx = Vx(cx) - width;
	cy = Vy(cy) - height;

	if ((delta_angle >= 360.0) || (delta_angle <= -360.0)) {
		start_angle = 0;
		delta_angle = 360;
	}

	if (rnd_conf.editor.view.flip_x) {
		start_angle = 180 - start_angle;
		delta_angle = -delta_angle;
	}
	if (rnd_conf.editor.view.flip_y) {
		start_angle = -start_angle;
		delta_angle = -delta_angle;
	}
	start_angle = rnd_normalize_angle(start_angle);
	if (start_angle >= 180)
		start_angle -= 360;

	render_trace(" = %#mD %#mSx%#mS %d %s\n", cx, cy, width, height, gc->width, gc->pcolor.str);
	set_gc(gc);
	XDrawArc(xrnd.display, xrnd.pixmap, xrnd.my_gc, cx, cy, width * 2, height * 2, (start_angle + 180) * 64, delta_angle * 64);
	if (use_mask())
		XDrawArc(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, cx, cy, width * 2, height * 2, (start_angle + 180) * 64, delta_angle * 64);
}

void xrnd_draw_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	int vw = Vw(gc->width);
	x1 = Vx(x1);
	y1 = Vy(y1);
	x2 = Vx(x2);
	y2 = Vy(y2);
	if (x1 < -vw && x2 < -vw)
		return;
	if (y1 < -vw && y2 < -vw)
		return;
	if (x1 > xrnd.view_width + vw && x2 > xrnd.view_width + vw)
		return;
	if (y1 > xrnd.view_height + vw && y2 > xrnd.view_height + vw)
		return;
	if (x1 > x2) {
		rnd_coord_t xt = x1;
		x1 = x2;
		x2 = xt;
	}
	if (y1 > y2) {
		int yt = y1;
		y1 = y2;
		y2 = yt;
	}
	set_gc(gc);
	XDrawRectangle(xrnd.display, xrnd.pixmap, xrnd.my_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
	if (use_mask())
		XDrawRectangle(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}

void xrnd_fill_circle(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t radius)
{

	render_trace("fill_circle %#mD %#mS", cx, cy, radius);
	radius = Vz(radius);
	cx = Vx(cx) - radius;
	cy = Vy(cy) - radius;
	if (cx < -2 * radius || cx > xrnd.view_width)
		return;
	if (cy < -2 * radius || cy > xrnd.view_height)
		return;

	render_trace(" = %#mD %#mS %s\n", cx, cy, radius, gc->pcolor.str);
	set_gc(gc);
	XFillArc(xrnd.display, xrnd.pixmap, xrnd.my_gc, cx, cy, radius * 2, radius * 2, 0, 360 * 64);
	if (use_mask())
		XFillArc(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, cx, cy, radius * 2, radius * 2, 0, 360 * 64);
}

/* Intentaional code duplication for performance */
void xrnd_fill_polygon(rnd_hid_gc_t gc, int n_coords, rnd_coord_t * x, rnd_coord_t * y)
{
	static XPoint *p = 0;
	static int maxp = 0;
	int i;

	if (maxp < n_coords) {
		maxp = n_coords + 10;
		if (p)
			p = (XPoint *) realloc(p, maxp * sizeof(XPoint));
		else
			p = (XPoint *) malloc(maxp * sizeof(XPoint));
	}

	for (i = 0; i < n_coords; i++) {
		p[i].x = Vx(x[i]);
		p[i].y = Vy(y[i]);
	}

	render_trace("fill_polygon %d pts\n", n_coords);
	set_gc(gc);
	XFillPolygon(xrnd.display, xrnd.pixmap, xrnd.my_gc, p, n_coords, Complex, CoordModeOrigin);
	if (use_mask())
		XFillPolygon(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, p, n_coords, Complex, CoordModeOrigin);
}

/* Intentaional code duplication for performance */
void xrnd_fill_polygon_offs(rnd_hid_gc_t gc, int n_coords, rnd_coord_t *x, rnd_coord_t *y, rnd_coord_t dx, rnd_coord_t dy)
{
	static XPoint *p = 0;
	static int maxp = 0;
	int i;

	if (maxp < n_coords) {
		maxp = n_coords + 10;
		if (p)
			p = (XPoint *) realloc(p, maxp * sizeof(XPoint));
		else
			p = (XPoint *) malloc(maxp * sizeof(XPoint));
	}

	for (i = 0; i < n_coords; i++) {
		p[i].x = Vx(x[i] + dx);
		p[i].y = Vy(y[i] + dy);
	}

	set_gc(gc);
	XFillPolygon(xrnd.display, xrnd.pixmap, xrnd.my_gc, p, n_coords, Complex, CoordModeOrigin);
	if (use_mask())
		XFillPolygon(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, p, n_coords, Complex, CoordModeOrigin);
}

void xrnd_fill_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	int vw = Vw(gc->width);
	x1 = Vx(x1);
	y1 = Vy(y1);
	x2 = Vx(x2);
	y2 = Vy(y2);
	if (x1 < -vw && x2 < -vw)
		return;
	if (y1 < -vw && y2 < -vw)
		return;
	if (x1 > xrnd.view_width + vw && x2 > xrnd.view_width + vw)
		return;
	if (y1 > xrnd.view_height + vw && y2 > xrnd.view_height + vw)
		return;
	if (x1 > x2) {
		rnd_coord_t xt = x1;
		x1 = x2;
		x2 = xt;
	}
	if (y1 > y2) {
		int yt = y1;
		y1 = y2;
		y2 = yt;
	}
	set_gc(gc);
	XFillRectangle(xrnd.display, xrnd.pixmap, xrnd.my_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
	if (use_mask())
		XFillRectangle(xrnd.display, xrnd.mask_bitmap, xrnd.mask_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}

/*** preview render ***/

void xrnd_render_preview(rnd_hid_t *hid, Display *display, Window window, int clrdepth, GC gc, int v_width, int v_height, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, double zoom, rnd_hid_expose_ctx_t *expctx, rnd_bool_t flip_local, rnd_bool_t flip_global, rnd_bool_t flip_localx, rnd_bool_t flip_localy)
{
	int save_vx, save_vy, save_vw, save_vh;
	int save_fx, save_fy;
	double save_vz;
	Pixmap save_px, save_main_px, save_mask_px, save_mask_bm;
	rnd_coord_t save_cpp;

	save_vx = xrnd.view_left_x;
	save_vy = xrnd.view_top_y;
	save_vz = xrnd.view_zoom;
	save_vw = xrnd.view_width;
	save_vh = xrnd.view_height;
	save_fx = rnd_conf.editor.view.flip_x;
	save_fy = rnd_conf.editor.view.flip_y;
	save_px = xrnd.main_pixmap;
	save_main_px = xrnd.main_pixmap;
	save_mask_px = xrnd.mask_pixmap;
	save_mask_bm = xrnd.mask_bitmap;
	save_cpp = rnd_gui->coord_per_pix;
	xrnd.main_pixmap = XCreatePixmap(xrnd.display, window, v_width, v_height, clrdepth);
	xrnd.mask_pixmap = XCreatePixmap(xrnd.display, window, v_width, v_height, clrdepth);
	xrnd.mask_bitmap = XCreatePixmap(xrnd.display, window, v_width, v_height, 1);
	xrnd.pixmap = xrnd.main_pixmap;
	xrnd.view_left_x = x1;
	xrnd.view_top_y = y1;
	xrnd.view_zoom = zoom;
	xrnd.view_width = x2;
	xrnd.view_height = y2;

	/* apply flip */
	if (flip_local) {
		rnd_conf_force_set_bool(rnd_conf.editor.view.flip_x, flip_localx);
		rnd_conf_force_set_bool(rnd_conf.editor.view.flip_y, flip_localy);
	}
	else if (!flip_global) {
		rnd_conf_force_set_bool(rnd_conf.editor.view.flip_x, 0);
		rnd_conf_force_set_bool(rnd_conf.editor.view.flip_y, 0);
	}

	XFillRectangle(xrnd.display, xrnd.pixmap, xrnd.bg_gc, 0, 0, v_width, v_height);

	expctx->view.X1 = x1;
	expctx->view.Y1 = y1;
	expctx->view.X2 = x2;
	expctx->view.Y2 = y2;

	expctx->coord_per_pix = xrnd.view_zoom;
	rnd_gui->coord_per_pix = xrnd.view_zoom;
	rnd_app.expose_preview(hid, expctx);

	XCopyArea(xrnd.display, xrnd.pixmap, window, gc, 0, 0, v_width, v_height, 0, 0);

	xrnd.view_left_x = save_vx;
	xrnd.view_top_y = save_vy;
	xrnd.view_zoom = save_vz;
	xrnd.view_width = save_vw;
	xrnd.view_height = save_vh;
	XFreePixmap(xrnd.display, xrnd.main_pixmap);
	XFreePixmap(xrnd.display, xrnd.mask_pixmap);
	XFreePixmap(xrnd.display, xrnd.mask_bitmap);
	xrnd.main_pixmap = save_main_px;
	xrnd.mask_pixmap = save_mask_px;
	xrnd.mask_bitmap = save_mask_bm;
	xrnd.pixmap = save_px;
	rnd_gui->coord_per_pix = save_cpp;
	rnd_conf_force_set_bool(rnd_conf.editor.view.flip_x, save_fx);
	rnd_conf_force_set_bool(rnd_conf.editor.view.flip_y, save_fy);
}


/*** High level zoom/pan (HID API) ***/

void xrnd_zoom_win(rnd_hid_t *hid, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, rnd_bool set_crosshair)
{
	zoom_win(hid, x1, y1, x2, y2, 1);
}

void xrnd_zoom(rnd_hid_t *hid, rnd_coord_t center_x, rnd_coord_t center_y, double factor, int relative)
{
	if (relative)
		zoom_by(factor, Vx(center_x), Vy(center_y));
	else
		zoom_to(factor, Vx(center_x), Vy(center_y));
}

void xrnd_pan(rnd_hid_t *hid, rnd_coord_t x, rnd_coord_t y, int relative)
{
	if (relative) {
		xrnd.view_left_x += x;
		xrnd.view_top_y += y;
		xrnd_pan_fixup();
	}
	else {
		xrnd.view_left_x = x - (xrnd.view_width * xrnd.view_zoom) / 2;
		xrnd.view_top_y = y - (xrnd.view_height * xrnd.view_zoom) / 2;
		xrnd_pan_fixup();
		XWarpPointer(xrnd.display, xrnd.window, xrnd.window, 0, 0, xrnd.view_width, xrnd.view_height, Vx(x), Vy(y));
	}
}

void xrnd_pan_mode(rnd_hid_t *hid, rnd_coord_t x, rnd_coord_t y, rnd_bool mode)
{
	Pan(mode, Vx(x), Vy(y));
}

void xrnd_pan_direct(rnd_bool mode, rnd_coord_t x, rnd_coord_t y)
{
	Pan(mode, x, y);
}


void xrnd_view_get(rnd_hid_t *hid, rnd_box_t *viewbox)
{
	viewbox->X1 = xrnd.view_left_x;
	viewbox->Y1 = xrnd.view_top_y;
	viewbox->X2 = rnd_round(xrnd.view_left_x + xrnd.view_width * xrnd.view_zoom);
	viewbox->Y2 = rnd_round(xrnd.view_top_y + xrnd.view_height * xrnd.view_zoom);
}

void xrnd_notify_crosshair_change(rnd_hid_t *hid, rnd_bool changes_complete)
{
	static int invalidate_depth = 0;
	Pixmap save_pixmap;

	if (!xrnd.my_gc)
		return;

	if (changes_complete)
		invalidate_depth--;

	if (invalidate_depth < 0) {
		invalidate_depth = 0;
		/* A mismatch of changes_complete == rnd_false and == rnd_true notifications
		 * is not expected to occur, but we will try to handle it gracefully.
		 * As we know the crosshair will have been shown already, we must
		 * repaint the entire view to be sure not to leave an artaefact.
		 */
		xrnd.need_redraw = 1;
		xrnd.need_idle_proc();
		return;
	}

	if (invalidate_depth == 0 && xrnd.crosshair_on) {
		rnd_hid_t *hid_save;
		XRND_SAVE_HID();
		save_pixmap = xrnd.pixmap;
		xrnd.pixmap = xrnd.window;
		if (rnd_app.draw_attached != NULL)
			rnd_app.draw_attached(xrnd.dsg, 1);
		xrnd.pixmap = save_pixmap;
		XRND_RESTORE_HID();
	}

	if (!changes_complete)
		invalidate_depth++;
}

void xrnd_notify_mark_change(rnd_hid_t *hid, rnd_bool changes_complete)
{
	static int invalidate_depth = 0;

	if (changes_complete)
		invalidate_depth--;

	if (invalidate_depth < 0) {
		invalidate_depth = 0;
		/* A mismatch of changes_complete == rnd_false and == rnd_true notifications
		 * is not expected to occur, but we will try to handle it gracefully.
		 * As we know the mark will have been shown already, we must
		 * repaint the entire view to be sure not to leave an artaefact.
		 */
		xrnd.need_idle_proc();
		return;
	}

	if ((invalidate_depth == 0) && xrnd.crosshair_on)
		xrnd_draw_marks();

	if (!changes_complete)
		invalidate_depth++;
}

/*** misc ***/
void xrnd_draw_marks(void)
{
		Pixmap save_pixmap;

		save_pixmap = xrnd.pixmap;
		xrnd.pixmap = xrnd.window;
		if (rnd_app.draw_marks != NULL)
			rnd_app.draw_marks(xrnd.dsg, 1);
		xrnd.pixmap = save_pixmap;
}
