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

// Main file for the X11 version of cqcam.  Most of the important X11 code
// is in xscan.C.

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

#include "xscan.h"
#include <camera.h>
#include <config.h>
#include <rcfile.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);

static struct xqc_flags_t xflags;
static int iport = DEFAULT_PORT;
static int idetect = DEFAULT_DETECT_MODE;
static int ibpp = DEFAULT_BPP;
static int idecimation = DEFAULT_DECIMATION;
static char *dpy_name = NULL;

int main(int argc, char **argv) {
  xflags.prv_cmap = PRIVATE_CMAP_DEFAULT;
  xflags.adj = AUTO_ADJ_DEFAULT;
  xflags.rgbadj = 1;
  xflags.shm = DEFAULT_SHM;

  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
  x_scan(&camera, argc, argv, xflags, dpy_name);
    // transfer control to xscan.C
#ifdef DEBUG
  fprintf(stderr, "Clean shutdown\n");
#endif
  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, "  -display d  Use an X display other than $DISPLAY\n");
  fprintf(stderr, "  -E val      Set software blue level\n");
  fprintf(stderr, "  -G val      Set software green level\n");
  fprintf(stderr, "  -H val      Set hue (blue level)\n");
  fprintf(stderr, "  -j         Write output in JPEG format\n");
  fprintf(stderr, "  -l val      Set left column\n");
  fprintf(stderr,
    "  -m[+|-]     Use/suppress the MIT shared memory (SHM) extension\n");
  fprintf(stderr,
    "  -p[+|-]     Use/suppress a private colormap on 8-bit displays\n");
  fprintf(stderr,
    "  -P val      Set port to attempt (val must be in hex format)\n");
  fprintf(stderr, "  -q val     Set JPEG quality, for use with -j (default=50)\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;
      case 'P':  if (opt) {
          ++i;
          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 '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 'd':  if (sw[2] == '\0') {
          ++i;
          if (opt) idetect = atoi(opt);
          else kaput(name, "option requires an argument: -d");
        }
        else {
          if (!strcmp(sw, "-display")) {
            ++i;
            if (opt) dpy_name = opt;
            else kaput(name, "option requires an argument: -display");
          }
          else {
            fprintf(stderr, "%s: unknown option %s\n", name, sw);
            exit(1);
          }
        } 
        break;
      case 'a':  switch (sw[2]) {
          case '+': xflags.adj = 1;    break;
          case '-': xflags.adj = 0;    break;
          default:  xflags.adj = !AUTO_ADJ_DEFAULT;
        }
        break;
      case 'p':  switch (sw[2]) {
          case '+': xflags.prv_cmap = 1; break;
          case '-': xflags.prv_cmap = 0; break;
          default:  xflags.prv_cmap = !PRIVATE_CMAP_DEFAULT;
        }
        break;
      case 'm':  switch (sw[2]) {
          case '+': xflags.shm = 1; break;
          case '-': xflags.shm = 0; break;
          default:  xflags.shm = !DEFAULT_SHM;
        }
        break;
      case 's':  if (opt) {
          ++i;
          idecimation = atoi(opt);
        }
        else kaput(name, "option requires an argument: -s");   
        break;
      case 'V':  kaput(name, CQC_VERSION);
        break;
      case 'b':  case 'B':  case 'c':  case 'E':  case 'G':
      case 'H':  case 'l':  case 'R':  case 'S':  case 't':
      case 'w':  case 'x':  case 'y':  i++;
      // these (above) all take one input, so i++ to ignore it this pass
      case 'u':  break;
      // these all get processed in pass 2
      default:   fprintf(stderr, "%s: unknown option: -%c\n", name, sw[1]);
        exit(1);
    }
  else
    switch (sw[1]) {
      case 'b':  if (opt) {
          ++i; 
          camera->set_brightness(atoi(opt));
        }
        else kaput(name, "option requires an argument: -b");
        break;
      case 'B':  if (opt) {
          ++i;
          camera->set_black_level(atoi(opt));
        }
        else kaput(name, "option requires an argument: -B");
        break;
      case 'c':  if (opt) {
          ++i;
          camera->set_contrast(atoi(opt));
        }
        else kaput(name, "option requires an argument: -c");
        break;
      case 's':
      case 'd':  i++;  break;  // we already dealt with this in pass 1
      case 'E':  ++i;
        if (opt) camera->set_blue(CLAMP(atoi(opt), 1, 255));
        else
          kaput(name, "option requires an argument: -E");
        xflags.rgbadj = 0;
        break;
      case 'G':  ++i;
        if (opt) camera->set_green(CLAMP(atoi(opt), 1, 255));
        else
          kaput(name, "option requires an argument: -G");
        xflags.rgbadj = 0;
        break;
      case 'H':  if (opt) {
          ++i;
          camera->set_hue(atoi(opt));
        }
        else kaput(name, "option requires an argument: -H");
        break;
      case 'l':  if (opt) {
          ++i;
          camera->set_left(atoi(opt));
        }
        else kaput(name, "option requires an argument: -l");
        break;
      case 'P':
      case 'R':  ++i;
        if (opt) camera->set_red(CLAMP(atoi(opt), 1, 255));
        else
          kaput(name, "option requires an argument: -R");
        xflags.rgbadj = 0;
        break;
      case 'S':  if (opt) {
          ++i;
          camera->set_saturation(atoi(opt));
        }
        else kaput(name, "option requires an argument: -S");
        break;
      case 't':  if (opt) {
          ++i;
          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':  if (opt) {
          ++i;
          camera->set_white_level(atoi(opt));
        }
        else kaput(name, "option requires an argument: -w");
        break;
      case 'x':  if (opt) {
          ++i;
          camera->set_width(atoi(opt));
        }
        else kaput(name, "option requires an argument: -x");
        break;
      case 'y':  if (opt) {
          ++i;
          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;
  }
}
