#include "dat.h"
#include "fns.h"
#include "cursor.h"
#include "keyboard.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#define ABS(x) ((x) < 0 ? -(x) : (x))

/*
 * alias defs for image types to overcome name conflicts
 */
typedef struct ICursor		ICursor;
typedef struct IPoint		IPoint;
typedef struct IRectangle	IRectangle;
typedef struct CRemapTbl	CRemapTbl;
struct ICursor
{
	int	w;
	int	h;
	int	hotx;
	int	hoty;
	char	*src;
	char	*mask;
};

struct IPoint
{
	int	x;
	int	y;
};

struct IRectangle
{
	IPoint	min;
	IPoint	max;
};

struct CRemapTbl
{
	ulong inferno[256];	/* The corresponding inferno colormap vals */
	ulong openslot[256];	
	Bool cellused[256];
	int cnt;
	int opencnt;
};

enum
{
	DblTime	= 300		/* double click time in msec */
};

XColor			map[256];	/* Inferno colormap array */
XColor			map7[128];	/* Inferno colormap array */
uchar			map7to8[128][2];
Colormap		xcmap;		/* Default shared colormap  */
int 			infernotox11[256]; /* Values for mapping between */
int 			x11toinferno[256]; /* X11 and inferno */
int				x24bitswap = 0;	/* swap endian for 24bit RGB */

static	int		triedscreen;
static	XModifierKeymap *modmap;
static	int		keypermod;
static	Drawable	xdrawable;
static	Atom		wm_take_focus;
static	void		xexpose(XEvent*);
static	void		xmouse(XEvent*);
static	void		xkeyboard(XEvent*);
static	void		xmapping(XEvent*);
static	void		xproc(void*);
static	void		xinitscreen(int, int);
static	void		initmap(Window);
static	GC		creategc(Drawable);
static 	CRemapTbl	crtbl;
static	void		graphicscmap(XColor*);
	int		xscreendepth;
	Drawable	xscreenid;
	Display*	xdisplay;
	Display*	xkmcon;
	Visual		*xvis;
	GC		xgcfill, xgccopy, xgcsimplesrc, xgczero, xgcreplsrc;
	GC		xgcfill0, xgccopy0, xgcsimplesrc0, xgczero0, xgcreplsrc0;

char *gkscanid = "emu_x11";


ulong*
attachscreen(IRectangle *r, int *ld, int *width, int *softscreen)
{
	extern ulong* makememones();

	r->min.x = 0;
	r->min.y = 0;
	r->max.x = Xsize;
	r->max.y = Ysize;
	*ld = 3;
	*width = Xsize/4;
	*softscreen = 1;
	if(!triedscreen){
		triedscreen = 1;
		xinitscreen(Xsize, Ysize);
		if(kproc("xproc", xproc, nil, 0) < 0) {
			fprint(2, "emu: win-x11 can't make X proc\n");
			return 0;
		}
	}
	return makememones();
}

void
flushmemscreen(IRectangle r)
{
	if(r.min.x > r.max.x)
		return;
	XCopyArea(xdisplay, xscreenid, xdrawable, xgccopy,
		r.min.x, r.min.y,
		r.max.x-r.min.x, r.max.y-r.min.y, r.min.x, r.min.y);
	XFlush(xdisplay);
}

static int
revbyte(int b)
{
	int r;

	r = 0;
	r |= (b&0x01) << 7;
	r |= (b&0x02) << 5;
	r |= (b&0x04) << 3;
	r |= (b&0x08) << 1;
	r |= (b&0x10) >> 1;
	r |= (b&0x20) >> 3;
	r |= (b&0x40) >> 5;
	r |= (b&0x80) >> 7;
	return r;
}

