/*
  This is a simple Xwindow that displays live video
*/

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <string.h>
#include "videoRoutines.h"
#include "ppmIO.h"
#include "myXtools.h"
#include "util.h"

#define HEAD_ONLY 0

/*
  Global Variables determining window and display
*/
Display *curDisplay;
Window   curWindow;
GC       curGC; /* graphics context */
XImage  *xim1, *xim2;
int     defdepth;
int     bytesPerPixel;
Pixmap  gblPixmap;


/*
  Global Variables for userland stuff
*/
unsigned char *ximdata;

int initWindow(long nFrames) {
   int defScreen;
   XSetWindowAttributes wAttr;
   XGCValues gcValues;
   int bitmap_pad;
   long height = Ysize, width = Xsize;
   long *offsets;
   char buffer[64] = "Live Video";
   XTextProperty xtp = {buffer, 125, 8, strlen(buffer)};

   offsets = (long *)malloc(sizeof(long) * nFrames);
   
   /*
    * connect to the X server.  uses the server specified in the
    * DISPLAY environment variable
    */
   curDisplay = XOpenDisplay((char *) NULL);
   if ((Display *) NULL == curDisplay) {
      fprintf(stderr, "xskeleton:  could not open display.\n");
      exit(-1);
   }
   
   /*
    * begin to create a window
    */
   printf("creating window\n");
   defdepth = DefaultDepth(curDisplay,0);
   bytesPerPixel = defdepth/8;
   bytesPerPixel = bytesPerPixel == 3 ? 4 : bytesPerPixel;
   printf("default depth %d\n", defdepth);

   defScreen = DefaultScreen(curDisplay);
   printf("Default screen %d\n", defScreen);

   curWindow = XCreateWindow(curDisplay, DefaultRootWindow(curDisplay),
			     10, 10, Xsize, Ysize, 0, 
			     defdepth, InputOutput, 
			     DefaultVisual(curDisplay, defScreen),
			     0, &wAttr);

   printf("Using depth %d\n", DefaultDepth(curDisplay, 0));

   /*
    * request mouse button and keypress events
    */
   wAttr.event_mask = ButtonPressMask | KeyPressMask | ExposureMask;
   XChangeWindowAttributes(curDisplay, curWindow, CWEventMask, &wAttr);

   /*
    * force it to appear on the screen
    */
   XSetWMName(curDisplay, curWindow, &xtp);
   XMapWindow(curDisplay, curWindow);

   /*
    * create a graphics context.  this stores a drawing state; stuff like
    * current color and line width.  this gc is for drawing into our
    * window. 
    */
   printf("creating graphics context\n");
   curGC = XCreateGC(curDisplay, curWindow, 0, &gcValues);

   XSetWindowColormap(curDisplay,curWindow,DefaultColormapOfScreen(DefaultScreenOfDisplay(curDisplay)));

   /*
    *  open the video device, which returns a pointer to the image data
    */
   printf("Allocating space for %ld frames with %d bytes per pixel\n", nFrames, bytesPerPixel);
   ximdata = OpenVideoDevice(&height, &width, bytesPerPixel, nFrames, 1, offsets);

   /*
    * create one or two XImage structures for holding image data
    */

   bitmap_pad = defdepth == 24 ? 32 : defdepth;

   printf("creating frame 1 with depth %d\n", defdepth);
   xim1 = XCreateImage(curDisplay, DefaultVisual(curDisplay, defScreen), defdepth, 
		       ZPixmap, 0, &(ximdata[offsets[0]]), Xsize, Ysize, bitmap_pad, 0);

   if(nFrames > 1) {
     printf("creating frame 2 with depth %d\n", defdepth);
     xim2 = XCreateImage(curDisplay, DefaultVisual(curDisplay, defScreen), defdepth, 
			 ZPixmap, 0, &(ximdata[offsets[1]]), Xsize, Ysize, 
			 bitmap_pad, 0);
   }

   free(offsets);

   printf("exiting init window\n");
   return(bytesPerPixel);
}

void refreshWindow()
{
  // refresh the window here
  XPutImage(curDisplay, curWindow, curGC, xim1, 0, 0, 0, 0, Xsize, Ysize);

  return;
}

// close the window and video device
void closeWindow(long nFrames)
{
   CloseVideoDevice(ximdata, Ysize, Xsize, bytesPerPixel, nFrames);
   
   XCloseDisplay(curDisplay);

   return;
}

