/***************************************
XWindow Support for QuickCam
by Paul Chinn <loomer@svpal.org>
Modified by Scott Laird <scott@laird.com>
1/4/96: xform additions by Paul Chinn
 
I took a bunch of this code from the source for VGB
"Virtual GameBoy" emulator by Marat Fayzullin and
Elan Feingold
*****************/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>

/** MIT Shared Memory Extension for X ************************/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
XShmSegmentInfo SHMInfo;

/** XForms include files */
#include <forms.h>
#include "controlpanel.h"

/** QuickCam include files */
#include "qcam.h"
#include "qcam-os.h"
 
/** Various X-related variables ******************************/

Screen *screen;
Display *disp;
Window root,win;
XColor col;
Colormap cmap;
XImage *ximage, *imagehi, *imagemed, *imagelo;
XEvent ev;
GC gc;
int screen_num;
unsigned long white,black;
int xstarted=0; 
int quit=0;
FD_QuickCam *mainwin;

struct qcam *q;

/* Set a flag to exit the loop at the end */

void quitprogram(int foo)
{
  quit=1;
}

void Slider(FL_OBJECT *obj, long val)
{
}

void Radio(FL_OBJECT *obj, long val)
{
switch(val){
	case 0:	/*hi res button*/
		if(q->width != 320)
			{
			q->width = 320;
			q->height = 240;
			ximage = imagehi;
			XResizeWindow(disp, win, 320, 240);
			}
	break;
	case 1: /*med res button*/
		if(q->width != 160)
			{
			q->width = 160;
			q->height = 120;
			ximage = imagemed;
			XResizeWindow(disp, win, 160, 120);
			}
	break;
	case 2: /*lo res button*/
		if(q->width != 80)
			{
			q->width = 80;
			q->height = 60;
			ximage = imagelo;
			XResizeWindow(disp, win, 80, 60);
			}
	break;
	case 3: /*6 bpp button*/
		q->bpp = 6;
	break;
	case 4: /*4 bpp button*/
		q->bpp = 4;
	break;
	}
}

void TakePic(FL_OBJECT *obj, long val)
{
	scanbuf *buf;
	FILE *f;

	buf = qc_scan(q);
	f = fopen("snapshot","w");
	if(!f)
	{
		printf("Can't create snapshot\n");
	}
	else
	{
		qc_writepgm(q, f, buf);
		fclose(f);
	}
	free(buf);
}

void Quit(FL_OBJECT *obj, long val)
{
	quit = 1;
}
 
/** Initialize xwindows, and prepare a shared memory buffer for
 the image.  Returns pointer to shared memory buffer. */
 
char *InitXWindows(struct qcam *q)
{
  XGCValues values;
  /*  XSizeHints hints;*/
  /*  XWMHints wmhints;*/
  int width, height;
  char *sbuf;

  width=q->width;
  height=q->height;

	/*disp is set by the call to fl_initalize */
  screen_num=DefaultScreen(disp);
  white=XWhitePixel(disp,screen_num);
  black=XBlackPixel(disp,screen_num);
  
  root=DefaultRootWindow(disp);
  
  win=XCreateSimpleWindow(disp,root,0,0,width,height,0,white,black);
  if(!win) {  printf("create window failed\n");return(NULL); }
  
#if 0
  /*tell window manager about our window*/
  hints.flags=USSize;
  hints.width=width;
  hints.height=height;
  wmhints.input=True;
  wmhints.flags=InputHint;
  XSetWMHints(disp,win,&wmhints);
  XSetWMNormalHints(disp,win,&hints);
#endif

  XStoreName(disp, win, "QuickCam");
  XSelectInput(disp, win, ExposureMask);
  XMapRaised(disp, win);
  XNextEvent(disp, &ev);
  
  gc = XCreateGC(disp, win, 0, &values);
  
  imagehi = XShmCreateImage(disp, DefaultVisual(disp, screen_num), 
			   8, ZPixmap, NULL, &SHMInfo, 320, 240);
  imagemed = XShmCreateImage(disp, DefaultVisual(disp, screen_num), 
			   8, ZPixmap, NULL, &SHMInfo, 160, 120);
  imagelo = XShmCreateImage(disp, DefaultVisual(disp, screen_num), 
			   8, ZPixmap, NULL, &SHMInfo, 80, 60);
 
  SHMInfo.shmid=shmget(IPC_PRIVATE, 320* 240, IPC_CREAT|0777);

  if(SHMInfo.shmid < 0) {
    perror("shmget failed:");
    return (NULL);
  }
 
  sbuf = imagehi->data = imagemed->data = imagelo->data 
	= SHMInfo.shmaddr = shmat(SHMInfo.shmid, 0, 0);
  XShmAttach(disp, &SHMInfo);
  signal(SIGHUP, quitprogram); 
  signal(SIGINT, quitprogram);
  signal(SIGQUIT, quitprogram); 
  signal(SIGTERM, quitprogram);
  xstarted=1;
  return(sbuf);
}


