/* X Application for CX100 Frame Grabber Device Driver for Linux */
/* Using the Athena widget set */
/* Started:  08/28/1995  23:14 */
/* Finished: */

int _Xdebug = 1;

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Box.h>
#include <X11/Xresource.h>

#include <lug.h>
#include <lugfnts.h>

#include "config.h"
#include "cxlib.h"

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

#include <cxlib.h>

void tbcb_quit(Widget widget, XtPointer client_data, XtPointer call_data);
void tbcb_grab(Widget widget, XtPointer client_data, XtPointer call_data);
void tbcb_live(Widget widget, XtPointer client_data, XtPointer call_data);
void tbcb_save(Widget widget, XtPointer client_data, XtPointer call_data);
void cb_dummy(Widget widget, XtPointer client_data, XtPointer call_data);
void evh_prvcolormap(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch_event);
void evh_camera(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch_event);
void evh_showbitmap(Widget widget, XtPointer client_data, XtPointer call_data, Boolean *continue_to_dispatch_event);

Boolean wp_grabframe(XtPointer client_data);
Boolean wp_initcolormap(XtPointer client_data);

void display_fps(void);

#define tv_to_usec(_tv_)   ( ((_tv_).tv_sec*1000000L) + (_tv_).tv_usec )
#define tv_to_msec(_tv_)   ( ((_tv_).tv_sec*1000L) + ((_tv_).tv_usec/1000L) )
#define tv_udiff(start,end)			\
    ( ((end).tv_sec*1000000+(end).tv_usec) -  \
		((start).tv_sec*1000000+(start).tv_usec) )
#define tv_mdiff(start,end)			\
    ( ((end).tv_sec*1000+(end).tv_usec/1000) -  \
		((start).tv_sec*1000+(start).tv_usec/1000) )

#define current_image() (count % numimages)

typedef struct {
	char *name;
	Widget widget;
	XtCallbackProc callback;
} BUTTON;

typedef struct {
	int managed;
	Widget widget, form, fps;
	BUTTON quit, live, stop, grab, save;
} TOOLBOX;

typedef struct {
	char *device;		/* Name of device to use */
	int pages;			/* # of pages */
	int private;		/* boolean value */
	int verbose;		/* boolean value */
	int timing;			/* boolean value */
	int flip;			/* boolean value */
	int resolution;	/* boolean value */
} GLOBALOPTIONS;

typedef struct {
	Widget widget;
	Window window;
	Display *display;
	int screen;
} CAMERA;

typedef struct {
	int x, y;				/* Start (x,y) coordinates within image buffer */
	int width, height;	/* Width of image area to display */
	float xscale, yscale;	/* Scaling factor */
} IMAGEOPTIONS;

#include "xcxappP.h"

	/* Define some X related global variables */
TOOLBOX ToolBox;
GLOBALOPTIONS GlobalOptions;
CAMERA Camera;
XtAppContext app_context;
Visual *visual;
Colormap colormap;      /* We'll need this if we're doing a private colormap */
GC blackgc, whitegc;
char colindex[256];     /* Pixel value remapping */
int numcols;            /* Number of colors we have allocated */
typedef struct {
	XImage *ximage;			/* Pointer to our X images */
	char *buf;					/* Pointer to buff in ximage */
	char *orig;					/* Original image */
	int width, height;
#if defined(USE_XSHM)
	XShmSegmentInfo shminfo;
#endif
} Image;
Image *image;
int numimages;          /* Number of pages we have (should equal to GlobalOptions.pages) */
int count=0;				/* Incremented every time an image is displayed */
		/* Total accumulated time for all frames */	
long int fg_msec=0;				/* Time for frame grab */
long int cv_msec=0;				/* Time for pixel conversion */
long int sc_msec=0;				/* Time for scaling */
long int dp_msec=0;				/* Time for displaying */
long int tot_msec=0;				/* All */

	/* Define some frame grabber related global variables */
int cxfd;		/* file descriptor for CX calls */
int res;			/* Are we in low res or high res mode? */
int width, height;		/* dimensions of the frame grabbed image */


