/* -- xinerama.c -- */

#include "x11vnc.h"
#include "xwrappers.h"
#include "blackout_t.h"
#include "scan.h"

/*
 * routines related to xinerama and blacking out rectangles
 */

/* blacked-out region (-blackout, -xinerama) */

#define BLACKR_MAX 100
blackout_t blackr[BLACKR_MAX];	/* hardwired max blackouts */
tile_blackout_t *tile_blackout;
int blackouts = 0;

void initialize_blackouts_and_xinerama(void);
void push_sleep(int n);
void push_black_screen(int n);
void refresh_screen(int push);
void zero_fb(int x1, int y1, int x2, int y2);


static void initialize_blackouts(char *list);
static void blackout_tiles(void);
static void initialize_xinerama (void);


/*
 * Take a comma separated list of geometries: WxH+X+Y and register them as
 * rectangles to black out from the screen.
 */
static void initialize_blackouts(char *list) {
	char *p, *blist = strdup(list);
	int x, y, X, Y, h, w, t;

	p = strtok(blist, ", \t");
	while (p) {
		if (!strcmp("noptr", p)) {
			blackout_ptr = 1;
			rfbLog("pointer will be blocked from blackout "
			    "regions\n");
			p = strtok(NULL, ", \t");
			continue;
		}
		if (! parse_geom(p, &w, &h, &x, &y, dpy_x, dpy_y)) {
			if (*p != '\0') {
				rfbLog("skipping invalid geometry: %s\n", p);
			}
			p = strtok(NULL, ", \t");
			continue;
		}
		w = nabs(w);
		h = nabs(h);
		x = nfix(x, dpy_x);
		y = nfix(y, dpy_y);
		X = x + w;
		Y = y + h;
		X = nfix(X, dpy_x+1);
		Y = nfix(Y, dpy_y+1);
		if (x > X) {
			t = X; X = x; x = t;
		}
		if (y > Y) {
			t = Y; Y = y; y = t;
		}
		if (x < 0 || x > dpy_x || y < 0 || y > dpy_y ||
		    X < 0 || X > dpy_x || Y < 0 || Y > dpy_y ||
		    x == X || y == Y) {
			rfbLog("skipping invalid blackout geometry: %s x="
			    "%d-%d,y=%d-%d,w=%d,h=%d\n", p, x, X, y, Y, w, h);
		} else {
			rfbLog("blackout rect: %s: x=%d-%d y=%d-%d\n", p,
			    x, X, y, Y);

			/*
			 * note that the black out is x1 <= x but x < x2
			 * for the region. i.e. the x2, y2 are outside
			 * by 1 pixel. 
			 */
			blackr[blackouts].x1 = x;
			blackr[blackouts].y1 = y;
			blackr[blackouts].x2 = X;
			blackr[blackouts].y2 = Y;
			blackouts++;
			if (blackouts >= BLACKR_MAX) {
				rfbLog("too many blackouts: %d\n", blackouts);
				break;
			}
		}
		p = strtok(NULL, ", \t");
	}
	free(blist);
}

/*
 * Now that all blackout rectangles have been constructed, see what overlap
 * they have with the tiles in the system.  If a tile is touched by a
 * blackout, record information.
 */