static void
gotcursor(ICursor c)
{
	Cursor xc;
	XColor fg, bg;
	Pixmap xsrc, xmask;
	static Cursor xcursor;

	if(c.src == nil){
		if(xcursor != 0) {
			XFreeCursor(xdisplay, xcursor);
			xcursor = 0;
		}
		XUndefineCursor(xdisplay, xdrawable);
		XFlush(xdisplay);
		return;
	}
	xsrc = XCreateBitmapFromData(xdisplay, xdrawable, c.src, c.w, c.h);
	xmask = XCreateBitmapFromData(xdisplay, xdrawable, c.mask, c.w, c.h);

	fg = map[255];
	bg = map[0];
	fg.pixel = infernotox11[255];
	bg.pixel = infernotox11[0];
	xc = XCreatePixmapCursor(xdisplay, xsrc, xmask, &fg, &bg, -c.hotx, -c.hoty);
	if(xc != 0) {
		XDefineCursor(xdisplay, xdrawable, xc);
		if(xcursor != 0)
			XFreeCursor(xdisplay, xcursor);
		xcursor = xc;
	}
	XFreePixmap(xdisplay, xsrc);
	XFreePixmap(xdisplay, xmask);
	XFlush(xdisplay);
	free(c.src);
}

void
setcursor(IPoint p)
{
	XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, p.x, p.y);
	XFlush(xdisplay);
}

void
drawcursor(Drawcursor* c)
{
	ICursor ic;
	IRectangle ir;
	uchar *bs, *bc;
	int i, h, j, bpl;
	char *src, *mask, *csrc, *cmask;

	/* Set the default system cursor */
	src = nil;
	mask = nil;
	if(c->data != nil){
		h = (c->maxy-c->miny)/2;
		ir.min.x = c->minx;
		ir.min.y = c->miny;
		ir.max.x = c->maxx;
		ir.max.y = c->maxy;
		/* passing IRectangle to Rectangle is safe */
		bpl = bytesperline(ir, 1);

		i = h*bpl;
		src = malloc(2*i);
		if(src == nil)
			return;
		mask = src + i;

		csrc = src;
		cmask = mask;
		bc = c->data;
		bs = c->data + h*bpl;
		for(i = 0; i < h; i++){
			for(j = 0; j < bpl; j++) {
				*csrc++ = revbyte(bs[j]);
				*cmask++ = revbyte(bs[j] | bc[j]);
			}
			bs += bpl;
			bc += bpl;
		}
	}
	ic.w = 8*bpl;
	ic.h = h;
	ic.hotx = c->hotx;
	ic.hoty = c->hoty;
	ic.src = src;
	ic.mask = mask;

	gotcursor(ic);
}

static void
xproc(void *arg)
{
	ulong mask;
	XEvent event;

	closepgrp(up->env->pgrp);
	closefgrp(up->env->fgrp);
	closeegrp(up->env->egrp);
	closesigs(up->env->sigs);

	mask = 	KeyPressMask|
		KeyReleaseMask|
		ButtonPressMask|
		ButtonReleaseMask|
		PointerMotionMask|
		Button1MotionMask|
		Button2MotionMask|
		Button3MotionMask|
		ExposureMask;

	XSelectInput(xkmcon, xdrawable, mask);		
	for(;;) {
		XWindowEvent(xkmcon, xdrawable, mask, &event);
		switch(event.type) {
		case KeyPress:
		case KeyRelease:
			xkeyboard(&event);
			break;
		case ButtonPress:
		case ButtonRelease:
		case MotionNotify:
			xmouse(&event);
			break;
		case Expose:
			xexpose(&event);
			break;
		case MappingNotify:
			xmapping(&event);
			break;
		default:
			break;
		}
	}
}

static int
shutup(Display *d, XErrorEvent *e)
{
	char buf[200];
	print("X error: error code=%d, request_code=%d, minor=%d\n", e->error_code, e->request_code, e->minor_code);
	XGetErrorText(d, e->error_code, buf, sizeof(buf));
	print("%s\n", buf);
	USED(d);
	USED(e);
	return 0;
}

