/*
 * xlib.c
 *
 * Xlib interface.
 * dist under gnu gpl
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#if defined(HAVE_LIBXEXT) && defined(HAVE_X11_EXTENSIONS_XSHM_H) \
 && defined(HAVE_SYS_IPC_H) && defined(HAVE_SYS_SHM_H)
#define USE_XSHM
#endif
#else
#define USE_XSHM  /* assume we have shm if no config.h */
#endif

#include <stdlib.h>

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

#ifdef USE_XSHM
#include <X11/extensions/XShm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#endif

#include "fb.h"
#include "input.h"
#include "rc.h"


struct fb fb;

static int vmode[3] = { 0, 0, 0 };
static int x_shmsync = 1;

rcvar_t vid_exports[] =
{
	RCV_VECTOR("vmode", &vmode, 3),
	RCV_BOOL("x_shmsync", &x_shmsync),
	RCV_END
};


static int initok;

/* Loads of bogus Xlib crap...bleh */

static char *x_displayname;

static Display *x_display;
static int x_screen;

static struct
{
	int bits;
	int vc;
	int bytes;
} x_vissup[] =
{
	{ 8, PseudoColor, 1 },
	{ 15, TrueColor, 2 },
	{ 16, TrueColor, 2 },
	{ 32, TrueColor, 4 },
	{ 24, TrueColor, 3 },
	{ 0, 0, 0 }
};

static int x_bits, x_bytes;
static Visual *x_vis;
static XVisualInfo x_visinfo;
static int x_pseudo;
static Colormap x_cmap;
static XColor x_ctable[256];

static int x_wattrmask;
static XSetWindowAttributes x_wattr;
static int x_gcvalmask;
static XGCValues x_gcval;

static Window x_win;
static int x_win_x, x_win_y;
static int x_width, x_height;
static GC x_gc;

static XSizeHints x_size;
static XWMHints x_wmhints;
/*static XClassHint x_class;*/

#ifdef USE_XSHM
static XShmSegmentInfo x_shm;
#endif

static int x_useshm;
static int x_shmevent;
static int x_shmdone;
static XImage *x_image;
static int x_byteswap;

static XEvent x_ev;




static void freescreen()
{
	if (!initok || !x_image) return;
	if ((char *)fb.ptr != (char *)x_image->data)
		free(fb.ptr);
#ifdef USE_XSHM
	if (x_useshm)
	{
		/* FIXME - is this the right way to free shared mem? */
		XSync(x_display, False);
		if (!XShmDetach(x_display, &x_shm))
			die ("XShmDetach failed\n");
		XSync(x_display, False);
		shmdt(x_shm.shmaddr);
		shmctl(x_shm.shmid, IPC_RMID, 0);
		x_image->data = NULL;
	}
#endif
	free(x_image);
	x_image = NULL;
	fb.ptr = NULL;
}

static void allocscreen()
{
	if (initok) freescreen();
#ifdef USE_XSHM
	if (x_useshm)
	{
		x_image = XShmCreateImage(
			x_display, x_vis, x_bits, ZPixmap, 0,
			&x_shm, x_width, x_height);
		if (x_image)
		{
			x_shm.shmid = shmget(
				IPC_PRIVATE,
				x_image->bytes_per_line * x_image->height,
				IPC_CREAT | 0777);
			if (x_shm.shmid < 0)
				die("shmget failed\n");
			x_image->data = x_shm.shmaddr =
				shmat(x_shm.shmid, 0, 0);
			if (!x_image->data)
				die("shmat failed\n");
			if (!XShmAttach(x_display, &x_shm))
				die("XShmAttach failed\n");
			XSync(x_display, False);
			x_shmdone = 1;
			fb.pitch = x_image->bytes_per_line;
		}
		else
		{
			x_useshm = 0;
		}
	}
#endif
	if (!x_useshm)
	{
		x_image = XCreateImage(
			x_display, x_vis, x_bits, ZPixmap, 0,
			malloc(x_width*x_height*x_bytes),
			x_width, x_height, x_bits, x_width*x_bytes);
		if (!x_image)
			die("XCreateImage failed\n");
	}
	x_byteswap = x_image->byte_order ==
#ifdef IS_LITTLE_ENDIAN
		MSBFirst
#else
		LSBFirst
#endif
		;
	if (x_byteswap && x_bytes > 1)
		fb.ptr = malloc(x_image->bytes_per_line * x_image->height);
	else
		fb.ptr = x_image->data;
}



