// 6.111 Final Project - Fall 1996 - "PIC-Cam"
// Kenneth B. Russell <kbrussel@mit.edu>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/modem.h>
#include <termios.h>
#include <sys/termiox.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include "download.h++"

static const int INIT_BUFFER_SIZE = 4096;
static int caughtSIGALRM;

int
openSerialPort()
{
  int fd;
  int res;
  struct termiox serParams;
  struct termios modeParams;
  mflag statusFlags;

  fprintf(stderr, "Opening serial port read only...\n");
  fd = open("/dev/tty00", O_RDONLY);
  if (fd < 0)
    {
      perror("openSerialPort/open");
      return -1;
    }

  if (!isatty(fd))
    {
      perror("openSerialPort/isatty");
      return -1;
    }

  fprintf(stderr, "Configuring baud rate and terminal mode...\n");
  if (tcgetattr(fd, &modeParams) < 0)
    {
      perror("openSerialPort/tcsetattr");
      return -1;
    }

  if (cfsetispeed(&modeParams, B38400) < 0)
    {
      perror("openSerialPort/cfsetispeed");
      return -1;
    }

  if (cfsetospeed(&modeParams, B38400) < 0)
    {
      perror("openSerialPort/cfsetospeed");
      return -1;
     }

  // Note loss of generality. We know we'll always
  // get an even number of bytes from our device
  modeParams.c_cc[VMIN] = 2;
  modeParams.c_cc[VTIME] = 0;

  if (tcsetattr(fd, TCSANOW, &modeParams) < 0)
    {
      perror("openSerialPort/tcsetattr");
      return -1;
    }

  fprintf(stderr, "Turning on hardware flow control...\n");
  
  if (ioctl(fd, TCGETX, &serParams) < 0)
    {
      perror("openSerialPort/TCGETX");
      return -1;
    }

  serParams.x_hflag |= RTSXOFF | CTSXON;
  
  if (ioctl(fd, TCSETX, &serParams) < 0)
    {
      perror("openSerialPort/TCSETX");
      return -1;
    }

  fprintf(stderr, "Activating DTR...\n");
  statusFlags = MRTS | MDTR;
  if (ioctl(fd, MCSETA, &statusFlags) < 0)
    {
      perror("openSerialPort/MCSETA");
      return -1;
    }

  fprintf(stderr, "Port opened and configured successfully.\n");
  return fd;
}

void
usage()
{
  fprintf(stderr, "usage: download \n");
  fprintf(stderr, "  Downloads data from serial port at 38400bps\n");
  fprintf(stderr, "  until a pause of greater than 5 seconds\n");
  fprintf(stderr, "  occurs in the data stream, at which point the\n");
  fprintf(stderr, "  downloaded data (if any) is written to the\n");
  fprintf(stderr, "  file FILENAME.\n");
}

void
alarmCB(int foo)
{
  caughtSIGALRM = 1;
}

float
timevalDifference(struct timeval *startTime,
		  struct timeval *endTime)
{
  return ((float) (endTime->tv_sec - startTime->tv_sec) +
	  (((float) endTime->tv_usec - startTime->tv_usec) * 1.0e-6));
}