static void
xinitscreen(int xsize, int ysize)
{
	int i, pmid;
	char *argv[2];
	char *disp_val;
	Window rootwin;
	XWMHints hints;
	Screen *screen;
	XVisualInfo xvi;
	int rootscreennum;
	XTextProperty name;
	XClassHint classhints;
	XSizeHints normalhints;
	XSetWindowAttributes attrs;
 
	xscreenid = 0;
	xdrawable = 0;

	xdisplay = XOpenDisplay(NULL);
	if(xdisplay == 0){
		disp_val = getenv("DISPLAY");
		if(disp_val == 0)
			disp_val = "not set";
		fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", disp_val);
		cleanexit(0);
	}

	rootscreennum = DefaultScreen(xdisplay);
	rootwin = DefaultRootWindow(xdisplay);

	xscreendepth = DefaultDepth(xdisplay, rootscreennum);
	if(XMatchVisualInfo(xdisplay, rootscreennum, 24, TrueColor, &xvi)
	|| XMatchVisualInfo(xdisplay, rootscreennum, 24, DirectColor, &xvi)){
		xvis = xvi.visual;
		xscreendepth = 24;
		xtblbit = 1;
	}
	else if(XMatchVisualInfo(xdisplay, rootscreennum, 8, PseudoColor, &xvi)
	|| XMatchVisualInfo(xdisplay, rootscreennum, 8, StaticColor, &xvi)){
		if(xscreendepth > 8) {
			fprint(2, "emu: win-x11 can't deal with depth %d screens\n", xscreendepth);
			cleanexit(0);
		}
		xvis = xvi.visual;
		xscreendepth = 8;
	}
	else{
		if(xscreendepth != 8){
			fprint(2, "emu: win-x11 can't deal with depth %d screens\n", xscreendepth);
			cleanexit(0);
		}
		xvis = DefaultVisual(xdisplay, rootscreennum);
	}

	screen = DefaultScreenOfDisplay(xdisplay);
	xcmap = DefaultColormapOfScreen(screen);

	if(xvis->class != StaticColor){
		graphicscmap(map);
		initmap(rootwin);
	}

	if(modmap = XGetModifierMapping(xdisplay))
		keypermod = modmap->max_keypermod;

	attrs.colormap = xcmap;
	attrs.background_pixel = 0;
	attrs.border_pixel = 0;
	/* attrs.override_redirect = 1;*/ /* WM leave me alone! |CWOverrideRedirect */
	xdrawable = XCreateWindow(xdisplay, rootwin, 0, 0, xsize, ysize, 0, xscreendepth, InputOutput, xvis, CWBackPixel|CWBorderPixel|CWColormap, &attrs);

	/*
	 * set up property as required by ICCCM
	 */
	name.value = "inferno";
	name.encoding = XA_STRING;
	name.format = 8;
	name.nitems = strlen(name.value);
	normalhints.flags = USSize|PMaxSize;
	normalhints.max_width = normalhints.width = xsize;
	normalhints.max_height = normalhints.height = ysize;
	hints.flags = InputHint|StateHint;
	hints.input = 1;
	hints.initial_state = NormalState;
	classhints.res_name = "inferno";
	classhints.res_class = "Inferno";
	argv[0] = "inferno";
	argv[1] = nil;
	XSetWMProperties(xdisplay, xdrawable,
		&name,			/* XA_WM_NAME property for ICCCM */
		&name,			/* XA_WM_ICON_NAME */
		argv,			/* XA_WM_COMMAND */
		1,			/* argc */
		&normalhints,		/* XA_WM_NORMAL_HINTS */
		&hints,			/* XA_WM_HINTS */
		&classhints);		/* XA_WM_CLASS */

	/*
	 * put the window on the screen
	 */
	XMapWindow(xdisplay, xdrawable);

	xscreenid = XCreatePixmap(xdisplay, xdrawable, xsize, ysize, xscreendepth);
	XFlush(xdisplay);

	xgcfill = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcfill, FillSolid);
	xgccopy = creategc(xscreenid);
	xgcsimplesrc = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcsimplesrc, FillStippled);
	xgczero = creategc(xscreenid);
	xgcreplsrc = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcreplsrc, FillTiled);

	pmid = XCreatePixmap(xdisplay, xdrawable, 1, 1, 1);
	xgcfill0 = creategc(pmid);
	XSetFillStyle(xdisplay, xgcfill0, FillSolid);
	xgccopy0 = creategc(pmid);
	xgcsimplesrc0 = creategc(pmid);
	XSetFillStyle(xdisplay, xgcsimplesrc0, FillStippled);
	xgczero0 = creategc(pmid);
	xgcreplsrc0 = creategc(pmid);
	XSetFillStyle(xdisplay, xgcreplsrc0, FillTiled);
	XFreePixmap(xdisplay, pmid);

	XSetForeground(xdisplay, xgccopy, infernotox11[0]);
	XFillRectangle(xdisplay, xscreenid, xgccopy, 0, 0, xsize, ysize);

	xkmcon = XOpenDisplay(NULL);
	if(xkmcon == 0){
		disp_val = getenv("DISPLAY");
		if(disp_val == 0)
			disp_val = "not set";
		fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", disp_val);
		cleanexit(0);
	}
}