int main(int argc, char *argv[])
{
	int a;
	Arg args[5];
	int screen, depth;
	Window rootw;
	Widget toplevel;
	int fcolor, bcolor;
	XGCValues xgcv;

	toplevel = XtAppInitialize(&app_context, "Xcxapp", Options,
		XtNumber(Options), &argc, argv, FallbackResources, NULL, 0);

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

			/* Get resources */
	XtGetApplicationResources(toplevel, &GlobalOptions, Resources,
		XtNumber(Resources), NULL, 0);
			/* Check for invalid resources */
	if( GlobalOptions.pages < 1 ) GlobalOptions.pages = 1;

			/* Tell the user what's going on */
	if( GlobalOptions.verbose )
	{
		if( GlobalOptions.private )
			printf("Using private colormap.\n");
		printf("Using %d page%s for video display.\n", GlobalOptions.pages,
			GlobalOptions.pages == 1 ? "" : "s" );
		if( GlobalOptions.timing )
		{
			printf("Print out timings. In %s order.\n",
				GlobalOptions.flip ? "seconds/frame" : "frames/sec");
		}
		printf("Using device: \"%s\"\n", GlobalOptions.device);
	}
	Camera.widget = toplevel;

		/* Start up the frame grabber here */
	if( GlobalOptions.verbose )
		printf("Now opening device: \"%s\"\n", GlobalOptions.device);
	if( (cxfd = cx_open(GlobalOptions.device)) == -1 )
		{ perror("xcxapp: opening cx100 device file"); return(0); }
	if( GlobalOptions.verbose )
		printf("Setting frame grabber to %s resolution.\n",
			GlobalOptions.resolution ? "high" : "low" );
	cx_setresolution(cxfd, GlobalOptions.resolution);
	width = cx_imagewidth(cxfd); height = cx_imageheight(cxfd);
	if( GlobalOptions.verbose )
		printf("Frame grabber has a resolution of %d by %d.\n", width,
				height);

		/* Set up some important variables */
	screen = DefaultScreen(Camera.display);
	Camera.display = XtDisplay(Camera.widget);
	Camera.screen = screen;
	rootw = RootWindow(Camera.display, screen);

	init_visual(Camera.screen, Camera.display, &visual, &depth);

	a=0;
	XtSetArg(args[a], XtNwidth, width); a++;
	XtSetArg(args[a], XtNheight, height); a++;
	XtSetValues(toplevel, args, a);
	
			/* Realize the toplevel widget */
	XtRealizeWidget(toplevel);

			/* This has to be done after toplevel is realized for private
				colormap to work */
	Camera.window = XtWindow(Camera.widget);
	init_colormap(toplevel);


		/* Let's make a little toolbox for controlling the camera */
	ToolBox.widget = XtCreatePopupShell("toolbox", topLevelShellWidgetClass, toplevel, NULL, 0);


		/* Start setting up some important stuff */
	init_toolbox(&ToolBox);
	a = init_ximages(GlobalOptions.pages, width, height, Camera.display, visual);
	if( GlobalOptions.verbose )
		printf("Initialized %d page%s.\n", a, a==1 ? "" : "s");

		/* Now realized the toolbox */
	XtRealizeWidget(ToolBox.widget);

			/* Make a GC */
	fcolor = WhitePixel(Camera.display, screen);
	bcolor = BlackPixel(Camera.display, screen);

	xgcv.background = bcolor;
	xgcv.foreground = bcolor;
	blackgc = XCreateGC(Camera.display, XtWindow(Camera.widget), GCForeground | GCBackground, &xgcv);
	xgcv.foreground = fcolor;
	whitegc = XCreateGC(Camera.display, XtWindow(Camera.widget), GCForeground | GCBackground, &xgcv);

		/* Add our event handlers */
	XtAddEventHandler(Camera.widget, ExposureMask | ButtonPressMask,
			False, evh_camera, 0);

	if( GlobalOptions.private )
	{
			/* Hmm... can't set it in the toolbox yet */
/*
		XtAddEventHandler(ToolBox.widget, EnterWindowMask | LeaveWindowMask |
				FocusChangeMask, False, evh_prvcolormap, 0);
*/
		XtAddEventHandler(Camera.widget, EnterWindowMask | LeaveWindowMask |
				FocusChangeMask, False, evh_prvcolormap, 0);
	}

			/* Show our widgets */
	XtMapWidget(Camera.widget);
	XtMapWidget(ToolBox.widget); ToolBox.managed = 1;

			/* Place work procedures here */
	XtAppAddWorkProc(app_context, wp_initcolormap, 0);
	XtAppMainLoop(app_context);

	return(0);
}


