/* X Viewer Demo for CX100 Frame Grabber Device Driver for Linux */
/* Written by: Danny Sung */
/* Started:  08/15/1995  13:42 */
/* Finished: 08/21/1995  19:13 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>

#define USE_XSHM

#if defined(USE_XSHM)
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif

#include "cxlib.h"


#define tv_diff(start,end)							\
		( ((end).tv_sec*1000000+(end).tv_usec) -	\
			((start).tv_sec*1000000+(start).tv_usec) )

struct WindowInfo {
	Widget toplevel;
	Display *disp;
	Colormap colormap;
	XtAppContext app_context;
	GC blackgc, whitegc;
	int fcolor, bcolor;
	int screen;
	Window rootw, window;
	Visual *visual;
	int depth;

	char colindex[256];	/* Pixel remapping */
	int priv;
	int width, height;
};
typedef struct WindowInfo WindowInfo;

typedef struct {
	XImage *ximage;
	char *buf;
	int width, height;
#if defined(USE_XSHM)
	XShmSegmentInfo shminfo;
#endif
} Image;

int putimage(WindowInfo *winfo, Image *image);
/*XImage *makeximage(WindowInfo *winfo, int width, int height, char **buf);*/
int makeimages(WindowInfo *winfo, int width, int height, Image **image, int num_images);
int handleevents(WindowInfo *winfo, XEvent *evt);
int init_greycolormap(WindowInfo *winfo);
int init_graphics(int argc, char **argv, WindowInfo *winfo);

int print_timings(struct timeval *start, struct timeval *end, char *str);

WindowInfo MainWindow;

int verbose = 0;
int resolution = 0;
int timings = 0;
int fps = 1;		/* fps or spf */
int num_pages = 1;