int
readDataFromPort(int fd, 
		 unsigned char **buf,
		 int *len,
		 int interactive,
		 char *filename)
{
  // Start with a 4K data buffer
  int size = INIT_BUFFER_SIZE;
  int numRead, totRead = 0;
  struct timeval startTime, endTime;
  struct timezone tz = {0, 0};
  char statusString[256];
  int i;
  float transferTime;
  int firstTime = 1;
  int dispcnt = 0;
  int imagesWritten = 0;
  
  *buf = (unsigned char *) malloc(size * sizeof(unsigned char));

  // Try to read pairs of bytes. We know that we'll always get
  // an even number of bytes from our device (not generally, though)
  
  signal(SIGALRM, alarmCB);

  gettimeofday(&startTime, &tz);

  while (!caughtSIGALRM)
    {
      if (totRead >= size - 2)
	{
	  // Grow block of memory
	  size *= 2;
	  *buf = (unsigned char *) realloc(*buf, size);
	  if (*buf == NULL)
	    {
	      fprintf(stderr, "\nOut of memory!\n");
	      *len = 0;
	      return -1;
	    }
	}
      
      caughtSIGALRM = 0;

      // Don't time out on the first bytes transferred
      if (firstTime)
	{
	  firstTime = 0;
	}
      else
	{
	  alarm(5);
	}

      if ((numRead = read(fd, &((*buf)[totRead]), 2)) < 0)
	{
	  if (caughtSIGALRM)
	    {
	      // Timed out.
	      *len = totRead;

	      if (*len == 0)
		{
		  // Got no data, so free the buffer we allocated
		  free(*buf);
		  fprintf(stderr, "Didn't successfully transfer any data!\n");
		  return -1;
		}
  
	      gettimeofday(&endTime, &tz);
	      transferTime = timevalDifference(&startTime, &endTime);
	      fprintf(stderr, "\nTransferred %d bytes in %.4f seconds ",
		      totRead, transferTime);
	      fprintf(stderr, "(%d bytes/sec)\n", 
		      (int) ((float) totRead / transferTime));
	      return 0;
	    }

	  // Error during read. Abort.
	  perror("readDataFromPort");
	  free(*buf);
	  *len = 0;
	  return -1;
	}

      // Else, verify we got our two bytes
      if (numRead != 2)
	{
	  fprintf(stderr, "readDataFromPort: got only %d bytes", numRead);
	}

      totRead += numRead;

      if ((totRead % 1000) == 0)
	{
	  sprintf(statusString, "Downloaded %d bytes.", totRead);
	  fprintf(stderr, "%s", statusString);
	  for (i = 0; i < strlen(statusString); i++)
	    {
	      fprintf(stderr, "\b");
	    }
	  fflush(stderr);
	}

      if (interactive)
	{
	  // Write out each image as it's downloaded
	  if ((totRead % 153600) == 0)
	    {
	      char tmpbuf[256];

	      sprintf(tmpbuf, "%s_%03d.pnm", filename, imagesWritten);
	      writeRGBPNM(tmpbuf, &((*buf)[imagesWritten * 153600]), 240);
	      imagesWritten++;
	    }
	}
    }
}

int
writeDataToFile(const char *filename,
		unsigned char *buf, int len)
{
  FILE *fp;
  int res;

  fp = fopen(filename, "w");
  res = fwrite(buf, sizeof(unsigned char), len, fp);
  fclose(fp);
  
  if (res < 0)
    {
      perror("writeDataToFile");
      return -1;
    }

  if (res < len)
    {
      fprintf(stderr, "Uh oh. Wrote %d bytes, expected to write %d.\n",
	      res, len);
      return -1;
    }

  return 0;
}