static int init_visual(int screen, Display *display, Visual **visual, int *depth)
{
	int rtn=0;
	XVisualInfo *visual_array, visual_info_template;

	if( !visual || !depth )
		return(0);

	visual_info_template.class = PseudoColor;
	visual_info_template.screen = screen;
	visual_array = XGetVisualInfo(display, VisualClassMask | VisualScreenMask,
		&visual_info_template, &rtn );

	if( rtn > 0 ) {
		*visual = visual_array[0].visual;
		*depth = visual_array[0].depth;
	}
	XFree(visual_array);

	if( *depth != 8 )
		printf("Error: Need an 8 bit depth.\n");

	return(rtn);
}

int init_toolbox(TOOLBOX *toolbox)
{
	Arg args[10];
	Widget widgets[10];
	int a;

	if( !toolbox )
		return(0);

	toolbox->managed = 0;

	a=0;
	XtSetArg(args[a], XtNwidth, 50); a++;
	XtSetArg(args[a], XtNheight, 75); a++;
	toolbox->form =
		XtCreateManagedWidget("form", boxWidgetClass, toolbox->widget, args, a);

		/* String definitions */
	toolbox->quit.name = "quit";
	toolbox->live.name = "live";
	toolbox->stop.name = "stop";
	toolbox->grab.name = "grab";
	toolbox->save.name = "save";

		/* Initialize callbacks */
	toolbox->quit.callback = tbcb_quit;
	toolbox->live.callback = tbcb_live;
	toolbox->stop.callback = tbcb_live;
	toolbox->grab.callback = tbcb_grab;
	toolbox->save.callback = tbcb_save;

		/* Create the widgets */
	toolbox->quit.widget = XtCreateWidget(toolbox->quit.name,
		commandWidgetClass, toolbox->form, 0, 0);
	toolbox->live.widget = XtCreateWidget(toolbox->live.name,
		commandWidgetClass, toolbox->form, 0, 0);
	toolbox->stop.widget = XtCreateWidget(toolbox->stop.name,
		commandWidgetClass, toolbox->form, 0, 0);
	toolbox->grab.widget = XtCreateWidget(toolbox->grab.name,
		commandWidgetClass, toolbox->form, 0, 0);
	toolbox->save.widget = XtCreateWidget(toolbox->save.name,
		commandWidgetClass, toolbox->form, 0, 0);

	toolbox->fps = XtCreateWidget("fps", labelWidgetClass,
		toolbox->form, 0, 0);

		/* Now add callbacks */
	XtAddCallback(ToolBox.quit.widget, XtNcallback, ToolBox.quit.callback, 0);
	XtAddCallback(ToolBox.live.widget, XtNcallback, ToolBox.live.callback, 0);
	XtAddCallback(ToolBox.stop.widget, XtNcallback, ToolBox.stop.callback, 0);
	XtAddCallback(ToolBox.grab.widget, XtNcallback, ToolBox.grab.callback, 0);
	XtAddCallback(ToolBox.save.widget, XtNcallback, ToolBox.save.callback, 0);

		/* Now manage all the buttons and stuff */
	a=0;
	widgets[a] = toolbox->quit.widget; a++;
	widgets[a] = toolbox->live.widget; a++;
	widgets[a] = toolbox->grab.widget; a++;
	widgets[a] = toolbox->save.widget; a++;
	if( GlobalOptions.timing )
		{ widgets[a] = toolbox->fps; a++; }
	XtManageChildren(widgets, a);

	return(1);
}

static int init_colormap(Widget widget)
{
	int rtn=0;

	if( GlobalOptions.private )
		colormap = XCreateColormap(Camera.display, Camera.window, visual,
				AllocNone );
	else
		colormap = XDefaultColormapOfScreen(XtScreen(widget));

	numcols = 0;
	return(rtn);
}