int main(int argc, char *argv[])
{
	Image **images;
	int x;
	int cxfd, width, height;
	int iter=0;
	char *devfile = 0;
	struct timeval tv_start, tv_end,
		tv_start1, tv_end1, tv_start2, tv_end2, tv_start3, tv_end3;
	
	MainWindow.priv = 0;

	while( (x=getopt(argc, argv, "hn:pstv")) != -1 )
	{
		switch(x)
		{
			case 'h':
				resolution = 1;
				break;
			case 'n':
				if( atoi(optarg) > 0 )
					num_pages = atoi(optarg);
				break;
			case 'p':
				MainWindow.priv = 1;
				break;
			case 's':
				fps = 0;
				break;
			case 't':
				timings = 1;
				break;
			case 'v':
				if( !verbose )
					printf("xcxdemo    - CX100 device driver for Linux   X11 demo program\n");
				verbose++;
				break;
			default:
				printf(
					"usage: xcxdemo [-hpstv] [-n pages] [device]\n\n"
					"  h : use high resolution mode of CX100\n"
					"  n : use \"pages\" extra pages (use if you're getting odd technicolor problems)\n"
					"  p : use private colormap\n"
					"  s : use sec/frame instead of frame/sec output for -t option\n"
					"  t : print out fps timings\n"
					"  v : verbose mode\n"
					"\n"
					"  device : device name for CX100 frame grabber.  Default is /dev/cx100\n"
					"\n"
				);
				exit(1);
		}
	}
	if( optind < argc )
		devfile = argv[optind];

	if( verbose )
		if( devfile )
			printf("Using device file: \"%s\"\n", devfile ? devfile : "/dev/null");

		/* Open up the device driver */
	if( (cxfd=cx_open(devfile)) == -1 )
		{ perror("xcxdemo"); return(0); }

	if( verbose )
		printf("Going into %s resolution.\n", resolution ? "high" : "low" );
	cx_setresolution(cxfd, resolution);

		/* Let's setup a window */
	MainWindow.width = cx_imagewidth(cxfd);
	MainWindow.height = cx_imageheight(cxfd);
	init_graphics(argc, argv, &MainWindow);

	XClearWindow(MainWindow.disp, MainWindow.window);
	XMapWindow(MainWindow.disp, MainWindow.window );

	XSelectInput(MainWindow.disp, MainWindow.window,
		KeyPressMask | EnterWindowMask | LeaveWindowMask |
		ButtonPressMask
		/*
		ButtonMotionMask | KeyReleaseMask |
		ButtonReleaseMask
		*/
	);
	
	width = cx_imagewidth(cxfd);
	height = cx_imageheight(cxfd);

	if( verbose )
	{
		printf("Image is %d x %d\n", width, height);
		printf("Using %d pages for display.\n", num_pages);
	}

	if( (images = malloc(num_pages * sizeof(*images))) == NULL )
		{ perror("xcxdemo"); return(-1); }

	makeimages(&MainWindow, width, height, images, num_pages);

	init_greycolormap(&MainWindow);


	if( timings ) gettimeofday( &tv_start, 0 );
	while(1)
	{
		char *buff;

		switch(handleevents(&MainWindow, 0)) 
		{
			case 0:
				buff = images[iter%num_pages]->buf;

						/* Get info from frame grabber */
				if( timings ) gettimeofday( &tv_start1, 0 );
				cx_getframe(cxfd, buff, width*height);
					/* Have to remap the pixels */
				if( timings ) gettimeofday( &tv_end1, 0);
				if( verbose ) print_timings(&tv_start1, &tv_end1, "grab");

						/* Do color remapping */
				if( timings ) gettimeofday( &tv_start2, 0 );
				for(x=0; x<width*height; x++)
					*(buff+x) = MainWindow.colindex[*(buff+x) & 0xff];
				if( timings ) gettimeofday( &tv_end2, 0);
				if( verbose ) print_timings(&tv_start2, &tv_end2, "remapping");

						/* Display Image */
				if( timings ) gettimeofday( &tv_start3, 0 );
				putimage(&MainWindow, images[iter%num_pages]);
				XFlush(MainWindow.disp);
				if( timings ) gettimeofday( &tv_end3, 0);
				if( verbose ) print_timings(&tv_start3, &tv_end3, "display");

						/* Give an overall fps */
				if( verbose ) print_timings(&tv_start1, &tv_end3, "total");
				if( timings && verbose ) printf("\n");
				
				break;
			case -1:		/* Quit */
				if( timings )
				{
					gettimeofday( &tv_end, 0 );
					x = tv_diff(tv_start, tv_end);
					if( x && iter )
						printf("avg: %.3f %s\n",
							fps ? (float) (iter*1000000)/x : (float)x/(1000000*iter),
							fps ? "fps" : "spf" );
				}
				for(x=0; x<num_pages; x++)
				{
#if defined(USE_XSHM)
					if( shmctl(images[x]->shminfo.shmid, IPC_RMID, 0) == -1 )
						printf("shmctl failed.\n");
#endif
					XDestroyImage(images[x]->ximage);
					free(images[x]);
				}
				cx_close(cxfd);

				exit(0);
				break;
		}
		iter++;
	}
}

int putimage(WindowInfo *winfo, Image *image)
{
#if defined(USE_XSHM)
	XShmPutImage(winfo->disp, winfo->window, winfo->whitegc,
		image->ximage, 0, 0, 0, 0, image->width, image->height, False );
#else
	XPutImage(winfo->disp, winfo->window, winfo->whitegc,
		image->ximage, 0, 0, 0, 0, image->width, image->height );
#endif
	return( 0 );
}


