/* 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.
 */

// command-line streaming-webcam command
// This probably won't be here forever, since it only handles one client
// at once, and I still intend to do a multi-client thing, RSN.
//
// original webcam hack added by Håkan Lennestål

#undef PIPEMODE // doesn't work yet

#define SEP "ThisRandomString"
#ifdef PIPEMODE
#define MAXPIPES 10
#endif

#ifndef JPEG
#error JPEG support is required to compile webcam support.
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "camera.h"
#include "imager.h"
#include "rcfile.h"

#include "config.h"

#ifndef CLAMP
#define CLAMP(a,b,c) ((a)<(b)?(b):((a)>(c)?(c):(a)))
#endif

// parse command-line options
// in pass 1, look for -h (help) or any invalid options before trying to
// start up the camera.  If the i/o port is an option, it should be pass
// 1, also.
// in pass 2, look for everything that requires an initialized camera
// object
void parse_opts(int argc, char **argv, camera_t *camera, int pass);

// print x in binary format (MSB->LSB) on the output stream where
void printbin(FILE *where, int x);

#ifdef PIPEMODE
// send an image through a PPM filter via pipes
void do_pipes(unsigned char **img, int *width, int *height, char *pipes);
#endif

static int red = -1, green = -1, blue = -1;
static int auto_adj = AUTO_ADJ_DEFAULT;
static int iport = DEFAULT_PORT, idetect = DEFAULT_DETECT_MODE;
static int ibpp = DEFAULT_BPP;
static int idecimation = DEFAULT_DECIMATION;
static int remember = 0;
static int jpeg_quality = 50;
static int loopm = 20;    // maximum number of frames to show (user can
                          // halt by hitting "stop" in his/her browser)
#ifdef PIPEMODE
static char *ppmpipe = NULL;
#endif

int main(int argc, char **argv) {
  int frames_so_far = 0;

  parse_opts(argc, argv, NULL, 1);     // look for pass 1 or invalid options

  camera_t camera(iport, idetect); // probe for and initialize the beast
#ifndef LYNX
  setgid(getgid());
  setuid(getuid());
#endif
  camera.set_bpp(ibpp);
  camera.set_decimation(idecimation);

  parse_opts(argc, argv, &camera, 2);  // pass 2: all the other options
#ifdef DEBUG
  fprintf(stderr, "Camera version: 0x%x.\n", camera.get_version());
  fprintf(stderr, "Camera status:  ");
  printbin(stderr, camera.get_status());
  fprintf(stderr, "\n");
#endif
  unsigned char *scan;
  int width = camera.get_pix_width();
  int height = camera.get_pix_height();

  printf("Content-type: multipart/x-mixed-replace;boundary=" SEP "\n");
  printf("Pragma: no-cache\n\n");
  fflush(stdout);

  if (auto_adj) {
    int done = 0;
    int upper_bound = 253, lower_bound = 5, loops = 0;
    do {
      printf("\n--" SEP "\n");
      fflush(stdout);

      scan = camera.get_frame();
      if (camera.get_bpp() == 32)
        scan = raw32_to_24(scan, width, height);
      int britemp = 0;
      done = get_brightness_adj(scan, width * height, britemp);
      if (!done) {
        int cur_bri = camera.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 = camera.get_brightness() + 1;
        else
          upper_bound = camera.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
        camera.set_brightness(cur_bri);

        if (red == -1 && green == -1 && blue == -1)
          get_rgb_adj(scan, width * height, red, green, blue);
        else {
          if (red == -1) red = 128;
          if (green == -1) green = 128;
          if (blue == -1) blue = 128;
        }
        camera.set_red(red);
        camera.set_green(green);
        camera.set_blue(blue);
#ifdef DEBUG
        fprintf(stderr, "Color adjustments: red=%d%%  green=%d%% blue=%d%%\n",
          red * 100 / 128, green * 100 / 128, blue * 100 / 128);
#endif

#ifdef DESPECKLE
        if (camera.get_bpp() == 24)
          scan = despeckle(scan, width, height);
#endif

        do_rgb_adj(scan, width * height,
          camera.get_red(), camera.get_green(), camera.get_blue());

        printf("Content-type: image/jpeg\n\n");
        write_jpeg(stdout, scan, width, height, jpeg_quality);
        fflush(stdout);

        delete[] scan;
      }
    } while (!done && upper_bound > lower_bound && ++loops <= 10 &&
        ++frames_so_far < loopm );
  }

  for ( ; frames_so_far<loopm; frames_so_far++) {
    printf("\n--" SEP "\n");
    fflush(stdout);

    scan = camera.get_frame();
    if (camera.get_bpp() == 32)
      scan = raw32_to_24(scan, width, height);

    if (red == -1 && green == -1 && blue == -1)
      get_rgb_adj(scan, width * height, red, green, blue);
    else {
      if (red == -1) red = 128;
      if (green == -1) green = 128;
      if (blue == -1) blue = 128;
    }
    camera.set_red(red);
    camera.set_green(green);
    camera.set_blue(blue);
#ifdef DEBUG
    fprintf(stderr, "Color adjustments: red=%d%%  green=%d%%  blue=%d%%\n",
      red * 100 / 128, green * 100 / 128, blue * 100 / 128);
#endif

#ifdef DESPECKLE
    if (camera.get_bpp() == 24)
      scan = despeckle(scan, width, height);
#endif

    do_rgb_adj(scan, width * height,
      camera.get_red(), camera.get_green(), camera.get_blue());

#ifdef PIPEMODE
    do_pipes(&scan, &width, &height, ppmpipe);
#endif

    printf("Content-type: image/jpeg\n\n");
    write_jpeg(stdout, scan, width, height, jpeg_quality);
    fflush(stdout);

    delete[] scan;
  }

  if (remember) {
    char *nfn = resolve_home_dir("~/.cqcrc");
    char *ofn = resolve_home_dir("~/.cqcrc.old");
    rename(nfn, ofn);  // ignore errors here
    FILE *qcrc = fopen(ofn, "r+");
    FILE *nqcrc = fopen(nfn, "w");
    if (nqcrc == NULL) {
      perror(nfn);
      exit(1);
    }
    if (qcrc != NULL) {
      char buf[200];
      while (!feof(qcrc)) {
        fgets(buf, 200, qcrc);
        if (!feof(qcrc) && strstr(buf, "brightness") == NULL)
          fputs(buf, nqcrc);
      }
      fclose(qcrc);
    }
    fprintf(nqcrc, "brightness\t%d\n", camera.get_brightness());
    fclose(nqcrc);
  }
  return 0;
}