int
writeRGBPNM(const char *filename,
	    unsigned char *buf, int h)
{
  // Assumes BUF is interleaved bytewise YUV data in the following order:
  // Y0 U0 Y1 V0 Y2 U1 Y3 U1 Y4 U2 Y5 V2...
  // Assumes 320 luma pixels, 160 chroma pixels across.

  int xc, yc;
  FILE *fp;
  float r, g, b;
  int ri, gi, bi;
  unsigned char rc, gc, bc;
  float y, u, v;
  float yd;

  if ((h % 240) != 0) return -1;

  if ((fp = fopen(filename, "w")) == NULL) goto fail;
  
  fprintf(fp, "P6 320 %d 255\n", h);

  for (yc = 0; yc < h; yc++)
    {
      for (xc = 0; xc < 160; xc++)
	{
	  // Write top luma pixel, converting to RGB
	  y = buf[(160*4*yc) + (4*xc)];
	  u = buf[(160*4*yc) + (4*xc) + 1];
	  v = buf[(160*4*yc) + (4*xc) + 3];

	  yd = (255.0 * (y - 16.0) / 219.0);
	  g = (yd -
	       0.114 * ((255.0 * (u - 128.0) / 126.0) + yd) -
	       0.299 * ((255.0 * (v - 128.0) / 160.0) + yd)) / 0.587;
	  b = (255.0 * (u - 128.0) / 126.0) + yd;
	  r = (255.0 * (v - 128.0) / 160.0) + yd;

	  ri = r;
	  gi = g;
	  bi = b;

	  // Clamp
	  if (ri > 255) ri = 255;
	  if (ri < 0) ri = 0;
	  if (gi > 255) gi = 255;
	  if (gi < 0) gi = 0;
	  if (bi > 255) bi = 255;
	  if (bi < 0) bi = 0;

	  rc = ri;
	  gc = gi;
	  bc = bi;

	  // Write
	  if (fwrite(&rc, sizeof(char), 1, fp) != 1) goto fail;
	  if (fwrite(&gc, sizeof(char), 1, fp) != 1) goto fail;
	  if (fwrite(&bc, sizeof(char), 1, fp) != 1) goto fail;

	  // Next pixel uses same u, v components
	  y = buf[(160*4*yc) + (4*xc) + 2];

	  yd = (255.0 * (y - 16.0) / 219.0);
	  g = (yd -
	       0.114 * ((255.0 * (u - 128.0) / 126.0) + yd) -
	       0.299 * ((255.0 * (v - 128.0) / 160.0) + yd)) / 0.587;
	  b = (255.0 * (u - 128.0) / 126.0) + yd;
	  r = (255.0 * (v - 128.0) / 160.0) + yd;

	  ri = r;
	  gi = g;
	  bi = b;

	  // Clamp
	  if (ri > 255) ri = 255;
	  if (ri < 0) ri = 0;
	  if (gi > 255) gi = 255;
	  if (gi < 0) gi = 0;
	  if (bi > 255) bi = 255;
	  if (bi < 0) bi = 0;

	  rc = ri;
	  gc = gi;
	  bc = bi;

	  // Write
	  if (fwrite(&rc, sizeof(char), 1, fp) != 1) goto fail;
	  if (fwrite(&gc, sizeof(char), 1, fp) != 1) goto fail;
	  if (fwrite(&bc, sizeof(char), 1, fp) != 1) goto fail;
	}
    }
  fclose(fp);
  return 0;

 fail:
  fclose(fp);
  return -1;
}

int
main(int argc, char **argv)
{
  int fd;
  char *outputFileName;
  unsigned char *dataBuf;
  int len;
  int res;
  char tmpFileName[256];

  if (argc < 2)
    {
      usage();
      exit(1);
    }

  outputFileName = argv[1];

  fd = openSerialPort();
  if (fd < 0)
    {
      fprintf(stderr, "Serial port open failed. Aborting.\n");
      exit(1);
    }

  if (readDataFromPort(fd, &dataBuf, &len, 1, outputFileName) < 0)
    {
      fprintf(stderr, "Error during read from serial port. Aborting.\n");
      exit(1);
    }

  sprintf(tmpFileName, "%s.raw", outputFileName);
  if (writeDataToFile(tmpFileName, dataBuf, len) < 0)
    {
      fprintf(stderr, "Error during write of data!\n");
      exit(1);
    }

#if 0
  if (len != 768000)
    {
      if (writeDataToFile(outputFileName, dataBuf, len) < 0)
	{
	  fprintf(stderr, "Error during write of data!\n");
	  exit(1);
	}
    }
  else
    {
      if (writeRGBPNM(outputFileName, dataBuf, len) < 0)
	{
	  fprintf(stderr, "Error during write of PNM!\n");
	  exit(1);
	}
    }
#endif
	  
  fprintf(stderr, "Transfer complete.\n");
  return 0;
}