int makeimages(WindowInfo *winfo, int width, int height, Image **images, int num_images)
{
	int rtn=0;
	int x;

#if defined(USE_XSHM)
	for(x=0; x<num_images; x++)
	{
		if( (images[x] = malloc(sizeof(Image))) == NULL )
			{ perror("xcxdemo"); return(-1); }

		images[x]->ximage = XShmCreateImage(winfo->disp, winfo->visual, 8, ZPixmap,
			NULL, &images[x]->shminfo, width, height);
	
		images[x]->width = width; images[x]->height = height;
		if( images[x]->ximage == NULL )
			{ printf("Argh... shared memory error.\n"); rtn=-1; }
	
		images[x]->shminfo.shmid = shmget(IPC_PRIVATE, width * height,
			IPC_CREAT | 0x01FF );
	
		if( images[x]->shminfo.shmid < 0 )
			{ printf("... more shared memory errors\n"); rtn=-1; }
	
		images[x]->shminfo.shmaddr = (char *) shmat (images[x]->shminfo.shmid, 0, 0);
		if( images[x]->shminfo.shmaddr == (char *) -1 )
			{ printf("stop!!! more shared memory erros\n"); rtn=-1; }
	
		images[x]->buf = images[x]->ximage->data = images[x]->shminfo.shmaddr;
		images[x]->shminfo.readOnly = False;
	
		XShmAttach(winfo->disp, &images[x]->shminfo);
	}
	XSync(winfo->disp, False);

#else
	for(x=0; x<num_images; x++)
	{
		if( (images[x] = malloc(sizeof(Image))) == NULL )
			{ perror("xcxdemo"); return(-1); }

		images[x]->width = width; images[x]->height = height;
		if( (images[x]->buf = malloc(width*height)) == NULL )
			{ perror("xcxdemo"); return(-1); }

		images[x]->ximage = XCreateImage(winfo->disp, winfo->visual, winfo->depth,
				ZPixmap, 0, images[x]->buf, width, height, 8, width );
	}
#endif

	return(rtn);
}

#if 0
XImage *makeximage(WindowInfo *winfo, int width, int height, char **buf)
{
#if defined(USE_XSHM)
	XImage *ximage;

	ximage = XShmCreateImage(winfo->disp, winfo->visual, 8, ZPixmap,
		NULL, &shminfo, width, height);

	if( ximage == NULL )
	{
		printf("Argh... shared memory error.\n");
	}

	shminfo.shmid = shmget(IPC_PRIVATE, width * height,
		IPC_CREAT | 0x01FF );

	if( shminfo.shmid < 0 )
	{
		printf("... more shared memory errors\n");
	}

	shminfo.shmaddr = (char *) shmat (shminfo.shmid, 0, 0);
	if( shminfo.shmaddr == (char *) -1 )
		printf("stop!!! more shared memory erros\n");

	*buf = ximage->data = shminfo.shmaddr;
	shminfo.readOnly = False;

	XShmAttach(winfo->disp, &shminfo);
	XSync(winfo->disp, False);

	return( ximage );
#else
	return (
		XCreateImage(winfo->disp, winfo->visual, winfo->depth,
			ZPixmap, 0, *buf, width, height, 8, width )
		);
#endif
}
#endif

int handleevents(WindowInfo *winfo, XEvent *evt)
{
	int rtn=0, pending;

	if( (pending=XPending(MainWindow.disp)) )
	{
		XEvent event;
		/*for(; pending>0; pending--)*/
		{
			XNextEvent(MainWindow.disp, &event);
			if( evt ) memcpy( evt, &event, sizeof(XEvent) );
			switch(rtn = event.type)
			{
				case EnterNotify:
					if( MainWindow.priv )
						XInstallColormap(MainWindow.disp, MainWindow.colormap);
					break;
				case LeaveNotify:
					if( MainWindow.priv )
						XUninstallColormap(MainWindow.disp, MainWindow.colormap);
					break;
				case ButtonPress:
				/*
					for(x=0; x<MainWindow.width; x+=16)
						XDrawLine(MainWindow.disp, MainWindow.window, MainWindow.whitegc, x, 0, x, 256);
					for(x=0; x<MainWindow.height; x+=16)
						XDrawLine(MainWindow.disp, MainWindow.window, MainWindow.whitegc, 0, x, MainWindow.width, x);
				*/
					break;
				case ButtonRelease:
				/*
					for(x=0; x<MainWindow.width; x+=16)
						XDrawLine(MainWindow.disp, MainWindow.window, MainWindow.blackgc, x, 0, x, 256);
					for(x=0; x<MainWindow.height; x+=16)
						XDrawLine(MainWindow.disp, MainWindow.window, MainWindow.blackgc, 0, x, MainWindow.width, x);
				*/
					break;
				case KeyPress:
					rtn = -1;		/* Any key exits */
					break;
				case KeyRelease:
					break;
				case MotionNotify:
					break;
				default:
					break;
			}
		}
	}
	
	return rtn;
}