int main()
{
   Bool   done = False;
   XEvent curEvent;
   char c;
   long event_mask = 0xFFFFFFFF;
   long nBuf = 2;
   long nFrames = 0;
   long start, end;
   Pixel *ppm;
   char ppmfilename[128];
   int ppmcounter = 0;

   /* My variables */
   unsigned char *bg = NULL;
   int i, fsize, nRegions;
   Point_t location[VISION_MAX_LOCATIONS];  //centroids
   Box_t bbox[VISION_MAX_LOCATIONS];        //bounding boxes
   Box_t headbox[VISION_MAX_LOCATIONS];     //bounding boxes for heads
   long size[VISION_MAX_LOCATIONS];         //size of each region
   short *regmap;
   unsigned char *binimage;
   int update = 20;
   
   /* memory allocation */
   fsize = Xsize*Ysize*4;     
   bg = (unsigned char *)malloc(fsize*4*sizeof(unsigned char));
   regmap = malloc(sizeof(short) * Xsize*Ysize);
   binimage = malloc(sizeof(unsigned char) * Xsize*Ysize);

   /* initialize the window and video device */
   initWindow(nBuf);

   start = time(NULL);

   /*
    * Interleave the video capture so we get real-time video (30 fps)
    */
   CaptureVideo(0);
   CaptureVideo(1);

   while(!done) {
     // get another picture
     SyncVideo(0);

     /*-------------------------------------------------*/
     /* check to see if background should be updated,
      * only updates if  current frame hasn't changed much
      * for the past 40 frames, I am only checking it for
      * xim1 and not xim2 */

     if (check_bg(xim2->data, xim1->data, fsize))
       update++;
     //printf("update: %i\n", update);
     
     if(update >= 20) {
       for(i=0; i<fsize; i++)
	 bg[i] = xim1->data[i];
       update = 0;
     }
  
     null_bg(bg, xim1->data, fsize);
     isolate_skin(xim1->data, fsize);
     ConvertToBinary(xim1->data, fsize, binimage);
     nRegions = locateRegions(binimage, Ysize, Xsize, 300, location, bbox, size, regmap);

#if HEAD_ONLY
     nRegions = get_heads(bbox, headbox, size, nRegions);
     show_box(xim1->data, headbox, Xsize, Ysize, nRegions);
#else
     show_box(xim1->data, bbox, Xsize, Ysize, nRegions);
#endif

     /*-------------------------------------------------*/

     XPutImage(curDisplay, curWindow, curGC, xim1, 0, 0, 0, 0, Xsize, Ysize);
     CaptureVideo(0);
     nFrames++;

     SyncVideo(1);

     /*-------------------------------------------------*/     
     
     /* if (check_bg(xim1->data, xim2->data, fsize))
       update++;
     //printf("update: %i\n", update);

     if(update >= 20) {
       for(i=0; i<fsize; i++)
	 bg[i] = xim2->data[i];
       update = 0;
     }
     */
     
     null_bg(bg, xim2->data, fsize);
     isolate_skin(xim2->data, fsize);
     ConvertToBinary(xim1->data, fsize, binimage);
     nRegions = locateRegions(binimage, Ysize, Xsize, 300, location, bbox, size, regmap);

#if HEAD_ONLY
     nRegions = get_heads(bbox, headbox, size, nRegions);
     show_box(xim2->data, headbox, Xsize, Ysize, nRegions);
#else
     show_box(xim2->data, bbox, Xsize, Ysize, nRegions);
#endif

     /*-------------------------------------------------*/

     XPutImage(curDisplay, curWindow, curGC, xim2, 0, 0, 0, 0, Xsize, Ysize);
     CaptureVideo(1);
     nFrames++;


     if(XCheckWindowEvent(curDisplay, curWindow, event_mask, &curEvent)) {

       switch (curEvent.type) {

       case ButtonPress:
	 /*
	  * you can access button by curEvent.xbutton.button (1,2,3)
	  * you can access position by curEvent.xbutton.x and .y
	  *
	  * This little piece writes out an image if you click the first mouse button
	 */
	 if(curEvent.xbutton.button == 1) {
	   // write out the current frame to a ppm
	   SyncVideo(0);

	   /* --------------------------------- */
	   null_bg(bg, xim1->data, fsize);
	   isolate_skin(xim1->data, fsize);
	   ConvertToBinary(xim1->data, fsize, binimage);
	   nRegions = locateRegions(binimage, Ysize, Xsize, 300, location, bbox, size, regmap);

#if HEAD_ONLY
	   nRegions = get_heads(bbox, headbox, size, nRegions);
	   show_box(xim1->data, headbox, Xsize, Ysize, nRegions);
#else
	   show_box(xim1->data, bbox, Xsize, Ysize, nRegions);
#endif
	   /* --------------------------------- */

	   ppm = ConvertToPPM(xim1->data, Xsize, Ysize, bytesPerPixel);
	   sprintf(ppmfilename, "frame.%03d.ppm", ppmcounter++);
	   writePPM(ppm, Ysize, Xsize, 255, ppmfilename);

	   free(ppm);

	   CaptureVideo(0);
	   SyncVideo(1);
	   CaptureVideo(1);
	   
	 }
	 else if(curEvent.xbutton.button == 2) {
	 }
	 break;

       case KeyPress:
	 /*
	   you can access string using XLookupString(*event,*char,numChars,NULL,NULL)
	 */
	 if(XLookupString((XKeyEvent *)&curEvent, &c, 1, NULL, NULL) == 1) {
	   switch(c) {
	     /* 
	      * This quits the program if the user hits q in the video window
	      */
	   case 'q':
	     done = True;
	     break;

	     /* 
	      * These set up which video channel it's looking at
	      */
	   case '0':
	   case '1':
	   case '2':
	   case '3':
	     SyncVideo(0);
	     SyncVideo(1);

	     SetVideoChannel(c - '0');

	     CaptureVideo(0);
	     CaptureVideo(1);
	     break;
	   default:
	     break;
	   }
	 }
	 break;

       case Expose:
	 refreshWindow();

       default:
	 break;
       }
     }
   }
   end = time(NULL);

   printf("Frame rate = %.2f/s\n", (double)nFrames / (double)(end - start));

   closeWindow(nBuf);

   return(0);
}