static void blackout_tiles(void) {
	int tx, ty;
	int debug_bo = 0;
	if (! blackouts) {
		return;
	}
	if (getenv("DEBUG_BLACKOUT") != NULL) {
		debug_bo = 1;
	}

	/* 
	 * to simplify things drop down to single copy mode, etc...
	 */
	single_copytile = 1;
	/* loop over all tiles. */
	for (ty=0; ty < ntiles_y; ty++) {
		for (tx=0; tx < ntiles_x; tx++) {
			sraRegionPtr tile_reg, black_reg;
			sraRect rect;
			sraRectangleIterator *iter;
			int n, b, x1, y1, x2, y2, cnt;

			/* tile number and coordinates: */
			n = tx + ty * ntiles_x;
			x1 = tx * tile_x;
			y1 = ty * tile_y;
			x2 = x1 + tile_x;
			y2 = y1 + tile_y;
			if (x2 > dpy_x) {
				x2 = dpy_x;
			}
			if (y2 > dpy_y) {
				y2 = dpy_y;
			}

			/* make regions for the tile and the blackouts: */
			black_reg = (sraRegionPtr) sraRgnCreate();
			tile_reg  = (sraRegionPtr) sraRgnCreateRect(x1, y1,
			    x2, y2);

			tile_blackout[n].cover = 0;
			tile_blackout[n].count = 0;

			/* union of blackouts */
			for (b=0; b < blackouts; b++) {
				sraRegionPtr tmp_reg = (sraRegionPtr)
				    sraRgnCreateRect(blackr[b].x1,
				    blackr[b].y1, blackr[b].x2, blackr[b].y2);

				sraRgnOr(black_reg, tmp_reg);
				sraRgnDestroy(tmp_reg);
			}

			if (! sraRgnAnd(black_reg, tile_reg)) {
				/*
				 * no intersection for this tile, so we
				 * are done.
				 */
				sraRgnDestroy(black_reg);
				sraRgnDestroy(tile_reg);
				continue;
			}

			/*
			 * loop over rectangles that make up the blackout
			 * region:
			 */
			cnt = 0;
			iter = sraRgnGetIterator(black_reg);
			while (sraRgnIteratorNext(iter, &rect)) {

				/* make sure x1 < x2 and y1 < y2 */
				if (rect.x1 > rect.x2) {
					int tmp = rect.x2;
					rect.x2 = rect.x1;
					rect.x1 = tmp;
				}
				if (rect.y1 > rect.y2) {
					int tmp = rect.y2;
					rect.y2 = rect.y1;
					rect.y1 = tmp;
				}

				/* store coordinates */
				tile_blackout[n].bo[cnt].x1 = rect.x1;
				tile_blackout[n].bo[cnt].y1 = rect.y1;
				tile_blackout[n].bo[cnt].x2 = rect.x2;
				tile_blackout[n].bo[cnt].y2 = rect.y2;

				/* note if the tile is completely obscured */
				if (rect.x1 == x1 && rect.y1 == y1 &&
				    rect.x2 == x2 && rect.y2 == y2) {
					tile_blackout[n].cover = 2;
					if (debug_bo) {
 						fprintf(stderr, "full: %d=%d,%d"
						    "  (%d-%d)  (%d-%d)\n",
						    n, tx, ty, x1, x2, y1, y2);
					}
				} else {
					tile_blackout[n].cover = 1;
					if (debug_bo) {
						fprintf(stderr, "part: %d=%d,%d"
						    "  (%d-%d)  (%d-%d)\n",
						    n, tx, ty, x1, x2, y1, y2);
					}
				}

				if (++cnt >= BO_MAX) {
					rfbLog("too many blackout rectangles "
					    "for tile %d=%d,%d.\n", n, tx, ty);
					break;
				}
			}
			sraRgnReleaseIterator(iter);

			sraRgnDestroy(black_reg);
			sraRgnDestroy(tile_reg);

			tile_blackout[n].count = cnt;
			if (debug_bo && cnt > 1) {
 				rfbLog("warning: multiple region overlaps[%d] "
				    "for tile %d=%d,%d.\n", cnt, n, tx, ty);
			}
		}
	}
}

static int did_xinerama_clip = 0;

