// XWindow Support for QuickCam (used by xcqcam.C)
//
// by: Paul Chinn <loomer@svpal.org>
// Modified by: Scott Laird <scott@laird.com>
// Modified heavily for "control center" support, image capturing, and xcqcam
// compatibility by: Patrick Reynolds <patrickr@virginia.edu>
 
// 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 <sys/stat.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>

#include "xqcctl.h"
#include "camera.h"
#include "imager.h"
#include "config.h"
#include "xscan.h"

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


// ** Various X-related variables ******************************

Screen *screen;
Display *disp;
Window root,win;
XColor col;
Colormap cmap;
XImage *ximage;
XEvent ev;
GC gc;
int screen_num;
unsigned long white,black;
int xstarted=0; 
int quit=0;
int depth;
int flag_auto;
int upper_bound, lower_bound, bri_loops;
int pal[256][3];
int rgb[256][3];
int palsize;
unsigned long pixels[256];

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

void quitprogram(int foo) {
  foo=quit=1;
}
 
// Initialize xwindows, and prepare a shared memory buffer for
// the image.  Returns pointer to shared memory buffer.
 
char *InitXWindows(camera_t *cam, char *dpy_name, int shm) {
  XGCValues values;
  XSizeHints hints;
  XWMHints wmhints;
  int width = cam->get_pix_width(), height = cam->get_pix_height();
  char *sbuf;
  char *window_name="QuickCam";
  char *icon_name="QuickCam";
  XTextProperty windowName, iconName;

  disp=XOpenDisplay(dpy_name);
  if(!disp) {fprintf(stderr, "open display failed\n"); return NULL;}
  
  screen=DefaultScreenOfDisplay(disp);
  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) {  
    fprintf(stderr, "create window failed\n");
    return(NULL); 
  }
  
  // tell window manager about our window
  hints.flags=PSize|PMaxSize|PMinSize;
  hints.min_width=hints.max_width=hints.width=width;
  hints.min_height=hints.max_height=hints.height=height;
  wmhints.input=True;
  wmhints.flags=InputHint;

  XStringListToTextProperty(&window_name, 1 ,&windowName);
  XStringListToTextProperty(&icon_name, 1 ,&iconName);


  XSetWMProperties(disp, win, 
		   &windowName, &iconName,
		   NULL, 0,
		   &hints, &wmhints, NULL);
  
  XSelectInput(disp, win, ExposureMask);
  XMapRaised(disp, win);
  XNextEvent(disp, &ev);
  
  gc = XCreateGC(disp, win, 0, &values);

  XSync(disp, 0);

  depth = DefaultDepthOfScreen(screen);

  if (shm) {
    ximage = XShmCreateImage(disp, DefaultVisual(disp, screen_num), 
      depth, ZPixmap, NULL, &SHMInfo, width, height);
    if(!ximage) {
      fprintf(stderr, "CreateImage Failed\n");
      return(NULL);
    }
    SHMInfo.shmid=shmget(IPC_PRIVATE, ximage->bytes_per_line*ximage->height,
      IPC_CREAT|0777);

    if(SHMInfo.shmid < 0) {
      perror("shmget failed:");
      return (NULL);
    }
    sbuf = ximage->data = SHMInfo.shmaddr =
      shmat(SHMInfo.shmid, 0, 0);

    XShmAttach(disp, &SHMInfo);

    XSync(disp, 0);
  }
  else {   // !shm
    shmdt(SHMInfo.shmaddr);
    SHMInfo.shmaddr = 0;

    if (depth == 24) {
      sbuf = new char[4 * width * height];  // 24 bpp is padded to 32 bpp
                                            // XFree86 3.1.2
      ximage = XCreateImage(disp, DefaultVisual(disp, screen_num), 
			    depth, ZPixmap, 0, sbuf, width, height, depth,
			    width * 4);
    }
    else {   // all other depths: 1, 4, 8, 16
      sbuf = new char[((depth + 7) / 8) * width * height];
      ximage = XCreateImage(disp, DefaultVisual(disp, screen_num), 
			    depth, ZPixmap, 0, sbuf, width, height, depth,
			    width * ((depth + 7) / 8));
    }
    if(!ximage) {
      fprintf(stderr, "CreateImage Failed\n");
      return(NULL);
    }
  }
  signal(SIGHUP, quitprogram); 
  signal(SIGINT, quitprogram);
  signal(SIGQUIT, quitprogram); 
  signal(SIGTERM, quitprogram);
  xstarted=1;
  return(sbuf);
}


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

