#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>

#define STX 0x02
#define ETX 0x03
#define	ENQ 0x05
#define	ACK 0x06
#define NAK 0x15
#define ETB 0x17

#define NORMAL 0
#define FINE 1

void flushtty(int fd);
void showttystate(int fd);
u_char rbyte(int fd);
int running=1;
int exiting=0;
void sigint();
int debug=0;
char *where="start";

main(argc,argv)
int argc;
char **argv;
{
  int qvfd, s, num, attr, i;
  char buf[8], byte=0;
  long tym, otym, tym1, tym2, tym3;

  qvfd=devinit("/dev/term/a");
if (debug) fprintf(stderr,"trying initial ok...\n");
  wbyte(qvfd,ENQ);
  wait_for_input(qvfd,10);
  if (read(qvfd,&byte,1) != 1) {
    fprintf(stderr, "Can't communicate! Trying high baud rate...\n");
    tty_change_speed(qvfd,B115200);
  }
  qvok(qvfd);
  fprintf(stderr, "(communicating!)\n");
  signal(SIGINT,sigint);
if (debug) showttystate(qvfd);
  sleep(1);
  debug_status("changing speed");
  qv_change_speed(qvfd,B115200);
  num=qvhowmany(qvfd);
if (debug) fprintf(stderr,"%d pictures\n",num);
/*
  debug_status("setting mode");
  qvmode(qvfd,NORMAL);
*/

  tym=0;
  while(running) {
    otym=tym;
    tym=time(NULL);
    if (otym) {
      printf("%d seconds for last snapshot", tym-otym);
      printf(" (%d, %d, %d, %d)\n", tym1-otym, tym2-tym1, tym3-tym2, tym-tym3);
    }
    debug_status("setting sector");
    qvsector(qvfd, 0x340);
    tym1=time(NULL);
    /* take a picture */
    usleep(700000);  /* seems to lessen camera lock-ups */
    debug_status("taking picture (ok)");
    qvok(qvfd);
    debug_status("taking picture (command)");
    wbyte(qvfd,'D');
    wbyte(qvfd,'R');
    s = rbyte(qvfd);
    debug_status("taking picture (command, ACK)");
    /* sleep(1); */
    wbyte(qvfd,ACK);
    s = rbyte(qvfd);
/*
    debug_status("taking picture (reset sector)");
    qvsector(qvfd, 0x600);
*/
    /* sleep(1); */

    debug_status("getting picture num");
    /* get the picture number */
    num=qvhowmany(qvfd);

    /* get picture attributes */
    attr=qvattr(qvfd,num);
    if (debug) { fprintf(stderr, "attr=0x%x\n", attr); }

    tym2=time(NULL);
    debug_status("getting picture data");
    /* grab the picture */
    get_picture(qvfd,num,attr);
    /* sleep(1); */

    debug_status("deleting picture data");
    /* delete the picture */
    qvdelete(qvfd,num);

    tym3=time(NULL);
    debug_status("resetting camera");
    /* reset the camera? */
    qvreset(qvfd);
  }
  clean_exit(qvfd);
}

int qvok (fd)
int fd;
{
  int i;

  for (i=0; i<100; ++i) {
if (debug) fprintf(stderr,"trying ok...\n");
    wbyte(fd,ENQ);
    if (rbyte(fd) == ACK) {
if (debug) fprintf(stderr,"OK\n");
      return(1);
    }
  }
  fprintf(stderr,"error at %s\n", where);
  perror("qv is not OK!");
  exit(-1);
}

int qvhowmany (fd)
int fd;
{
  int x;

  qvok(fd);
  write(fd,"MP",2);
  x = rbyte(fd);
  if (x != 0x62) {
    fprintf(stderr,"error at %s\n", where);
    perror("howmany invalid response");
    exit(-1);
  }
  wbyte(fd,ACK);
  x = rbyte(fd);
  return((int) x);
}

int qvattr (fd,picnum)
int fd, picnum;
{
  u_int c;

  qvok(fd);
  write(fd,"DY",2);
  wbyte(fd, 0x02);
  wbyte(fd, picnum);
  rbyte(fd);
  wbyte(fd,ACK);
  c=rbyte(fd);
  return(c);
}