void check_xinerama_clip(void) {
#if LIBVNCSERVER_HAVE_LIBXINERAMA
	int n, k, i, ev, er, juse = -1;
	int score[32], is = 0;
	XineramaScreenInfo *x;

	if (!clip_str || !dpy) {
		return;
	}
	if (sscanf(clip_str, "xinerama%d", &k) == 1) {
		;
	} else if (sscanf(clip_str, "screen%d", &k) == 1) {
		;
	} else {
		return;
	}

	free(clip_str);
	clip_str = NULL;

	if (! XineramaQueryExtension(dpy, &ev, &er)) {
		return;
	}
	if (! XineramaIsActive(dpy)) {
		return;
	}
	x = XineramaQueryScreens(dpy, &n);
	if (k < 0 || k >= n) {
		XFree_wr(x);
		return;
	}
	for (i=0; i < n; i++) {
		score[is++] = nabs(x[i].x_org) + nabs(x[i].y_org);
		if (is >= 32) {
			break;
		}
	}
	for (i=0; i <= k; i++) {
		int j, jmon = 0, mon = -1, mox = -1;
		for (j=0; j < is; j++) {
			if (mon < 0 || score[j] < mon) {
				mon = score[j];
				jmon = j;
			}
			if (mox < 0 || score[j] > mox) {
				mox = score[j];
			}
		}
		juse = jmon;
		score[juse] = mox+1+i;
	}

	if (juse >= 0 && juse < n) {
		char str[64];
		sprintf(str, "%dx%d+%d+%d", x[juse].width, x[juse].height,
		    x[juse].x_org, x[juse].y_org);
		clip_str = strdup(str);
		did_xinerama_clip = 1;
	} else {
		clip_str = strdup("");
	}
	XFree_wr(x);
	if (!quiet) {
		rfbLog("set -clip to '%s' for xinerama%d\n", clip_str, k);
	}
#endif
}

static void initialize_xinerama (void) {
#if !LIBVNCSERVER_HAVE_LIBXINERAMA
	rfbLog("Xinerama: Library libXinerama is not available to determine\n");
	rfbLog("Xinerama: the head geometries, consider using -blackout\n");
	rfbLog("Xinerama: if the screen is non-rectangular.\n");
#else
	XineramaScreenInfo *sc, *xineramas;
	sraRegionPtr black_region, tmp_region;
	sraRectangleIterator *iter;
	sraRect rect;
	char *bstr, *tstr;
	int ev, er, i, n, rcnt;

	RAWFB_RET_VOID

	if (! XineramaQueryExtension(dpy, &ev, &er)) {
		if (verbose) {
			rfbLog("Xinerama: disabling: display does not support it.\n");
		}
		xinerama = 0;
		xinerama_present = 0;
		return;
	}
	if (! XineramaIsActive(dpy)) {
		/* n.b. change to XineramaActive(dpy, window) someday */
		if (verbose) {
			rfbLog("Xinerama: disabling: not active on display.\n");
		}
		xinerama = 0;
		xinerama_present = 0;
		return;
	}
	xinerama_present = 1;
	rfbLog("\n");
	rfbLog("Xinerama is present and active (e.g. multi-head).\n");

	/* n.b. change to XineramaGetData() someday */
	xineramas = XineramaQueryScreens(dpy, &n);
	if (verbose) {
		rfbLog("Xinerama: number of sub-screens: %d\n", n);
	}

	if (! use_xwarppointer && ! got_noxwarppointer && n > 1) {
		rfbLog("Xinerama: enabling -xwarppointer mode to try to correct\n");
		rfbLog("Xinerama: mouse pointer motion. XTEST+XINERAMA bug.\n");
		rfbLog("Xinerama: Use -noxwarppointer to force XTEST.\n");
		use_xwarppointer = 1;
	}

	if (n == 1) {
		if (verbose) {
			rfbLog("Xinerama: no blackouts needed (only one"
			    " sub-screen)\n");
			rfbLog("\n");
		}
		XFree_wr(xineramas);
		return;		/* must be OK w/o change */
	}

	black_region = sraRgnCreateRect(0, 0, dpy_x, dpy_y);

	sc = xineramas;
	for (i=0; i<n; i++) {
		int x, y, w, h;
		
		x = sc->x_org;
		y = sc->y_org;
		w = sc->width;
		h = sc->height;

		tmp_region = sraRgnCreateRect(x, y, x + w, y + h);

		sraRgnSubtract(black_region, tmp_region);
		sraRgnDestroy(tmp_region);
		sc++;
	}
	XFree_wr(xineramas);


	if (sraRgnEmpty(black_region)) {
		rfbLog("Xinerama: no blackouts needed (screen fills"
		    " rectangle)\n");
		rfbLog("\n");
		sraRgnDestroy(black_region);
		return;
	}
	if (did_xinerama_clip) {
		rfbLog("Xinerama: no blackouts due to -clip xinerama.\n");
		return;
	}

	/* max len is 10000x10000+10000+10000 (23 chars) per geometry */
	rcnt = (int) sraRgnCountRects(black_region);
	bstr = (char *) malloc(30 * (rcnt+1));
	tstr = (char *) malloc(30);
	bstr[0] = '\0';

	iter = sraRgnGetIterator(black_region);
	while (sraRgnIteratorNext(iter, &rect)) {
		int x, y, w, h;

		/* make sure x1 < x2 and y1 < y2 */
		if (rect.x1 > rect.x2) {
			int tmp = rect.x2;
			rect.x2 = rect.x1;
			rect.x1 = tmp;
		}
		if (rect.y1 > rect.y2) {
			int tmp = rect.y2;
			rect.y2 = rect.y1;
			rect.y1 = tmp;
		}
		x = rect.x1;
		y = rect.y1;
		w = rect.x2 - x;
		h = rect.y2 - y;
		sprintf(tstr, "%dx%d+%d+%d,", w, h, x, y);
		strcat(bstr, tstr);
	}
	sraRgnReleaseIterator(iter);
	initialize_blackouts(bstr);
	rfbLog("\n");

	free(bstr);
	free(tstr);
#endif
}