void xqc_createpalette(Colormap cmap) {
  for (int trysize=6; trysize>=2; trysize--) {
#ifdef DEBUG
    fprintf(stderr, "trying %dx%dx%d palette...",
      trysize, trysize, trysize);  fflush(stderr);
#endif
    unsigned int ncol = trysize * trysize * trysize;
    allocate_rgb_palette(trysize, pal, rgb);
    unsigned long ign[1];
    if (!XAllocColorCells(disp, cmap, 1, ign, 0, pixels, ncol)) {
#ifdef DEBUG
      fprintf(stderr, " failed\n");
#endif
      continue;
    }
    XColor ctable[256];
    for (unsigned int i=0; i<ncol; i++) {
      ctable[i].pixel = pixels[i];
      ctable[i].red = pal[i][0] * 256;
      ctable[i].green = pal[i][1] * 256;
      ctable[i].blue = pal[i][2] * 256;
      ctable[i].flags = DoRed | DoGreen | DoBlue;
    }
    XStoreColors(disp, cmap, ctable, ncol);
    palsize = trysize;
#ifdef DEBUG
    fprintf(stderr, " success.\n");
#endif
    return;
  } // for trysize
}


void scan_show(camera_t *cam, char *sbuf) {
  int i;
  int limit;
  unsigned char *scan;
  int temp, width, height;

  width = cam->get_pix_width();
  height = cam->get_pix_height();
  scan = cam->get_frame();
  if (cam->get_bpp() == 32)
    scan = raw32_to_24(scan, width, height);

  limit = height * width;

  if (flag_auto & AUTO_BAL) {
    int rtemp = cam->get_red();
    int gtemp = cam->get_green();
    int btemp = cam->get_blue();
    get_rgb_adj(scan, limit, rtemp, gtemp, btemp);
    cam->set_red(rtemp);
    cam->set_green(gtemp);
    cam->set_blue(btemp);
    if (!(flag_auto & AUTO_BRIGHT))
      flag_auto ^= AUTO_BAL;
#ifdef REMOTE
    xqc_adj(cam);
#endif
  }

  if (flag_auto & AUTO_BRIGHT) {
    int britemp;
    int done = get_brightness_adj(scan, limit, britemp);
    if (!done) {
      int cur_bri = cam->get_brightness() + britemp;
      if (cur_bri > upper_bound)
        cur_bri = upper_bound - 1;
      if (cur_bri < lower_bound)
        cur_bri = lower_bound + 1;
      if (britemp > 0)
        lower_bound = cam->get_brightness() + 1;
      else
        upper_bound = cam->get_brightness() - 1;
#ifdef DEBUG
        fprintf(stderr, "Brightness %s to %d (%d..%d)\n",
          (britemp<0)? "decreased" : "increased", cur_bri, lower_bound,
          upper_bound);
#endif
      cam->set_brightness(cur_bri);
    }
    if (done || upper_bound <= lower_bound || ++bri_loops > 10)
      flag_auto ^= AUTO_BRIGHT;
#ifdef REMOTE
    xqc_adj(cam);
#endif
  }

  // take care of rgb adjustments here so that they'll be effective in
  // saved pictures
  do_rgb_adj(scan, limit, cam->get_red(), cam->get_green(), cam->get_blue());

#ifdef DESPECKLE
  if (cam->get_bpp() == 24)
    scan = despeckle(scan, width, height);
#endif

#ifdef REMOTE
  if (flag_auto & AUTO_SAVE) {
    FILE *outfile;
    char buf[100], *p;
    umask(077);  // let's keep the pix private; you never know...  :)
    fprintf(stderr, "Enter a filename: ");  fflush(stderr);
    if (fgets(buf, 99, stdin) != NULL) {
      int saveuid = getuid();
      setreuid(0, saveuid);  // give up powers but be able to get them back
      if ((p = strchr(buf, '\n')) != NULL) p[0] = '\0';
      if (buf[0]) {  // only bother if the user typed something in
        if ((outfile = fopen(buf, "w")) == NULL) perror("fopen");
        else write_ppm(outfile, scan, width, height);
      }
      setreuid(saveuid, 0);  // restore powers after file is written
    }
    flag_auto ^= AUTO_SAVE;
  }
#endif
  if (depth == 8)
    scan = rgb_2_pal(scan, width, height, palsize, pal, rgb);

  for(i=0;i<limit;i++)
    {
      unsigned char r = 0, g = 0, b = 0;
      if (depth != 8) {
        r = scan[i*3];    // inefficient, but memory copies are *not* our
        g = scan[i*3+1];  // bottleneck in this app
        b = scan[i*3+2];
      }
      switch(depth) {  
        case 8:
          sbuf[i] = pixels[scan[i]];
          break;
        case 16:                // works - XFree 3.1.2G 16bpp, weight 565
          temp = b >> 3;
          temp |= (int)(g >> 2) << 5;
          temp |= (int)(r >> 3) << 11;
          sbuf[2*i+1]=temp >> 8;
          sbuf[2*i]=temp & 0xff;
          break;
        case 24:                // correct for XFree86 3.1.2
	  sbuf[(i<<2)] = b;
	  sbuf[(i<<2)+1] = g;
	  sbuf[(i<<2)+2] = r;
          sbuf[(i<<2)+3] = 0;
	  break;
	case 32:		// correct for XFree86 3.1.2
	  sbuf[(i<<2)] = b;
	  sbuf[(i<<2)+1] = g;
	  sbuf[(i<<2)+2] = r;
          sbuf[(i<<2)+3] = 0;
	  break;
	}
    }
  delete[] scan;
  
  if (SHMInfo.shmaddr) {
    XShmPutImage(disp, win, gc, ximage, 0, 0, 0, 0, width, height, False);
  } else {
    XPutImage(disp, win, gc, ximage,  0, 0, 0, 0, width, height);
  }
  XFlush(disp);
}

