/* cqcam - Color Quickcam capture programs
 * Copyright (C) 1996-1998 by Patrick Reynolds
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

// 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 <reynolds@cs.duke.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 "xscan.h"
#include <camera.h>
#include <imager.h>
#include <config.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 *

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

// returns the highest bit set in a word, or -1 if none
static int highbit(unsigned long n) {
  int i;
  for (i=31; ((n & 0x80000000) == 0) && i>=0; i--)
    n <<= 1;
  return i;
}
 
// 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 (depth != 8 && depth != 16 && depth != 15 && depth != 24 && depth != 32) {
    fprintf(stderr, "Depth %d not supported.\n", depth);
    exit(-1);
  }

  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 =
      (char*)shmat(SHMInfo.shmid, 0, 0);

    XShmAttach(disp, &SHMInfo);

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

    if (depth == 24) {
      // 24bpp breaks a lot... I'm using a bitmap_pad of 8 now, and
      // it seems to work under XFree86 3.3.1.  32bpp still doesn't.
      sbuf = new char[4 * width * height];
      ximage = XCreateImage(disp, DefaultVisual(disp, screen_num), 
                            depth, ZPixmap, 0, sbuf, width, height, 32,
                            0);
    }
    else {   // all other depths: 8, 16, 32
      sbuf = new char[depth/8 * width * height];
      ximage = XCreateImage(disp, DefaultVisual(disp, screen_num), 
                            depth, ZPixmap, 0, sbuf, width, height, depth,
                            0);
    }
    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
  fprintf(stderr, "Could not allocate even 8 color cells.\n");
  fprintf(stderr, "Close color-hungry programs, change your color depth,\n");
  fprintf(stderr, "or use the -p+ option.\n");
  exit(-1);
}


void scan_show(camera_t *cam, char *sbuf) {
  int i;
  int limit;
  unsigned char *scan;
  int 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);
    #ifdef DEBUG
      fprintf(stderr, "Color adjustments: red=%d%%  green=%d%%  blue=%d%%\n",
        rtemp * 100 / 128, gtemp * 100 / 128, btemp * 100 / 128);
    #endif
#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) {
      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);
      }
    }
    flag_auto ^= AUTO_SAVE;
  }
#endif
  if (depth == 8)
    scan = rgb_2_pal(scan, width, height, palsize, pal, rgb);

  // XImage code is awful.  If you know something about it, please feel
  // free to send me patches to improve this...  What you see here draws
  // ideas (no actual code) from Mosaic and XV, though Mosaic is actually
  // broken for 24bpp displays.
  //
  // XImage code can be used to scare small children.
  //   --me, with apologies to Linus
  switch (depth) {
    case 8: {
        unsigned char *sbp = (unsigned char *)sbuf, *scp = scan;
        for (i=0; i<limit; i++, sbp++, scp++)
          *sbp = pixels[*scp];  // efficient form of: sbuf[i] = pixels[scan[i]];
      }
      break;
    case 15:
    case 16: {
        Visual *theVisual = DefaultVisual(disp, DefaultScreen(disp));
        int rshift = 15 - highbit(theVisual->red_mask);
        int gshift = 15 - highbit(theVisual->green_mask);
        int bshift = 15 - highbit(theVisual->blue_mask);
        unsigned char *sbp = (unsigned char *)sbuf, *scp = scan;
        unsigned int temp;
        for (i=0; i<limit; i++) {
          temp = (((*scp++ << 8) >> rshift) & theVisual->red_mask);
          temp |= (((*scp++ << 8) >> gshift) & theVisual->green_mask);
          temp |= (((*scp++ << 8) >> bshift) & theVisual->blue_mask);
          *sbp++ = temp & 0xff;
          *sbp++ = (temp >> 8) & 0xff;
        }
      }
      break;
    case 32:  // do we ever actually see depth==32?
    case 24: {
        Visual *theVisual = DefaultVisual(disp, DefaultScreen(disp));
        int rshift = highbit(theVisual->red_mask) - 7;
        int gshift = highbit(theVisual->green_mask) - 7;
        int bshift = highbit(theVisual->blue_mask) - 7;
        int bpp = ximage->bits_per_pixel;  // not necessarily == depth (!)
        unsigned char *sbp = (unsigned char *)sbuf, *scp = scan;
        unsigned int temp;
        for (i=0; i<limit; i++) {
          temp = (*scp++ << rshift);
          temp |= (*scp++ << gshift);
          temp |= (*scp++ << bshift);
          if (bpp == 32) {
            *sbp++ = (unsigned char)(temp & 0xff);
            *sbp++ = (unsigned char)((temp >> 8) & 0xff);
            *sbp++ = (unsigned char)((temp >> 16) & 0xff);
            *sbp++ = (unsigned char)((temp >> 24) & 0xff);
          }
          else {  // bpp == 24, or at least we hope so...
            *sbp++ = (unsigned char)((temp) & 0xff);
            *sbp++ = (unsigned char)((temp >> 8) & 0xff);
            *sbp++ = (unsigned char)((temp >> 16) & 0xff);
          }
        }
      }
      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);
  }

  if (depth <= 8)  // should this check involve visual rather than depth?
    xqc_createpalette(cmap);

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

#ifdef REMOTE
  xqc_fork(argc, argv, cam, dpy_name);
  signal(SIGCHLD, quitprogram);
#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();
}