void initialize_blackouts_and_xinerama(void) {

	blackouts = 0;
	blackout_ptr = 0;

	if (blackout_str != NULL) {
		initialize_blackouts(blackout_str);
	}
	if (xinerama) {
		initialize_xinerama();
	}
	if (blackouts) {
		blackout_tiles();
		/* schedule a copy_screen(), now is too early. */
		do_copy_screen = 1;
	}
}

void push_sleep(int n) {
	int i;
	for (i=0; i<n; i++) {
		rfbPE(-1);
		if (i != n-1 && defer_update) {
			usleep(defer_update * 1000);
		}
	}
}

/*
 * try to forcefully push a black screen to all connected clients
 */
void push_black_screen(int n) {
	int Lx = dpy_x, Ly = dpy_y;
	if (!screen) {
		return;
	}
#ifndef NO_NCACHE
	if (ncache > 0) {
		Ly = dpy_y * (1+ncache);
	}
#endif
	zero_fb(0, 0, Lx, Ly);
	mark_rect_as_modified(0, 0, Lx, Ly, 0);
	push_sleep(n);
}

void refresh_screen(int push) {
	int i;
	if (!screen) {
		return;
	}
	mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0);
	for (i=0; i<push; i++) {
		rfbPE(-1);
	}
}

/*
 * Fill the framebuffer with zero for the prescribed rectangle
 */
void zero_fb(int x1, int y1, int x2, int y2) {
	int pixelsize = bpp/8;
	int line, fill = 0, yfac = 1;
	char *dst;

#ifndef NO_NCACHE
	if (ncache > 0) {
		yfac = 1+ncache;
		if (ncache_xrootpmap) {
			yfac++;
		}
	}
#endif
	
	if (x1 < 0 || x2 <= x1 || x2 > dpy_x) {
		return;
	}
	if (y1 < 0 || y2 <= y1 || y2 > yfac * dpy_y) {
		return;
	}
	if (! main_fb) {
		return;
	}

	dst = main_fb + y1 * main_bytes_per_line + x1 * pixelsize;
	line = y1;
	while (line++ < y2) {
		memset(dst, fill, (size_t) (x2 - x1) * pixelsize);
		dst += main_bytes_per_line;
	}
}