int qvsector (fd, n)
int fd;
int n;
{
  u_char c;
  qvok(fd);
  write(fd,"PP",2);
  wbyte(fd,(u_char)(n >> 8) & 0xff);
  wbyte(fd,(u_char)n & 0xff);
  c=rbyte(fd);
  wbyte(fd,ACK);
}

int devinit(devfile)
char *devfile;
{
  int fd, mode, val;
  struct termios tio;

  fd=open(devfile,O_RDWR | O_NDELAY);
  if (fd < 0) {
    perror("open");
    exit(-1);
  }
  val=1;
/*
  if (ioctl(fd, TIOCSSOFTCAR, &val) < 0) {
    perror("soft carrier detect");
    exit(-1);
  }
*/
/*
  if (ioctl(fd, TIOCEXCL, 0) < 0) {
    perror("exclusive");
    exit(-1);
  }
  if (ioctl(fd, TIOCHPCL, 0) < 0) {
    perror("hold");
    exit(-1);
  }
*/

  tty_change_speed(fd, B9600);
  return(fd);
}

int qv_change_speed(fd, baud)
int fd, baud;
{
  int mode, val;
  struct termios tio;
  int baud_code;

  switch(baud) {
    case B9600: baud_code=46; break;
    case B19200: baud_code=22; break;
    case B38400: baud_code=11; break;
    case B57600: baud_code=7; break;
    case B115200: baud_code=3; break;
    case B153600: baud_code=2; break;
    case B230400: baud_code=1; break;
    case B460800: baud_code=0; break;
    default:
      fprintf(stderr,"No such baud available!\n");
      exit(1);
  }

  qvok(fd);
  wbyte(fd,'C');
  wbyte(fd,'B');
  wbyte(fd,baud_code);
  rbyte(fd);
  wbyte(fd,ACK);
  sleep(1);

  tty_change_speed(fd, baud);

  return(0);
}

int tty_change_speed(fd, baud)
int fd, baud;
{
  int mode, val;
  struct termios tio;

  if (tcgetattr(fd, &tio) < 0) {
    perror("tcgetattr");
    exit(-1);
  }
  tio.c_iflag=0;
  tio.c_oflag=0;
  tio.c_cflag=CS8 | CREAD | CLOCAL ;
  tio.c_lflag=0;
  tio.c_cc[VMIN]=1;
  tio.c_cc[VTIME]=5;
  if (cfsetispeed(&tio, baud)) {
    perror("set input speed");
    exit(-1);
  }
  if (cfsetospeed(&tio, baud)) {
    perror("set output speed");
    exit(-1);
  }
  if (tcsetattr(fd, TCSANOW, &tio) < 0) {
    perror("tcsetattr");
    exit(-1);
  }
  mode = TIOCM_RTS;
  if(ioctl(fd, TIOCMBIC, &mode) < 0){ /* RTS OFF */
    fprintf(stderr, "Can't set RTS OFF.\n");
    close(fd);
    return(-1);
  }
  mode = TIOCM_CTS|TIOCM_DTR;
  if(ioctl(fd, TIOCMBIS, &mode) < 0){ /* CTS DTR ON */
    fprintf(stderr, "Can't set CTS DTR ON.\n");
    close(fd);
    return(-1);
  }
/* showttystate(fd); */

  flushtty(fd);
  return(0);
}


int clean_exit(fd)
int fd;
{
  int mode, val;
  struct termios tio;
  int baud_code;

  /* don't re-enter */
  if (exiting) { exit(1); }
  exiting=1;
  flushtty(fd);
  if (tcgetattr(fd, &tio) < 0) {
    perror("tcgetattr");
    exit(-1);
  }
  tio.c_iflag=0;
  tio.c_oflag=0;
  tio.c_cflag=CS8 | CREAD | CLOCAL ;
  tio.c_lflag=0;
  tio.c_cc[VMIN]=1;
  tio.c_cc[VTIME]=5;
  if (cfsetispeed(&tio, B9600)) {
    perror("set input speed");
    exit(-1);
  }
  if (cfsetospeed(&tio, B9600)) {
    perror("set output speed");
    exit(-1);
  }

  qvok(fd);
  wbyte(fd,'C');
  wbyte(fd,'B');
  wbyte(fd, 46);
  rbyte(fd);
  wbyte(fd,ACK);
  sleep(1);

  if (tcsetattr(fd, TCSANOW, &tio) < 0) {
    perror("tcsetattr");
    exit(-1);
  }
  fprintf(stderr, "Exited cleanly!\n");
  exit(0);
}