void vid_resize()
{
	freescreen();
	x_width = fb.w;
	x_height = fb.h;
	XResizeWindow(x_display, x_win, x_width, x_height);
	x_size.flags = PSize | PMinSize | PMaxSize;
	x_size.min_width = x_size.max_width = x_size.base_width = x_width;
	x_size.min_height = x_size.max_height = x_size.base_height = x_height;
	XSetWMNormalHints(x_display, x_win, &x_size);
	XSync(x_display, False);
	allocscreen();
}


static void colorshifts()
{
	int i;
	int mask[3];
	int l, c;

	mask[0] = x_vis->red_mask;
	mask[1] = x_vis->green_mask;
	mask[2] = x_vis->blue_mask;

	for (i = 0; i < 3; i++)
	{
		for (l = 0; l < 32 && !((1<<l) & mask[i]); l++);
		for (c = 0; l+c < 32 && ((1<<(l+c)) & mask[i]); c++);
		fb.cc[i].l = l;
		fb.cc[i].r = 8-c;
	}
}




void vid_preinit()
{
	/* do nothing; only needed on systems where we must drop perms */
}

void vid_init()
{
	int i;
	
	if (initok) return;

	x_displayname = getenv("DISPLAY");
	if (!x_displayname)
		die("DISPLAY environment variable not set\n");
	x_display = XOpenDisplay(NULL);
	if (!x_display)
		die("failed to connect to X display\n");
	x_screen = DefaultScreen(x_display);

	for (i = 0; x_vissup[i].bits; i++)
	{
		if (XMatchVisualInfo(
			x_display, x_screen,
			x_vissup[i].bits, x_vissup[i].vc, &x_visinfo))
		{
			if (x_vissup[i].vc == PseudoColor)
				x_pseudo = 1;
			else
				x_pseudo = 0;
			x_bits = x_vissup[i].bits;
			x_bytes = x_vissup[i].bytes;
			break;
		}
	}
	if (!x_bits) die("no suitable X visuals\n");
	x_vis = x_visinfo.visual;
	if (!x_vis) die("X visual is NULL");

	if (x_pseudo)
	{
		x_cmap = XCreateColormap(
			x_display, RootWindow(x_display, x_screen),
			x_vis, AllocAll);
		for (i = 0; i < 256; i++)
		{
			x_ctable[i].pixel = i;
			x_ctable[i].flags = DoRed|DoGreen|DoBlue;
		}
	}

	x_wattrmask = CWEventMask | CWBorderPixel | CWColormap;
	x_wattr.event_mask = KeyPressMask | KeyReleaseMask | ExposureMask
		| FocusChangeMask;
	x_wattr.border_pixel = 0;
	x_wattr.colormap = x_cmap;

	x_gcvalmask = GCGraphicsExposures;
	x_gcval.graphics_exposures = False;

	if (!vmode[0] || !vmode[1])
	{
		int scale = rc_getint("scale");
		if (scale < 1) scale = 1;
		vmode[0] = 160 * scale;
		vmode[1] = 144 * scale;
	}
	
	fb.w = vmode[0];
	fb.h = vmode[1];
	fb.pelsize = x_bytes == 3 ? 4 : x_bytes;
	fb.pitch = fb.w * fb.pelsize;
	fb.indexed = x_pseudo;
	fb.enabled = 1;
	fb.dirty = 0;
	
	x_win_x = x_win_y = 0;
	x_width = fb.w;
	x_height = fb.h;
	x_win = XCreateWindow(
		x_display, RootWindow(x_display, x_screen),
		x_win_x, x_win_y, x_width, x_height, 0, x_bits,
		InputOutput, x_vis, x_wattrmask, &x_wattr);
	if (!x_win) die("could not create X window\n");

	x_gc = XCreateGC(x_display, x_win, x_gcvalmask, &x_gcval);

	x_size.flags = PSize | PMinSize | PMaxSize;
	x_size.min_width = x_size.max_width = x_size.base_width = x_width;
	x_size.min_height = x_size.max_height = x_size.base_height = x_height;
	XSetWMNormalHints(x_display, x_win, &x_size);

	x_wmhints.initial_state = NormalState;
	x_wmhints.input = True;
	x_wmhints.flags = StateHint | InputHint;
	XSetWMHints(x_display, x_win, &x_wmhints);

	/* FIXME - set X class info stuff (with XSetClassHint)... */
	
	XMapWindow(x_display, x_win);

	for(;;)
	{
		XNextEvent(x_display, &x_ev);
		if (x_ev.type == Expose && !x_ev.xexpose.count)
			break;
	}

	XSetInputFocus(x_display, x_win, RevertToPointerRoot, CurrentTime);

#ifdef USE_XSHM
	if (XShmQueryExtension(x_display) && x_displayname[0] == ':')
	{
		x_useshm = 1;
		x_shmevent = XShmGetEventBase(x_display) + ShmCompletion;
	}
#endif

	colorshifts();
	allocscreen();

	joy_init();

	initok = 1;
}

