// command-line, batchable, crontab-able version of the cqcam software
//
// by: Patrick Reynolds <patrickr@virginia.edu>

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

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

#include "config.h"

// 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 int auto_adj = AUTO_ADJ_DEFAULT;
static int iport = DEFAULT_PORT, idetect = DEFAULT_DETECT_MODE;
static int ibpp = DEFAULT_BPP;
static int remember = 0;

int main(int argc, char **argv) {
  parse_opts(argc, argv, NULL, 1);     // look for pass 1 or invalid options

#ifndef BSDI   // BSDI doesn't require root
#ifndef LYNX   // neither does Lynx
  if (geteuid()) {
    fprintf(stderr, "%s: this program requires root perissions.\n", argv[0]);
    exit(1);
  }
#endif
#endif

  camera_t camera(iport, idetect); // probe for and initialize the beast
  camera.set_bpp(ibpp);

  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();

  if (auto_adj) {
    int done = 0;
    int upper_bound = 253, lower_bound = 5, loops = 0;
    do {
      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);
        delete[] scan;
      }
    } while (!done && upper_bound > lower_bound && ++loops <= 10);
    scan = camera.get_frame();
    if (camera.get_bpp() == 32)
      scan = raw32_to_24(scan, width, height);
  }
  else {
    scan = camera.get_frame();
    if (camera.get_bpp() == 32)
      scan = raw32_to_24(scan, width, height);
  }

  int rtemp, gtemp, btemp;
  get_rgb_adj(scan, width * height, rtemp, gtemp, btemp);
  camera.set_red(rtemp);
  camera.set_green(gtemp);
  camera.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 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());

  write_ppm(stdout, scan, width, height);

  delete[] scan;

  if (remember) {
    int saveuid = getuid();
    setreuid(0, saveuid);
    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);
    setreuid(saveuid, 0);
  }
  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, "  -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,
    "  -P val     Set port to attempt (val must be in hex format)\n");
  fprintf(stderr, "  -r         Remember (store) the brightness in .cqcrc\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, "  -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 (!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 '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;
      // 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 '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 '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;
      case 'P':  i++;  break;  // we already dealt with this in pass 1
      case 's':  ++i;
        if (opt) camera->set_decimation(atoi(opt));
        else kaput(name, "option requires an argument: -s");
        break;
      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;
  }
}