int qvdelete (fd,picnum)
int fd, picnum;
{
  qvok(fd);
  wbyte(fd,'D');
  wbyte(fd,'F');
  wbyte(fd,picnum);
  wbyte(fd,255);
  rbyte(fd);
  wbyte(fd,ACK);
}

int show_picture (fd,picnum)
int fd, picnum;
{
  qvok(fd);
  wbyte(fd,'D');
  wbyte(fd,'A');
  wbyte(fd,picnum);
  rbyte(fd);
  wbyte(fd,ACK);
}

int get_picture (fd,picnum,attr)
int fd, picnum, attr;
{
  u_char c, buf[128*1024], *ptr;
  int i, sum, blksz, nr;
  FILE *fp;

  show_picture(fd,picnum);
  /* the DL may not be necessary, but it doesn't take much time */
  qvok(fd);
  wbyte(fd,'D');
  wbyte(fd,'L');
  rbyte(fd); /* should be 6f */
  wbyte(fd,ACK);
  qvok(fd);
  wbyte(fd,'M');
  if (attr & 0x02) {  /* fine mode */
    wbyte(fd,'g');
  } else {            /* normal mode */
    wbyte(fd,'G');
  }
  rbyte(fd); /* should be 6b */
  wbyte(fd,ACK);

  wbyte(fd,0x12);

  ptr=buf;
  while(1) {
if (debug) fprintf(stderr,"======starting block read======\n");
    sum=0;
    c=rbyte(fd);
    if (c!=STX) { fprintf(stderr,"didn't get STX (got 0x%x)\n", c); exit(1); }
    c=rbyte(fd); sum+=c;
    blksz=c*256;
    c=rbyte(fd); sum+=c;
    blksz += c;
    if (blksz == 0) break;
    nr=0;
    /* while ((nr+=read(fd,ptr+nr,blksz-nr)) < blksz); */
    while (nr < blksz) {
      wait_for_input(fd,5);
      nr += read(fd,ptr+nr,blksz-nr);
      if (errno == EINTR && running == 0) { clean_exit(fd); }
    }
if (debug) fprintf(stderr, "    read completed, nr=%d\n",nr);
    for (i=0; i<blksz; ++i) sum += ptr[i];
    c=rbyte(fd); sum+=c;
    if (c!=ETB) { fprintf(stderr,"didn't get ETB (got 0x%x)\n", c); exit(1); }
    c=rbyte(fd);  /* checksum */
    if ((0xff&(~c)) != (sum &0xff)) {
if (debug) fprintf(stderr,"RETRYING!!! (sum=%d, checksum=%d)\n", sum, c);
      wbyte(fd,NAK);
    } else {
      wbyte(fd,ACK);
if (debug) fprintf(stderr,"======YAY successful block read======\n");
      /* save JPEG data */
      ptr += blksz;
    }
  }
  c=rbyte(fd);
  if (c!=ETX) { fprintf(stderr,"didn't get ETX (got 0x%x)\n", c); exit(1); }
  c=rbyte(fd);
  if (c!=0xFC) { fprintf(stderr,"didn't get 0xFC (got 0x%x)\n", c); exit(1); }
  wbyte(fd,ACK);

  if ((fp=fopen("/tmp/test.jpg","w")) == NULL) {
    perror("fopen");
    clean_exit(fd);
  }
  if (attr & 0x02) {  /* fine mode */
    write_jpeg_fine(buf,fp);
  } else {            /* normal mode */
    write_jpeg(buf,fp);
  }
  fclose(fp);
  system("/home/fine/bin/update-webcam /tmp/test.jpg &");
}