static void
graphicscmap(XColor *map)
{
	int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;

	for(r=0; r!=4; r++) {
		for(g = 0; g != 4; g++) {
			for(b = 0; b!=4; b++) {
				for(v = 0; v!=4; v++) {
					den=r;
					if(g > den)
						den=g;
					if(b > den)
						den=b;
					/* divide check -- pick grey shades */
					if(den==0)
						cr=cg=cb=v*17;
					else {
						num=17*(4*den+v);
						cr=r*num/den;
						cg=g*num/den;
						cb=b*num/den;
					}
					idx = r*64 + v*16 + ((g*4 + b + v - r) & 15);
					idx = 255 - idx;
					map[idx].red = cr*0x0101;
					map[idx].green = cg*0x0101;
					map[idx].blue = cb*0x0101;
					map[idx].pixel = idx;
					map[idx].flags = DoRed|DoGreen|DoBlue;

					v7 = v >> 1;
					idx7 = r*32 + v7*16 + g*4 + b;
					if((v & 1) == v7){
						map7to8[idx7][0] = idx;
						if(den == 0) { 		/* divide check -- pick grey shades */
							cr = ((255.0/7.0)*v7)+0.5;
							cg = cr;
							cb = cr;
						}
						else {
							num=17*15*(4*den+v7*2)/14;
							cr=r*num/den;
							cg=g*num/den;
							cb=b*num/den;
						}
						map7[idx7].red = cr*0x0101;
						map7[idx7].green = cg*0x0101;
						map7[idx7].blue = cb*0x0101;
						map7[idx7].pixel = idx7;
						map7[idx7].flags = DoRed|DoGreen|DoBlue;
					}
					else
						map7to8[idx7][1] = idx;
				}
			}
		}
	}
}

/*
 * Initialize and install the Inferno colormap as a private colormap for this
 * application.  Inferno gets the best colors here when it has the cursor focus.
 */  