void vid_close()
{
	joy_close();
	if (!initok) return;
	freescreen();
	XFreeGC(x_display, x_gc);
	XDestroyWindow(x_display, x_win);
	XCloseDisplay(x_display);
	initok = 0;
}







/* keymap - mappings of the form { keysym, localcode } - from x11/keymap.c */
extern int keymap[][2];

static int mapxkeycode(int xkeycode)
{
	int i;
	int sym;
	int code;

	sym = XKeycodeToKeysym(x_display, xkeycode, 0);
	for (i = 0; keymap[i][0]; i++)
		if (keymap[i][0] == sym)
			return keymap[i][1];
	if (sym < XK_space || sym > XK_asciitilde)
		return 0;
	code = sym - XK_space + ' ';
	if (code >= 'A' && code <= 'Z')
		code = tolower(code);;
	return code;
}


void vid_end();

static int nextevent(int sync)
{
	event_t ev;
	
	if (!sync && !XPending(x_display))
		return 0;

	XNextEvent(x_display, &x_ev);
	switch(x_ev.type)
	{
	case KeyPress:
		ev.type = EV_PRESS;
		ev.code = mapxkeycode(x_ev.xkey.keycode);
		break;
	case KeyRelease:
		ev.type = EV_RELEASE;
		ev.code = mapxkeycode(x_ev.xkey.keycode);
		break;
	case Expose:
		vid_end();
		return 1;
		break;
	default:
		if (x_ev.type == x_shmevent) x_shmdone = 1;
		return 1;
		break;
	}
	return ev_postevent(&ev);  /* returns 0 if queue is full */
}



void ev_poll()
{
	while (nextevent(0));
	joy_poll();
}



void vid_settitle(char *title)
{
	XStoreName(x_display, x_win, title);
	XSetIconName(x_display, x_win, title);
}

void vid_setpal(int i, int r, int g, int b)
{
	if (!initok) return;
	
	if (x_pseudo == 1)
	{
		x_ctable[i].red = r << 8;
		x_ctable[i].green = g << 8;
		x_ctable[i].blue = b << 8;
		XStoreColors(x_display, x_cmap, x_ctable, 256);
	}
}

void vid_begin()
{
	if (!x_useshm) return;

	/* XSync(x_display, False); */
	while (!x_shmdone && x_shmsync)
		nextevent(1);
}

static void endianswap()
{
	int cnt;
	un16 t16;
	un32 t32;
	un16 *src16 = (void *)fb.ptr;
	un16 *dst16 = (void *)x_image->data;
	un32 *src32 = (void *)fb.ptr;
	un32 *dst32 = (void *)x_image->data;
	
	switch (x_bytes)
	{
	case 2:
		cnt = (x_image->bytes_per_line * x_image->height)>>1;
		while (cnt--)
		{
			t16 = *(src16++);
			*(dst16++) = (t16 << 8) | (t16 >> 8);
		}
		break;
	case 4:
		cnt = (x_image->bytes_per_line * x_image->height)>>2;
		while (cnt--)
		{
			t32 = *(src32++);
			*(dst32++) = (t32 << 24) | ((t32 << 8) & 0x00FF0000) |
				((t32 >> 8) & 0x0000FF00) | (t32 >> 24);
		}
		break;
	}
}


void vid_end()
{
	if (!initok) return;

	if (x_byteswap) endianswap();
	if (x_useshm)
	{
		if (!x_shmdone) return;
#ifdef USE_XSHM
		if (!XShmPutImage(
			x_display, x_win, x_gc, x_image,
			0, 0, 0, 0, x_width, x_height, True))
			die("XShmPutImage failed\n");
#endif
		x_shmdone = 0;
	}
	else
	{
		XPutImage(x_display, x_win, x_gc, x_image,
			  0, 0, 0, 0, x_width, x_height);
	}
}