void ExitXWindows(void)
{
  if(xstarted) {
    XShmDetach(disp, &SHMInfo);
    if(SHMInfo.shmaddr)
      shmdt(SHMInfo.shmaddr);
    if(SHMInfo.shmid > 0)
      shmctl(SHMInfo.shmid, IPC_RMID, 0);
  }
}


int *xqc_createpalette(Colormap cmap)
{
  int *pal;
  int i;

  pal=malloc(sizeof(int[64]));
  
  for(i=0; i<64; i++) {
    col.red =col.green = col.blue = i * 1024;
    XAllocColor(disp, cmap, &col);
    pal[i] = col.pixel;
  }
  return pal;
}


void xqc_sync(struct qcam *q, int *colortable, char *sbuf)
{
  int i;
  scanbuf *scan;

  qc_set(q);

  scan=qc_scan(q);
  
  for(i=0;i<q->height*q->width;i++)
    switch(q->bpp) {
    case 4: 
      sbuf[i]=colortable[(scan[i]<<2)+1];
      break;
    case 6:
      sbuf[i]=colortable[scan[i]];
      break;
    default:
      fprintf(stderr,"Unsupported depth!\n");
      exit(1);
    }
  
  free(scan);
  XShmPutImage(disp, win, gc, ximage, 0,0,0,0,q->width, q->height, False);
  XFlush(disp);
}

void usage(void)
{
  fprintf(stderr,"Usage:\n");
  fprintf(stderr,"  qcam [options]\n");
  fprintf(stderr,"    Options:\n");
  fprintf(stderr,"      -x width   Set width\n");
  fprintf(stderr,"      -y height  Set height\n");
  fprintf(stderr,"      -p port    Set port\n");
  fprintf(stderr,"      -B bpp     Set bits per pixel\n");

  fprintf(stderr,"      -c val     Set contrast\n");
  fprintf(stderr,"      -w val     Set white balance\n");
  fprintf(stderr,"      -b val     Set brightness\n");
  fprintf(stderr,"      -V         Show version information\n");
  fprintf(stderr,"      -v         Verbose output\n");
  fprintf(stderr,"      -C         Use private colormap\n");
  

}

void modegripe(struct qcam *q)
{
  fprintf(stderr,"Unsupported resolution/bit depth!\n");
  fprintf(stderr,"This program only supports 320x240, 160x120, and 80x60.\n");
  fprintf(stderr,"You specified %d x %d.  Try again.\n",q->width,q->height);
  exit(1);
}