static void 
initmap(Window w)
{
	XColor c;
	XColor xcol;
	ulong v;
	int i, pix, ncmaps;

	if(xscreendepth <= 1)
		return;

	if(xvis->class == TrueColor || xvis->class == DirectColor) {
	int color_order_init = 0;
	uint pp, p;

		for(i = 0; i < 256; i++) {
			c = map[i];
			/* find out index into colormap for our RGB */
			if(!XAllocColor(xdisplay, xcmap, &c)) {
				fprint(2, "emu: win-x11 can't alloc color\n");
				cleanexit(0);
			}

			/* The pixel value returned from XGetPixel needs to
			 * be converted to RGB so we can call rgb2cmap()
			 * to translate between 24 bit X and our color. Unfortunately,
			 * the return value appears to be display server endian 
			 * dependant. Therefore, we run some heuristics to later
			 * determine how to mask the int value correctly.
			 * Yeah, I know we can look at xvis->byte_order but 
			 * some displays say MSB even though they run on LSB.
			 * Besides, this is more anal.
			 */

			p  = c.pixel;
			pp = rgb2cmap((p>>16)&0xff,(p>>8)&0xff,p&0xff);
			if(!color_order_init && (pp!=map[i].pixel)) {
				/* check if endian is other way */
				pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
				if(pp!=map[i].pixel) {
					fprint(2, "emu: win-x11 can't convert 24bit colors\n");
					cleanexit(0);
				}
				color_order_init = 1;
				x24bitswap = 1;
			}

			if(color_order_init) {
				pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
				if(pp!=map[i].pixel) {
					fprint(2, "emu: win-x11 can't convert 24bit colors\n");
					cleanexit(0);
				}
				/* no x11toinferno; later use rgb2cmap() */
				infernotox11[map[i].pixel] = c.pixel;
			}
			else if(pp!=map[i].pixel) {
				fprint(2, "emu: win-x11 can't convert 24bit colors\n");
				cleanexit(0);
			}
			else {
				/* no x11toinferno; later use rgb2cmap() */
				infernotox11[map[i].pixel] = c.pixel;
			}
		}
	}
	else if(xvis->class == PseudoColor) {
		if(xtblbit == 0){
			xcmap = XCreateColormap(xdisplay, w, xvis, AllocAll); 
			XStoreColors(xdisplay, xcmap, map, 256);
			for(i = 0; i < 256; i++) {
				infernotox11[i] = i;
				x11toinferno[i] = i;
			}
		}
		else {
			for(i = 0; i < 128; i++) {
				c = map7[i];
				if(!XAllocColor(xdisplay, xcmap, &c)) {
					fprint(2, "emu: win-x11 can't alloc colors in default map, don't use -7\n");
					cleanexit(0);
				}
				infernotox11[map7to8[i][0]] = c.pixel;
				infernotox11[map7to8[i][1]] = c.pixel;
				x11toinferno[c.pixel] = map7to8[i][0];
			}
		}
	}
	else {
		xtblbit = 0;
		fprint(2, "emu: win-x11 unsupported visual class %d\n", xvis->class);
	}
	return;
}

static void
xmapping(XEvent *e)
{
	XMappingEvent *xe;

	if(e->type != MappingNotify)
		return;
	xe = (XMappingEvent*)e;
	if(modmap)
		XFreeModifiermap(modmap);
	modmap = XGetModifierMapping(xe->display);
	if(modmap)
		keypermod = modmap->max_keypermod;
}


/*
 * Disable generation of GraphicsExpose/NoExpose events in the GC.
 */
static GC
creategc(Drawable d)
{
	XGCValues gcv;

	gcv.function = GXcopy;
	gcv.graphics_exposures = False;
	return XCreateGC(xdisplay, d, GCFunction|GCGraphicsExposures, &gcv);
}

static void
xexpose(XEvent *e)
{
	IRectangle r;
	XExposeEvent *xe;

	if(e->type != Expose)
		return;
	xe = (XExposeEvent*)e;
	r.min.x = xe->x;
	r.min.y = xe->y;
	r.max.x = xe->x + xe->width;
	r.max.y = xe->y + xe->height;
	drawqlock();
	flushmemscreen(r);
	drawqunlock();
}