void kaput(const char *progname, const char* str) {
  fprintf(stderr, "%s: %s\n", progname, str);
  exit(1);
}

void print_usage(const char *progname) {
  fprintf(stderr, "Usage: %s [options]\n\n", progname);
  fprintf(stderr, "  -32[+|-]    Turn 32-bpp mode on or off (off = 24bpp)\n");
  fprintf(stderr,
    "  -a[+|-]    Use/suppress brightness and color balance auto-adjustments\n");
  fprintf(stderr, "  -b val     Set brightness\n");
  fprintf(stderr, "  -B val     Set black level\n");
  fprintf(stderr, "  -c val     Set contrast\n");
  fprintf(stderr, "  -d val     Specify (or skip) camera-detection\n"); 
  fprintf(stderr, "  -E val     Set software blue level\n");
  fprintf(stderr, "  -G val     Set software green level\n");
  fprintf(stderr, "  -h         View this brief help screen\n");
  fprintf(stderr, "  -H val     Set hue (blue level)\n");
  fprintf(stderr, "  -l val     Set left column\n");
  fprintf(stderr, "  -L val     Set maximum number of frames\n");
#ifdef PIPEMODE
  fprintf(stderr, "  -p filter[,filter[,filter]]\n");
  fprintf(stderr, "             Use PPM filter(s) before sending the image\n");
#endif
  fprintf(stderr,
    "  -P val     Set port to attempt (val must be in hex format)\n");
  fprintf(stderr, "  -q val     Set JPEG quality (default=50)\n");
  fprintf(stderr, "  -r         Remember (store) the brightness in .cqcrc\n");
  fprintf(stderr, "  -R val     Set software red level\n");
  fprintf(stderr, "  -s val     Set scale factor (decimation)\n");
  fprintf(stderr, "  -S val     Set saturation\n");
  fprintf(stderr, "  -t val     Set top row\n");
  fprintf(stderr, "  -u         Force a unidirectional port mode\n");
  fprintf(stderr, "  -V         Print version information and exit\n");
  fprintf(stderr, "  -w val     Set white level\n");
  fprintf(stderr, "  -x val     Set width\n");
  fprintf(stderr, "  -y val     Set height\n");
  exit(1);
}