int qvreset (fd)
int fd;
{
  qvok(fd);
  /* QE is much faster than QR, doesn't require a sleep, doesn't reset the
     speed (more time savings because we don't have to set it back) and seems
     to work fine */
  wbyte(fd,'Q');
  wbyte(fd,'E');
  rbyte(fd);
  wbyte(fd,ACK);
  /* sleep(2); */
  qvok(fd);
}

int wait_for_input(fd,tymout)
int fd, tymout;
{
  fd_set readfds;
  int nfds;
  struct timeval tv;

  tv.tv_sec=tymout;
  tv.tv_usec=0;
  FD_ZERO(&readfds);
  FD_SET(fd,&readfds);
  nfds=select(fd+1, &readfds, NULL, NULL, &tv);
  if (nfds == 0) {
    if (errno == EINTR && running == 0) { clean_exit(fd); } 
    return(-1);
  }
  return(0);
}

u_char rbyte (fd)
int fd;
{
  u_char byte;

  wait_for_input(fd,30);
  if (read(fd,&byte,1) != 1) {
    if (errno == EINTR && running == 0) { clean_exit(fd); } 
    fprintf(stderr,"error at %s\n", where);
    perror("read");
    exit(1);
  }
if (debug) fprintf(stderr,"rbyte: %d\n",byte);
  return(byte);
}

int wbyte (fd,byte)
int fd;
u_char byte;
{
  if (write(fd,&byte,1) != 1) {
    perror("write");
    exit(1);
  }
}

void flushtty(fd)
     int fd;
{
  u_char c;
  fd_set readfds;
  int nfds;
  struct timeval tv;
  int i,j;
  unsigned char u;

  FD_ZERO(&readfds);
  FD_SET(fd, &readfds);
  tv.tv_sec = 0;
  tv.tv_usec = 0;

  while (1) {
    nfds = select(fd +1 , &readfds, NULL, NULL, &tv);
    if(nfds == 0){
      return;
    } else {
      if(FD_ISSET(fd, &readfds)){
        if(read(fd, &c, 1) < 0){
          fprintf(stderr,"tty read fail.\n");
          return;
        }
if (debug) fprintf(stderr,"flushtty: read %d\n", c);
      }
    }
  }
}

void showttystate (fd)
int fd;
{
  int mode, val;
  struct termios tio;

  fprintf(stderr,"State of fd %d\n",fd);
  if (ioctl(fd, TIOCGSOFTCAR, &val) < 0) {
    perror("soft carrier detect");
    exit(-1);
  }
  fprintf(stderr,"SoftCar=%s ",val?"T":"F");
  if(ioctl(fd, TIOCMGET, &mode) < 0) {
    perror("modem line bits");
    exit(-1);
  }
  fprintf(stderr,(mode & TIOCM_RTS) ? "RTS is on  " : "RTS is off ");
  fprintf(stderr,(mode & TIOCM_CTS) ? "CTS is on  " : "CTS is off ");
  fprintf(stderr,(mode & TIOCM_DTR) ? "DTR is on  " : "DTR is off ");
  fprintf(stderr,(mode & TIOCM_CAR) ? "CAR is on  " : "CAR is off ");
  if (tcgetattr(fd, &tio) < 0) {
    perror("tcgetattr");
    exit(-1);
  }
  fprintf(stderr,"\n    ");
  fprintf(stderr,"ospd=%d ",cfgetospeed(&tio));
  fprintf(stderr,"ispd=%d ",cfgetispeed(&tio));
  fprintf(stderr,"iflag=%d ",tio.c_iflag);
  fprintf(stderr,"oflag=%d ",tio.c_oflag);
  fprintf(stderr,"cflag=%d ",tio.c_cflag);
  fprintf(stderr,"\n");
}

void sigint(sig)
int sig;
{
  running=0;
}

int debug_status(msg)
char *msg;
{
  where=msg;
  if (debug) { fprintf(stderr, "************ %s\n", msg); }
}