static void
xkeyboard(XEvent *e)
{
	int ind, n;
	KeySym k;
	Rune r;
	unsigned int md;
	char buf[1];

	if(gkscanq) {
		uchar ch = (KeyCode)e->xkey.keycode;
		if(e->xany.type == KeyRelease)
			ch |= 0x80;
		qproduce(gkscanq, &ch, 1);
		return;
	}
	
	/*
	 * I tried using XtGetActionKeysym, but it didn't seem to
	 * do case conversion properly
	 * (at least, with Xterminal servers and R4 intrinsics)
	 */
	if(e->xany.type != KeyPress)
		return;

	md = e->xkey.state;
	ind = 0;
	if(md & ShiftMask)
		ind = 1;

	k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, ind);

	/* May have to try unshifted version */
	if(k == NoSymbol && ind == 1)
		k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, 0);

	if(k == XK_Multi_key || k == NoSymbol)
		return;
	if(k&0xFF00){
		switch(k){
		case XK_BackSpace:
		case XK_Tab:
		case XK_Escape:
		case XK_Delete:
		case XK_KP_0:
		case XK_KP_1:
		case XK_KP_2:
		case XK_KP_3:
		case XK_KP_4:
		case XK_KP_5:
		case XK_KP_6:
		case XK_KP_7:
		case XK_KP_8:
		case XK_KP_9:
		case XK_KP_Divide:
		case XK_KP_Multiply:
		case XK_KP_Subtract:
		case XK_KP_Add:
		case XK_KP_Decimal:
			k &= 0x7F;
			break;
		case XK_Linefeed:
			k = '\r';
			break;
		case XK_KP_Enter:
		case XK_Return:
			k = '\n';
			break;
		case XK_Alt_L:
		case XK_Alt_R:
		case XK_Meta_L:
		case XK_Meta_R:
			k = Latin;
			break;
		case XK_Left:
		case XK_KP_Left:
		  k = Left;
		  break;
		case XK_Down:
		case XK_KP_Down:
		  k = Down;
		  break;
		case XK_Right:
		case XK_KP_Right:
		  k = Right;
		  break;
		case XK_Up:
		case XK_KP_Up:
		  k = Up;
		  break;
		case XK_Home:
		case XK_KP_Home:
		  k = Home;
		  break;
		case XK_End:
		case XK_KP_End:
		  k = End;
		  break;
		case XK_Page_Up:
		case XK_KP_Page_Up:
		  k = Pgup;
		  break;
		case XK_Page_Down:
		case XK_KP_Page_Down:
		  k = Pgdown;
		  break;
		default:		/* not ISO-1 or tty control */
			return;
		}
	}
	/* Compensate for servers that call a minus a hyphen */
	if(k == XK_hyphen)
		k = XK_minus;
	/* Do control mapping ourselves if translator doesn't */
	if(md & ControlMask)
		k &= 0x9f;
	if(k == '\t' && ind)
		k = BackTab;
	
	if(md & Mod1Mask)
		k = APP|(k&0xff);
	if(k == NoSymbol) 
		return;

	gkbdputc(gkbdq, k);
}

static void
xmouse(XEvent *e)
{
	int s, dbl;
	XButtonEvent *be;
	XMotionEvent *me;
	XEvent motion;
	Pointer m;
	static ulong lastb, lastt;

	dbl = 0;
	switch(e->type){
	case ButtonPress:
		be = (XButtonEvent *)e;
		m.x = be->x;
		m.y = be->y;
		s = be->state;
		if(be->button == lastb && be->time - lastt < DblTime)
			dbl = 1;
		lastb = be->button;
		lastt = be->time;
		switch(be->button){
		case 1:
			s |= Button1Mask;
			break;
		case 2:
			s |= Button2Mask;
			break;
		case 3:
			s |= Button3Mask;
			break;
		}
		break;
	case ButtonRelease:
		be = (XButtonEvent *)e;
		m.x = be->x;
		m.y = be->y;
		s = be->state;
		switch(be->button){
		case 1:
			s &= ~Button1Mask;
			break;
		case 2:
			s &= ~Button2Mask;
			break;
		case 3:
			s &= ~Button3Mask;
			break;
		}
		break;
	case MotionNotify:
		me = (XMotionEvent *) e;

		/* remove excess MotionNotify events from queue and keep last one */
		while(XCheckTypedWindowEvent(xkmcon, xdrawable, MotionNotify, &motion) == True)
			me = (XMotionEvent *) &motion;

		s = me->state;
		m.x = me->x;
		m.y = me->y;
		break;
	default:
		return;
	}

	m.b = 0;
	if(s & Button1Mask)
		m.b |= 1;
	if(s & Button2Mask)
		m.b |= 2;
	if(s & Button3Mask)
		m.b |= 4;
	if(dbl)
		m.b |= 1<<4;

	m.modify = 1;
	mouseproduce(m);
}