static int init_ximages(int numpages, int width, int height, Display *display, Visual *visual)
{
	int x;

	numimages = numpages;
	if( (image=malloc(numpages*sizeof(Image))) == NULL )
		{ perror("xcxapp: making ximage"); return(0); }

#if defined(USE_XSHM)
	for(x=0; x<numpages; x++)
	{
		image[x].width = width; image[x].height = height;
		image[x].ximage = XShmCreateImage(display, visual, 8, ZPixmap,
			NULL, &image[x].shminfo, width, height);
		if( image[x].ximage == NULL )
			{ printf("Argh... shared memory error.\n"); return(-1); }
		image[x].shminfo.shmid = shmget(IPC_PRIVATE, width*height,
			IPC_CREAT | 0x01FF );
		if( image[x].shminfo.shmid < 0 )
			{ printf("... more shared memory errors\n"); return(-1); }
		image[x].shminfo.shmaddr = (char *)shmat(
			image[x].shminfo.shmid, 0, 0);
		if( image[x].shminfo.shmaddr == (char *) -1 )
			{ printf("stop!!! mroe shared memory errors\n"); return(-1); }
		image[x].buf = image[x].ximage->data = image[x].shminfo.shmaddr;
		image[x].shminfo.readOnly = False;

		XShmAttach(display, &image[x].shminfo);

		if( (image[x].orig = malloc(width*height)) == NULL )
			{ perror("xcxap: making ximag buffer"); return(0); }
	}
	XSync(display, False);
#else
	for(x=0; x<numpages; x++)
	{
		image[x].width = width; image[x].height = height;
		if( (image[x].buf=malloc(width*height)) == NULL )
			{ perror("xcxapp: making ximage buffer"); return(0); }

		image[x].ximage = XCreateImage(display, visual, 8, ZPixmap, 0,
			image[x].buf, width, height, 8, width);

		if( (image[x].orig = malloc(width*height)) == NULL )
			{ perror("xcxapp: making ximage buffer"); return(0); }
	}
#endif
	return(x);
}

void tbcb_quit(Widget widget, XtPointer client_data, XtPointer call_data)
{
	int x;
	if( GlobalOptions.verbose ) printf("Closing frame grabber driver\n");
	cx_close(cxfd);

	if( GlobalOptions.verbose ) printf("Freeing images\n");
	for(x=0; x<numimages; x++)
	{
#if defined(USE_XSHM)
		if( shmctl(image[x].shminfo.shmid, IPC_RMID, 0) == -1 )
			printf("shmctl failed.\n");
#endif
		XDestroyImage(image[x].ximage);
		free(image[x].orig);
	}
	free(image);

	/*if( GlobalOptions.private )*/
	{
		if( GlobalOptions.verbose ) printf("Freeing colormap\n");
		XFreeColormap(Camera.display, colormap);
	}

	if( GlobalOptions.verbose ) printf("Terminating normally.\n");
	exit(0);
}

void tbcb_grab(Widget widget, XtPointer client_data, XtPointer call_data)
{
	if( GlobalOptions.verbose )
		printf("Grabbing a frame\n");

	wp_grabframe(client_data);

	return;
}

void tbcb_live(Widget widget, XtPointer client_data, XtPointer call_data)
{
	static int live=0;
	static XtWorkProcId procid=0;

	live = !live;

	if( GlobalOptions.verbose )
		printf("Live capture %s.\n", live ? "on" : "off");

	if( live )
	{
		XtUnmanageChild(ToolBox.live.widget);
		XtUnmanageChild(ToolBox.grab.widget);
		XtManageChild(ToolBox.stop.widget);
		procid = XtAppAddWorkProc(app_context, wp_grabframe, 0);
	}
	else
	{
		XtUnmanageChild(ToolBox.stop.widget);
		XtManageChild(ToolBox.live.widget);
		XtManageChild(ToolBox.grab.widget);
		if( procid )
			XtRemoveWorkProc( procid );
	}
}

Boolean wp_initcolormap(XtPointer client_data)
{
	XColor xcolor;
	static int x=0;

	xcolor.flags = DoRed | DoGreen | DoBlue;
	xcolor.red = x*256; xcolor.green = x*256; xcolor.blue = x*256;

	if( !XAllocColor(Camera.display, colormap, &xcolor) )
	{
		printf("Failed to allocate color on: %d\n", x);
	}
	else
		colindex[numcols++] = xcolor.pixel;

	if( x++ >= 255 )
	{
		if( GlobalOptions.verbose )
			printf("numcols allocated: %d\n", numcols);
		return(True);
	}
	return(False);
}