/* The following is verbatim code from QVplay */

u_short
get_u_short(buf)
     u_char     *buf;
{
  return ((u_short)buf[0] << 8) | buf[1];
}

u_int
get_u_int(buf)
u_char  *buf;
{
  u_int   t;

  t = (((u_int)buf[0] << 8) | buf[1]) << 16;;
  t |= ((u_int)buf[2] << 8) | buf[3];;
  return t;
}

#include "/home/fine/qv/QVplay095/src/cam2jpgtab.h"
#include "/home/fine/qv/QVplay095/src/jpegtab_f.h"

int
write_file(buf, len, outfp)
     u_char     *buf;
     int        len;
     FILE       *outfp;
{
  int i, l;

  i = 0;
  while( len > i) {
    l = ( (len - i) < BUFSIZ) ? (len -i) : BUFSIZ;
    if(fwrite(&buf[i], sizeof(u_char), l, outfp) != l){
      perror("write_file");
      return(-1);
    };
    i = i + l;
  }
  return(i);
}


int
write_jpeg(buf, outfp)
     u_char	*buf;
     FILE	*outfp;
{
  int i = 0;
  int areaNum;
  int ysize;
  int usize;
  int vsize;
  
  areaNum =  get_u_short(buf);	/* areaNum == 0x03 */
  ysize = get_u_short(buf + 2);
  usize = get_u_short(buf + 4);
  vsize = get_u_short(buf + 6);
  i = i + 8;
  
  if(write_file(soi, sizeof(soi), outfp) == -1) return(-1);
  if(write_file(app0, sizeof(app0), outfp) == -1) return(-1);
  if(write_file(dqt0, sizeof(dqt0), outfp) == -1) return(-1);
  if(write_file(&buf[i], 64, outfp) == -1) return(-1);
  i = i + 64;

  if(write_file(dqt1, sizeof(dqt1), outfp) == -1) return(-1);
  if(write_file(&buf[i], 64, outfp) == -1) return(-1);
  i = i + 64;

  if(write_file(sof, sizeof(sof), outfp) == -1) return(-1);
  if(write_file(dht, sizeof(dht), outfp) == -1) return(-1);

  if(write_file(sos_y, sizeof(sos_y), outfp) == -1) return(-1);
  if(write_file(&buf[i], ysize, outfp) == -1) return(-1);
  i = i + ysize;
  
  if(write_file(sos_u, sizeof(sos_u), outfp) == -1) return(-1);
  if(write_file(&buf[i], usize, outfp) == -1) return(-1);
  i = i + usize;

  if(write_file(sos_v, sizeof(sos_v), outfp) == -1) return(-1);
  if(write_file(&buf[i], vsize, outfp) == -1) return(-1);
  i = i + vsize;

  if(write_file(eoi, sizeof(eoi), outfp) == -1) return(-1);

  return(i);
  
}

int
write_jpeg_fine(buf, outfp)
     u_char	*buf;
     FILE	*outfp;
{
  int i = 0;
  int size;
  u_char c = 0x01;
  
  size = get_u_int(buf + 4);
  i = i + 8;
  if(write_file(soi, sizeof(soi), outfp) == -1) return(-1);
  if(write_file(app_f, sizeof(app_f), outfp) == -1) return(-1);
  if(write_file(dqt_f, sizeof(dqt_f), outfp) == -1) return(-1);

  if(write_file(&buf[i], 64, outfp) == -1) return(-1);
  i = i + 64;
  if(write_file(&c, 1, outfp) == -1) return(-1);
  if(write_file(&buf[i], 64, outfp) == -1) return(-1);
  i = i + 64;
  if(write_file(sof_f, sizeof(sof_f), outfp) == -1) return(-1);

  if(write_file(dht_f, sizeof(dht_f), outfp) == -1) return(-1);
  
  if(write_file(sos_f, sizeof(sos_f), outfp) == -1) return(-1);
  if(write_file(&buf[i], size, outfp) == -1) return(-1);

  if(write_file(eoi, sizeof(eoi), outfp) == -1) return(-1);

  return(i);
  
}