void parse(char *sw, char *opt, char *name, int pass, camera_t *camera,
  int &i) {
  if (pass == 1)
    switch(sw[1]) {
      case 'h':  print_usage(name);  break;
#ifdef PIPEMODE
      case 'p':  if (opt) {
          ++i;
          ppmpipe = opt;
        }
        else
          kaput(name, "option requires an argument: -p");
        break;
#endif
      case 'P':  if (opt) {
          ++i;
          if (!strncmp(opt, "0x", 2)) opt += 2;
          if (!sscanf(opt, "%x", &iport)) {
            fprintf(stderr, "%s: bad port number: %s\n", name, opt);
            exit(1);
          }
#ifdef DEBUG 
          else
            fprintf(stderr, "port set to: 0x%x\n", iport);
#endif
        }
        else
          kaput(name, "option requires an argument: -P");
        break;
      case 'd':  ++i;
        if (opt) idetect = atoi(opt);
        else
          kaput(name, "option requires an argument: -d");
        break; 
      case 'r':  remember = 1;
        break;
      case 'a':  switch (sw[2]) {
          case '+': auto_adj = 1; break;
          case '-': auto_adj = 0; break;
          default:  auto_adj = !AUTO_ADJ_DEFAULT;
        }
        break;
      case 's':  ++i;
        if (opt) idecimation = atoi(opt);
        else kaput(name, "option requires an argument: -s");
        break;
      case 'L':  ++i;
        if (opt) loopm= atoi(opt);
        else kaput(name, "option requires an argument: -L");
        break;
      case 'V':  kaput(name, CQC_VERSION);
      case '3':  if (!strncmp(sw, "-32", 3)) {
          switch (sw[3]) {
            case '+': ibpp = 32; break;
            case '-': ibpp = 24; break;
            default:  ibpp = (56-DEFAULT_BPP);
          }
        }
        else {
          fprintf(stderr, "%s: unknown option %s\n", name, sw);
          exit(1);
        }
        break;
      case 'E':  ++i;
        if (opt) blue = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -E");
        break;
      case 'G':  ++i;
        if (opt) green = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -G");
        break;
      case 'R':  ++i;
        if (opt) red = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -R");
        break;
      case 'q':  ++i;
        if (opt) jpeg_quality = atoi(opt);
        else
          kaput(name, "option requires an argument: -q");
        break;
      // these all get processed in pass 2
      // this group all takes one input, so i++ to ignore it this pass
      case 'b':  case 'B':  case 'c':  case 'H':  case 'l':
      case 'S':  case 't':  case 'w':  case 'x':
      case 'y':  i++;

      case 'u':  break;
      default:   fprintf(stderr, "%s: unknown option: -%c\n", name, sw[1]);
                 exit(1);
    }
  else
    switch (sw[1]) {
      case 'b':  ++i;
        if (opt) camera->set_brightness(atoi(opt));
        else kaput(name, "option requires an argument: -b");
        break;
      case 'B':  ++i;
        if (opt) camera->set_black_level(atoi(opt));
        else kaput(name, "option requires an argument: -B");
        break;
      case 'c':  ++i;
        if (opt) camera->set_contrast(atoi(opt));
        else kaput(name, "option requires an argument: -c");
        break;
      case 'q':
      case 's':
      case 'L':
      case 'E':
      case 'G':
      case 'R':
      case 'd':  i++;  break;  // we already dealt with this in pass 1
      case 'H':  ++i;
        if (opt) camera->set_hue(atoi(opt));
        else kaput(name, "option requires an argument: -H");
        break;
      case 'l':  ++i;
        if (opt) camera->set_left(atoi(opt));
        else kaput(name, "option requires an argument: -l");
        break;
#ifdef PIPEMODE
      case 'p':  i++;  break;  // we already dealt with this in pass 1
#endif
      case 'P':  i++;  break;  // we already dealt with this in pass 1
      case 'S':  ++i;
        if (opt) camera->set_saturation(atoi(opt));
        else kaput(name, "option requires an argument: -S");
        break;
      case 't':  ++i;
        if (opt) camera->set_top(atoi(opt));
        else kaput(name, "option requires an argument: -t");
        break;
      case 'u':  camera->set_port_mode(QC_UNI_DIR); break;
      case 'w':  ++i;
        if (opt) camera->set_white_level(atoi(opt));
        else kaput(name, "option requires an argument: -w");
        break;
      case 'x':  ++i;
        if (opt) camera->set_width(atoi(opt));
        else kaput(name, "option requires an argument: -x");
        break;
      case 'y':  ++i;
        if (opt) camera->set_height(atoi(opt));
        else kaput(name, "option requires an argument: -y");
        break;
    }
}

void parse_opts(int argc, char **argv, camera_t *camera, int pass) {
  int i;
  rcfile_t rc;
  char *sw, *opt;
  rc.get(&sw, &opt, 1);
  while (sw != NULL) {
    parse(sw, opt, argv[0], pass, camera, i);
    rc.get(&sw, &opt, 0);
  }
  for (i=1; i<argc; i++)
    if (argv[i][0] == '-')
      parse(argv[i], (i+1<argc)?argv[i+1]:(char *)NULL, argv[0], pass, 
        camera, i);
    else {
      fprintf(stderr, "%s: unknown option: %s\n", argv[0], argv[i]);
      exit(1);
     }
  if (getuid() != 0 && iport != DEFAULT_PORT) {
    fprintf(stderr, "%s: port == 0x%x: permission denied\n", argv[0], iport);
    exit(1);
  }
}

void printbin(FILE *where, int x) {
  int b;
  for (b=0;b<8;b++) {
    fprintf(where, "%d", (x & 0x80) >> 7);
    x *= 2;
  }
}

#ifdef PIPEMODE
void do_pipes(unsigned char **img, int *width, int *height, char *pipes) {
  int p[2];
  fd_set rfds, wfds;

  if (!pipes || !img || !*img || !width || *width < 1 || !height || *height < 1)
    return;

  if (pipe(p) < 0) { perror("pipe"); return; }
  switch (fork()) {
    case -1:  perror("fork"); return;
    case 0:
      dup2(p[0], 0);
      dup2(p[1], 1);
      close(p[0]);
      close(p[1]);
#ifdef LYNX  // Lynx is still root here, so give that up
      setgid(getgid());
      setuid(getuid());
#endif
      execl("/bin/sh", "-c", pipes);
      perror(pipes);   // if we get here, there was an error
      exit(-1);
    default: ;
  }
  
}
#endif