Boolean wp_grabframe(XtPointer client_data)
{
	int n, x;
	char *ptr, *optr;
	struct timeval fg_tv, cv_tv, sc_tv, dp_tv, tv;

	n = current_image();
	ptr = image[n].buf;
	optr = image[n].orig;

			/* This is the code fragment we use to grab a frame */
	if( GlobalOptions.timing ) gettimeofday(&fg_tv, 0);
	cx_getframe(cxfd, optr, width*height);
	if( GlobalOptions.timing ) gettimeofday(&tv, 0);
	fg_msec += tv_mdiff(fg_tv,tv);

		/* This should be faster than trying to use XPutPixel & XGetPixel */
	if( GlobalOptions.timing ) gettimeofday(&cv_tv, 0);
	for(x=0; x<width*height; x++, optr++, ptr++)
		*ptr = colindex[(int)*optr & 0xff ];
	if( GlobalOptions.timing ) gettimeofday(&tv, 0);
	cv_msec += tv_mdiff(cv_tv,tv);

	if( GlobalOptions.timing ) gettimeofday(&dp_tv, 0);
#if defined(USE_XSHM)
	XShmPutImage(Camera.display, Camera.window, whitegc,
		image[n].ximage, 0, 0, 0, 0, width, height, False );
#else
	XPutImage(Camera.display, Camera.window, whitegc, image[n].ximage, 0, 0,
		0, 0, width, height );
#endif
	count++;

	if( GlobalOptions.timing ) 
	{
		gettimeofday(&tv, 0);
		dp_msec += tv_mdiff(dp_tv,tv);

		tot_msec += tv_mdiff(fg_tv,tv);
		display_fps();
	}

	return False;
}

void display_fps(void)
{
	int a;
	Arg arg[5];
	char str[32];

	if( tot_msec )
		sprintf(str, "%.3f fps", (float) (count * 1000 / fg_msec) );
	else
		sprintf(str, "div by zero");
	a=0;
	XtSetArg(arg[a], XtNlabel, str); a++;
	XtSetValues(ToolBox.fps, arg, a);
	return;
}

void cb_dummy(Widget widget, XtPointer client_data, XtPointer call_data)
{
	return;
}

void evh_prvcolormap(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch_event)
/* This handler should only be installed if we are using a private colormap */
{
	static int use_cmap=0;

	switch(event->type ) {
		case FocusIn:
		case EnterNotify:
			use_cmap++;
			break;
		case FocusOut:
		case LeaveNotify:
			use_cmap--;
			break;
	}
	if( use_cmap )
		XInstallColormap(Camera.display, colormap);
	else
		XUninstallColormap(Camera.display, colormap);
}

void evh_camera(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch_event)
{
	switch(event->type) {
		case ButtonPress:
			ToolBox.managed = !ToolBox.managed;
			if( ToolBox.managed )
				XtMapWidget(ToolBox.widget);
			else
				XtUnmapWidget(ToolBox.widget);
			break;
		case Expose:
		{
			XExposeEvent *xexpose;
			int x, y, n;

			n = (count-1) % numimages;
			xexpose = &event->xexpose;
			x = xexpose->x; y = xexpose->y;

#if defined(USE_XSHM)
			XShmPutImage(Camera.display, Camera.window, whitegc,
				image[n].ximage, x, y, x, y, xexpose->width,
				xexpose->height, False );
#else
			XPutImage(Camera.display, Camera.window, whitegc,
				image[n].ximage, x, y, x, y, xexpose->width,
				xexpose->height);
#endif
		}
			break;
		default:
			break;
	}
}

void evh_showbitmap(Widget widget, XtPointer client_data, XtPointer call_data, Boolean *continue_to_dispatch_event)
{
	XImage *image;
	Window window;
	Display *display;

	char *buf;
	int x, y, n;

	display = XtDisplay(widget);
	window = XtWindow(widget);

	if( (buf=malloc(64000)) == 0 )
		perror("xcxapp");

	image = XCreateImage(display, visual, 8, ZPixmap, 0, buf, 100, 100, 8, 100);

	for(x=0, n=0; x<100; x++)
		for(y=0; y<100; y++)
		{
			XPutPixel(image, x, y, colindex[n]); n++;
			if( n >= numcols ) n = 0;
		}

	XPutImage(display, window, whitegc, image, 0, 0, 0, 0, 100, 100);

	XDestroyImage(image);

}

void tbcb_save(Widget widget, XtPointer client_data, XtPointer call_data)
{
	Image *img;
	bitmap_hdr in;

	img = &image[current_image()];

	in.magic = LUGUSED;
	in.xsize = img->width;
	in.ysize = img->height;
	in.depth = 8;
	in.colors = ( 1 << in.depth );
	in.cmap = create_bw_pallete();
	in.r = img->orig;

	write_gif_file("test.gif", &in);
}

