/* 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, batchable, crontab-able version of the cqcam software

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.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);

// take <movie_frames> pictures, one per <movie_delay> seconds, storing
// them as <movie_name>-%d.ppm or <movie_name>-%d.jpg.
void movie(camera_t *cam);

// take one picture and splat it to stdout
void one_frame(camera_t *cam);

static float bulb = -1;
static int auto_adj = AUTO_ADJ_DEFAULT;
static int red = -1, green = -1, blue = -1;
static int iport = DEFAULT_PORT, idetect = DEFAULT_DETECT_MODE;
static int ibpp = DEFAULT_BPP;
static int idecimation = DEFAULT_DECIMATION;
static int remember = 0;
static float movie_delay = -1;
static int movie_frames = -1;
static char *movie_name = NULL;
#ifdef JPEG
static int jpeg = 0, jpeg_quality = 50;
#endif

int main(int argc, char **argv) {
  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

  if (movie_name != NULL)
    movie(&camera);
  else
    one_frame(&camera);
    
  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");
#ifdef JPEG                              
  fprintf(stderr, "  -j                     Write output in JPEG format\n");
#endif                                   
  fprintf(stderr, "  -l val                 Set left column\n");
  fprintf(stderr, "  -m delay,name[,count]  Movie mode: capture count frames,"
                                            " one every val\n");
  fprintf(stderr, "                         seconds to name-X.ppm\n"); 
  fprintf(stderr, "  -P val                 Set port to attempt (val must be in"
                                            " hex format)\n");
#ifdef JPEG                              
  fprintf(stderr, "  -q val                 Set JPEG quality, for use with -j"
                                            " (default=50)\n");
#endif                                   
  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) {
  char *p, *q;
  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 'm':  ++i;
        if (!opt) kaput(name, "option requires an argument: -m");
        movie_delay = atof(opt);
        p = strchr(opt, ',');
        if (!p) kaput(name, "invalid parameter: -m");
        p++;
        if ((q = strchr(p, ',')) != NULL) {
          *q++ = '\0';
          movie_frames = atoi(q);
        }
        movie_name = p;
        break;
      case 's':  ++i;
        if (opt) idecimation = atoi(opt);
        else kaput(name, "option requires an argument: -s");
        break;
      case 'V': kaput(name, CQC_VERSION);
        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 'R':  ++i;
        if (opt) red = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -R");
        break; 
      case 'G':  ++i;
        if (opt) green = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -G");
        break; 
      case 'E':  ++i;
        if (opt) blue = CLAMP(atoi(opt), 1, 255);
        else
          kaput(name, "option requires an argument: -E");
        break; 
#ifdef JPEG
      case 'j':  jpeg = 1;
        break;
      case 'q':  ++i;
        if (opt) jpeg_quality = atoi(opt);
        else
          kaput(name, "option requires an argument: -q");
        break; 
#endif

      // 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 'L':
      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 'd':
      case 'R':
      case 'E':
      case 'G':  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 'L':  ++i;
        if (!opt) kaput(name, "option requires an argument: -L");
        bulb = atof(opt);
        break;
      case 'm':  i++;  break;  // we already dealt with this in pass 1
      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;
  }
}

static float timedifference(struct timeval a, struct timeval b) {
  return (a.tv_usec - b.tv_usec)/1000000.0 + (a.tv_sec - b.tv_sec);
}

void movie(camera_t *cam) {
  int width = cam->get_pix_width();
  int height = cam->get_pix_height();
  int limit = height * width;
  char *filename = (char*)malloc(strlen(movie_name) + 1+10+4+1);
  int frameno = 1;

  fprintf(stderr, "Capturing %d frames, one every %.3f seconds and calling",
    movie_frames, movie_delay);
#ifdef JPEG
  fprintf(stderr, " them %s-%%d.%s\n", movie_name, jpeg?"jpg":"ppm");
#else
  fprintf(stderr, " them %s-%%d.ppm\n", movie_name);
#endif

  struct timeval last = { 0, 0 }, now;
  for ( ; movie_frames==-1||movie_frames-->0; frameno++) {
    gettimeofday(&now, 0);
    float tdiff = timedifference(now, last);
    if (tdiff < movie_delay)
      usleep((unsigned long)(1000000*(movie_delay-tdiff)));
    unsigned char *scan;
    gettimeofday(&last, 0);
    scan = cam->get_frame();
    if (cam->get_bpp() == 32)
      scan = raw32_to_24(scan, width, height);
    if (auto_adj) {
      int bright, bright_adj;

      get_brightness_adj(scan, limit, bright_adj);
      bright = cam->get_brightness() + bright_adj;
      if (bright > 253) bright = 253;
      if (bright < 1) bright = 1;
      cam->set_brightness(bright);
    }

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

		int r_adj, g_adj, b_adj;

    if (red == -1 && green == -1 && blue == -1) {
      get_rgb_adj(scan, limit, r_adj, g_adj, b_adj);
    }
    else {
			r_adj = (red == -1) ? 128 : red;
			g_adj = (green == -1) ? 128 : green;
			b_adj = (blue == -1) ? 128 : blue;
    }
    do_rgb_adj(scan, limit, r_adj, g_adj, b_adj);
#ifdef JPEG
    if (jpeg) {
      sprintf(filename, "%s-%d.jpg", movie_name, frameno);
      FILE *out = fopen(filename, "w");
      write_jpeg(out, scan, width, height, jpeg_quality);
      fclose(out);
    }
    else {
#endif
      sprintf(filename, "%s-%d.ppm", movie_name, frameno);
      FILE *out = fopen(filename, "w");
      write_ppm(out, scan, width, height);
      fclose(out);
#ifdef JPEG
    }
#endif
    delete[] scan;
  }
}

void one_frame(camera_t *cam) {
  unsigned char *scan;
  int width = cam->get_pix_width();
  int height = cam->get_pix_height();

  if (auto_adj) {
    int done = 0;
    int upper_bound = 253, lower_bound = 5, loops = 0;
    do {
      scan = cam->get_frame();
      if (cam->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 = 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);
        delete[] scan;
      }
    } while (!done && upper_bound > lower_bound && ++loops <= 10);
    scan = cam->get_frame();
    if (cam->get_bpp() == 32)
      scan = raw32_to_24(scan, width, height);
  }
  else {
    if (bulb != -1) {
      scan = cam->get_frame();
      delete[] scan;
      cam->set_brightness(255);
      fprintf(stderr, "Bulb mode: sleeping %d microseconds\n",
        (int)(1000000*bulb));
      usleep((int)(1000000*bulb));
    }
    scan = cam->get_frame();
    if (cam->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;
  }
  cam->set_red(red);
  cam->set_green(green);
  cam->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 (cam->get_bpp() == 24)
    scan = despeckle(scan, width, height);
#endif

  do_rgb_adj(scan, width * height,
    cam->get_red(), cam->get_green(), cam->get_blue());

#ifdef JPEG
  if (jpeg)
    write_jpeg(stdout, scan, width, height, jpeg_quality);
  else
#endif
    write_ppm(stdout, scan, width, height);

  delete[] scan;
}