int main(int argc, char **argv)
{
  int arg;
  extern char *optarg;
  int *colortable;
  char *sbuf;
  int verbose=0;
  int privatecmap=0;
  Colormap cmap;
  struct timeval tv1, tv2;
  double framerate=0,fr;

  colortable=malloc(sizeof(int[64]));


  if(geteuid()) {
    fprintf(stderr,"%s: Must be installed SUID or run as root.  Exiting.\n",
	    argv[0]);
    exit(1);
  }
/*intialize xforms and get the display */
disp = fl_initialize(argv[0], "QuickCam Viewer", 0,0, &argc, argv);
  q=qc_init();
  qc_initfile(q, 0);

  /* Read command line */

  while((arg=getopt(argc,argv,"hCvx:y:p:b:B:c:w:V"))>0) { 
    switch (arg) {
    case 'x':
      q->width=atoi(optarg);
      break;
    case 'y':
      q->height=atoi(optarg);
      break;
    case 'p':
      if (!getuid())
	q->port=strtol(optarg,NULL,0);
      break;
    case 'B':
      q->bpp=atoi(optarg);
      break;
    case 'b':
      q->brightness=atoi(optarg);
      break;
    case 'c':
      q->contrast=atoi(optarg);
      break;
    case 'w':
      q->whitebal=atoi(optarg);
      break;
    case 'V':
      fprintf(stderr,"%s: Version 0.2\n",argv[0]);
      exit(0);
      break;
    case 'h':
      usage();
      exit(0);
      break;
    case 'C':
      privatecmap=1;
      break;
    case 'v':
      verbose=1;
      break;
    default:
      fprintf(stderr,"%s: Unknown option or error in option\n",argv[0]);
      usage();
      exit(1);
      break;
    }
  }

  switch (q->width) {
  case 80:  if(q->height!=60)  modegripe(q); break;
  case 160: if(q->height!=120) modegripe(q); break;
  case 320: if(q->height!=240) modegripe(q); break;
  default:
    modegripe(q);
  }

  switch (q->bpp) {
  case 4: 
  case 6: break;
  default:
    fprintf(stderr,"%s: Error: Unsupported bit depth\n",argv[0]);
    exit(1);
  }


  /* Attempt to get permission to access IO ports.  Must be root */

  if (qc_open(q)) {
    fprintf(stderr,"Cannot open QuickCam; exiting.\n");
    exit(1);
  }

  setuid(getuid());

  fprintf(stderr,"Scanning from QuickCam at 0x%x at %dx%d @ %dbpp\n",
	  q->port,q->width,q->height,q->bpp);

/*open the xform control panel*/
mainwin = create_form_QuickCam();
/*intialize slider controls*/
fl_set_slider_bounds(mainwin->Brightness, 0, 255);
fl_set_slider_step(mainwin->Brightness, 1);
fl_set_slider_precision(mainwin->Brightness, 0);
fl_set_slider_bounds(mainwin->Contrast, 0, 255);
fl_set_slider_step(mainwin->Contrast, 1);
fl_set_slider_precision(mainwin->Contrast, 0);
fl_set_slider_bounds(mainwin->WhiteBal, 0, 255);
fl_set_slider_step(mainwin->WhiteBal, 1);
fl_set_slider_precision(mainwin->WhiteBal, 0);

/*set the sliders to qcam values*/
fl_set_slider_value(mainwin->Brightness, q->brightness);
fl_set_slider_value(mainwin->Contrast, q->contrast);
fl_set_slider_value(mainwin->WhiteBal, q->whitebal);

/*set bpp buttons*/
if(q->bpp == 4)
	fl_set_button(mainwin->Bpp4, 1);
else
	fl_set_button(mainwin->Bpp6, 1);


/*show the form*/
fl_show_form(mainwin->QuickCam, FL_PLACE_SIZE, FL_FULLBORDER, "Control Panel");

  /* Prepare a window for camera output */

  if ((sbuf=InitXWindows(q))==NULL) exit(1);

/* set resolution */
if(q->width == 320)
	{ximage = imagehi;fl_set_button(mainwin->HiRes, 1);}
else
if(q->width == 160)
	{ximage = imagemed; fl_set_button(mainwin->ResMed, 1);}
else
	{ximage = imagelo; fl_set_button(mainwin->ResLo, 1);}

/*private map is probably bad news for xforms but I left it in- PWC*/
  if (privatecmap) {
    cmap = XCreateColormap(disp, win, DefaultVisual(disp, screen_num), 
			   AllocNone);
    XSetWindowColormap(disp, win, cmap);
  } else {
    cmap=DefaultColormap(disp, screen_num);
  }
  colortable=xqc_createpalette(cmap);


  /* Scan one image */
  if(verbose) {
    gettimeofday(&tv1,NULL);
  }
  
  while(!quit) {
	/*get current values from control panel*/
	q->brightness = fl_get_slider_value(mainwin->Brightness);
	q->contrast = fl_get_slider_value(mainwin->Contrast);
	q->whitebal = fl_get_slider_value(mainwin->WhiteBal);
    xqc_sync(q,colortable,sbuf);
	/*give xforms a little time to process control panel*/
	fl_check_forms();

    /* Calculate frame rate */
            if(verbose) {
      gettimeofday(&tv2,NULL);

      /* The frame rate is calculated using the TCP RTT algorithm */

      fr=(1.0/(tv2.tv_sec-tv1.tv_sec+(tv2.tv_usec-tv1.tv_usec)/1000000.0));
      if(framerate!=0)
	framerate=0.9*framerate+0.1*fr;
      else
	framerate=fr;

      fprintf(stderr,"Frame rate: %f fps       \r",framerate);
      tv1.tv_sec=tv2.tv_sec;
      tv1.tv_usec=tv2.tv_usec;
    }

  }

  ExitXWindows();

  /* Release IO privileges */
  qc_close(q);

  return 0;
}