void x_scan(camera_t *cam, int argc, char *argv[], struct xqc_flags_t xflags,
  char *dpy_name) {
  char *sbuf;

  if ((sbuf=InitXWindows(cam, dpy_name, xflags.shm))==NULL) {
    fprintf(stderr,"InitXWindows failed, exiting\n");
    exit(1);
  }

  if (xflags.prv_cmap) {
    cmap = XCreateColormap(disp, win, DefaultVisual(disp, screen_num), 
			   AllocNone);
    XSetWindowColormap(disp, win, cmap);
  } else {
    cmap=DefaultColormap(disp, screen_num);
  }

  xqc_createpalette(cmap);

  flag_auto = xflags.adj ? (AUTO_BAL | AUTO_BRIGHT) : (AUTO_BAL);
  upper_bound = 253;
  lower_bound = 5;
  bri_loops = 0;

#ifdef REMOTE
  xqc_fork(argc, argv, cam, dpy_name);
#ifdef DEBUG
  fprintf(stderr, "Waiting for control panel to open");
#endif
  while (xqc_poll(cam) != XQC_STARTUP) {
#ifdef DEBUG
    fprintf(stderr, ".");
#endif
    sleep(1);
  }
#ifdef DEBUG
  fprintf(stderr, "\n");
#endif
#endif  // REMOTE
  while(!quit) {
    scan_show(cam, sbuf);
#ifdef REMOTE
    switch (xqc_poll(cam)) {
      case -1:                   quit = 1;                               break;
      case XQC_ADJUST:           flag_auto |= (AUTO_BAL | AUTO_BRIGHT);
                                 upper_bound = 253;
                                 lower_bound = 5;
                                 bri_loops = 0;                          break;
      case XQC_TAKE:             flag_auto |= AUTO_SAVE;                 break;
    }
#endif
  }
#ifdef REMOTE
  xqc_quit();
#endif

  ExitXWindows();
}