int init_graphics(int argc, char **argv, WindowInfo *winfo)
/* Initialize all the X stuff here */
/* We'll leave the window unmapped, however */
{
	int a;
	Arg args[5];
	XGCValues xgcv;
	XVisualInfo *visual_array, visual_info_template;
	int number_visuals;

	winfo->toplevel = XtAppInitialize(&winfo->app_context, "Xcxdemo", NULL, 0,
		&argc, argv, NULL, NULL, 0);

	if( !winfo->toplevel ) {
		perror("Can't open display or init Xtoolkit");
		exit(1);
	}

	winfo->disp = XtDisplay(winfo->toplevel);

		/* Let's get a visual */
	visual_info_template.class = PseudoColor;
	visual_info_template.screen = winfo->screen;
	visual_array = XGetVisualInfo(winfo->disp,
		VisualClassMask | VisualScreenMask, &visual_info_template,
		&number_visuals );
	if( number_visuals <= 0 )
		printf("Couldn't find any visuals\n");
	winfo->visual = visual_array[0].visual;
	winfo->depth = visual_array[0].depth;
	XFree(visual_array);

	winfo->screen = DefaultScreen(winfo->disp);
	winfo->rootw = RootWindow(winfo->disp, winfo->screen);
	winfo->fcolor = WhitePixel(winfo->disp, winfo->screen);
	winfo->bcolor = BlackPixel(winfo->disp, winfo->screen);

	winfo->window = XCreateWindow(winfo->disp, winfo->rootw,
		50, 50, winfo->width, winfo->height, 2, 8, InputOutput, winfo->visual, 0, 0);

	if( !winfo->window )
	{
		printf("Can't open main window.\n");
		return(1);
	}

	xgcv.background = winfo->bcolor;
	xgcv.foreground = winfo->bcolor;
	winfo->blackgc = XCreateGC(winfo->disp, winfo->window, GCForeground | GCBackground, &xgcv);
	xgcv.foreground = winfo->fcolor;
	winfo->whitegc = XCreateGC(winfo->disp, winfo->window, GCForeground | GCBackground, &xgcv);

	a=0;
	XtSetArg(args[a], XtNtitle, "xcxdemo"); a++;
	XtSetArg(args[a], XtNwidth, winfo->width); a++;
	XtSetArg(args[a], XtNheight, winfo->height); a++;
	XtSetArg(args[a], XtNmappedWhenManaged, False); a++;
	XtSetValues(winfo->toplevel, args, a);

		/* ugh.. XtNtitle not working, so let's do it the "old" way */
	XStoreName(winfo->disp, winfo->window, "xcxdemo");

	XtRealizeWidget(winfo->toplevel);

	return(0);
}

int init_greycolormap(WindowInfo *winfo)
/* Create a colormap.  If winfo->priv is true, we'll use a private
	colormap.  Otherwise, let's use the default
*/
{
	int x;
	XColor xcolor;

		/* Let's get a color map */
	if( winfo->priv )
		winfo->colormap = XCreateColormap(winfo->disp, winfo->window, winfo->visual, AllocNone );
	else
		winfo->colormap = XDefaultColormapOfScreen(XtScreen(winfo->toplevel));

	xcolor.flags = DoRed | DoGreen | DoBlue;
	for(x=0; x<256; x++)
	{
		xcolor.red = x * 256; xcolor.green = x * 256; xcolor.blue = x * 256;

		if( verbose == 2)
			printf("Pixel %d  reqest: (%x,%x,%x)",
				x, xcolor.red, xcolor.green, xcolor.blue);

		if( !XAllocColor(winfo->disp, winfo->colormap, &xcolor) )
		{
			printf("Failed to allocate color on: %d\n", x);
		}
		winfo->colindex[x] = xcolor.pixel;

		if( verbose == 2)
			printf(" mapped to %d  (%x,%x,%x)\n",
				(int)xcolor.pixel, xcolor.red, xcolor.green, xcolor.blue);
	}
	return(0);
}

int print_timings(struct timeval *start, struct timeval *end, char *str)
{
	int x;

	if( !timings )
		return(1);

	x = tv_diff(*start, *end);
	if( x )
		printf("%s: %.2f %s  ",
			str, fps ? (float) 1000000/x : (float)x/1000000,
			fps ? "fps" : "spf" );
	return(0);
}

