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

#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#if defined(__linux__)
#include <scc.h>
#elif defined(__FreeBSD__)
#include <machine/scc.h>
#endif

#include "display.h"

short   rdata[SCC_NTSCWIDTH * SCC_NTSCHEIGHT];
unsigned char rgbdata[SCC_NTSCWIDTH * SCC_NTSCHEIGHT * 4];
unsigned char yuvdata[SCC_NTSCWIDTH * SCC_NTSCHEIGHT * 2];
unsigned char *pdata;

int     hwidth  = SCC_NTSCWIDTH / 2;
int     hheight = SCC_NTSCHEIGHT;
int     format = 0;

struct scc_geomet scc;
struct scc_tv scc_tv;

int     verbose;
int     spd_mode = SCC_FAST;

#ifdef SH_MEM
int     shmem = 1;
#else
int     shmem = 0;
#endif

int     dithertype = -1;
int     loop = 0;

double   r0, r1, r2, r3, r4, r5;
double    interval = 30;
int     ff;
int	vsize;

int nframes = -1;
int sccat   = 0;
int tvchannel, tv;

double
gettime()
{
  struct timeval a;

  gettimeofday(&a, NULL);
  return((double)(a.tv_sec + a.tv_usec * 1E-6));
}

void
xdisp(double a,double c)
{
  double b;
  b = gettime();
  r1 += b - a;
  if (dithertype == ORDERED_DITHER)
    OrderedDitherImage(yuvdata, yuvdata + vsize,
		       yuvdata + vsize + vsize / 4, pdata, hheight, hwidth);
  a = gettime();
  r2 += a - b;
  ExecuteDisplay();
  b = gettime();
  r3 += b - a;

  {
    double t = interval - (b-c)*1e6;
    if(t >0)
      usleep(t);
  }

  a = gettime();
  r4 += a - b;
  r5 += a - c;
  if (verbose) {
    ff++;
    if (ff == 100) {
      printf("read           %8.3f\n", r0 / ff);
      printf("raw -> xxx     %8.3f\n", r1 / ff);
      printf("xxx -> Ximage  %8.3f\n", r2 / ff);
      printf("Draw           %8.3f\n", r3 / ff);
      printf("Idle           %8.3f\n", r4 / ff);
      printf("Total(FPS)     %8.3f (%8.3f) \n\n", r5/ff,ff/r5);
      ff = 0;
      r0 = 0;
      r1 = 0;
      r2 = 0;
      r3 = 0;
      r4 = 0;
      r5 = 0;
    }
  }
}

rgb565_depth16(int video)
{
  double c, b;
  int	size;

  size = vsize * sizeof(short);

  while((nframes < 0) || ++loop < nframes){

    c = gettime();
    lseek(video, 0, SEEK_SET);
    read(video, pdata, size);
    b = gettime();
    r0 += b - c;
    xdisp(b, c);
  }
}

rgb565_fullcolor(int video)
{
  double c, b;
  int i, j, n, size;
  size = vsize * sizeof(short);

  while((nframes < 0) || ++loop < nframes){

    c = gettime();
    n = 0;
    lseek(video, 0, SEEK_SET);
    read(video, rdata, size);
    b = gettime();
    r0 += b - c;
    for (i = 0; i < hheight; i++){
      for (j = 0; j < hwidth; j++) {

	pdata[n++] = (unsigned char) ((rdata[i * hwidth + j] & 0x001f) << 3);
	pdata[n++] = (unsigned char) ((rdata[i * hwidth + j] & 0x07e0) >> 3);
	pdata[n++] = (unsigned char) ((rdata[i * hwidth + j] & 0xf800) >> 8);
	n++;
      }
    }
    xdisp(b, c);
  }
}

yuv422_ordered(int video)
{
  double c, b;
  int size;
  unsigned char *y, *u, *v;
  int     i, j, yy, uv;

  size = vsize * sizeof(short);

  while((nframes < 0) || ++loop < nframes){

    c = gettime();
    lseek(video, 0, SEEK_SET);
    read(video, rdata, size);
    b = gettime();
    r0 += b - c;
    yy = 0;
    uv = 0;
    y = yuvdata;
    u = y + vsize;
    v = u + vsize / 4;
    for (i = 0; i < hheight; i++){
      for (j = 0; j < hwidth;) {
	if (!(i % 2)) {
	  y[yy++] = (unsigned char) ((rdata[i * hwidth + j] & 0xff00) >> 8);
	  u[uv] = (unsigned char) ((rdata[i * hwidth + j] & 0x00ff) ^ 0x80);
	  j++;
	  y[yy++] = (unsigned char) ((rdata[i * hwidth + j] & 0xff00) >> 8);
	  v[uv] = (unsigned char) ((rdata[i * hwidth + j] & 0x00ff) ^ 0x80);
	  uv++;
	  j++;
	} else {
	  y[yy++] = (unsigned char) ((rdata[i * hwidth + j] & 0xff00) >> 8);
	  j++;
	}
      }
    }
    xdisp(b, c);
  }
}

yuv422_gray(int video)
{
  double c, b;
  int size;
  unsigned char *y, *u, *v;
  int     i, j, yy, uv;

  size = vsize * sizeof(short);

  while((nframes < 0) || ++loop < nframes){

    c = gettime();
    lseek(video, 0, SEEK_SET);
    read(video, rdata, size);
    b = gettime();
    r0 += b - c;
    yy = 0;
    uv = 0;
    y = yuvdata;
    u = y + vsize;
    v = u + vsize / 4;
    for (i = 0; i < hheight; i++){
      for (j = 0; j < hwidth;j++) {
	pdata[i * hwidth + j] = pixel[(unsigned char)
				      ((rdata[i * hwidth + j] & 0xff00) >> 8)];
      }
    }
    xdisp(b, c);
  }
}

main(int narg, char **argv)
{

  int     video;
  int     oarg;
  extern char *optarg;

  while ((oarg = getopt(narg, argv, "x:y:vhsSgn:ci:t:")) > 0) {

    switch (oarg) {
      
    case 'x':
      hwidth = atoi(optarg);
      break;

    case 'y':
      hheight = atoi(optarg);
      break;
      
    case 'v':
      verbose = 1;
      break;

    case 'S':
      spd_mode = 1;
      break;

    case 'h':

      printf("Usage:\n");
      printf("  %s [options]\n", argv[0]);
      printf("    Options:\n");
      printf("      -x width   Set width\n");
      printf("      -y height  Set height\n");
      printf("      -s         No Shared Memory \n");
      printf("      -S         Slow Mode \n");
      printf("      -g         Grayscale (Only 8bit Display) \n");
      printf("      -n frames  Set frames\n");
      printf("      -i msec    Set Interval\n");
      printf("      -c         stdout by 24bit ppm\n");
      printf("      -t channel Set TV Channel\n");
      printf("      -v         Verbose Output\n");
      printf("      -h         Help\n");
      exit(0);
      break;

    case 's':
      shmem = 0;
      break;

    case 'g':
      dithertype = GRAY_DITHER;
      break;

    case 'i':
      interval = atoi(optarg);
      break;

    case 'n':
      nframes = atoi(optarg);
      break;

    case 'c':
      sccat = 1;
      break;

    case 't':
      tv=1;
      tvchannel = atoi(optarg);
      break;

    default:
      fprintf(stderr, "%s : Unknown option or error "
	      "in option\n", argv[0]);
      exit(1);
    }
  }

  hwidth  = (int) (hwidth  / 8) * 8;
  hheight = (int) (hheight / 4) * 4;

  video = open("/dev/scc0", O_RDONLY);
  if (video < 0) {
    fprintf(stderr, "open failed: %s\n", strerror(errno));
    exit(1);
  }

  scc.width  = hwidth;
  scc.height = hheight;
  if (ioctl(video, SCCSETGEO, &scc)) {
    perror("ioctl");
    exit(1);
  }

  if(tv){

    scc_tv.tuntype = 0x61;
    scc_tv.channel = tvchannel;
    scc_tv.fine    = 0;
    scc_tv.country = 0;

    if (ioctl(video, SCCSETTVCHANNEL, &scc_tv)) {
      perror("ioctl");
      exit(1);
    }

  }

  if(sccat){

    int i,j,n = 0;

    spd_mode = SCC_SLOW;

    if (ioctl(video, SCCSETSPDMODE, &spd_mode)) {
      perror("ioctl");
      exit(1);
    }

    sleep(1); /* ad hot! but necessary */
    printf("P6\n");
    printf("%d %d\n",hwidth,hheight);
    printf("255\n");
    fflush(stdout);

    read(video,rdata,hwidth*hheight*sizeof(short));

    close(video);

    for(i=0;i<hheight;i++)
      for(j=0;j<hwidth;j++){
	rgbdata[n++]=(char)((rdata[i*hwidth+j]&0xf800) >> 8);
	rgbdata[n++]=(char)((rdata[i*hwidth+j]&0x07e0) >> 3);
	rgbdata[n++]=(char)((rdata[i*hwidth+j]&0x001f) << 3);	  
      }

    write(fileno(stdout),rgbdata,hwidth*hheight*3); 

    exit(0);

  }

  interval = interval * 1000;

  vsize = hwidth * hheight;

  MakeWindow(NULL);

  switch (depth) {
  case 8:
    if (dithertype != GRAY_DITHER) {
      dithertype = ORDERED_DITHER;
      InitColor();
      InitOrderedDither();
    }
    format = SCC_YUV422;
    break;
  case 16:
    if (dithertype != GRAY_DITHER) {
      dithertype = DEPTH16;
      format = SCC_RGB565;
    } else {
      fprintf(stderr, "xscc can not display Grayscale on %d bit display.\n", depth);
      exit(1);
    }
    break;
  case 24:
  case 32:
    if (dithertype != GRAY_DITHER) {
      dithertype = FULL_COLOR_DITHER;
      format = SCC_RGB565;
    } else {
      fprintf(stderr, "xscc can not display Grayscale on %d bit display.\n", depth);
      exit(1);
    }
    break;
  default:
    fprintf(stderr, "xscc can not display.\n");
    exit(1);
  }

  InitDisplay();

  if (ioctl(video, SCCSETFMT, &format)) {
    perror("ioctl");
    exit(1);
  }

  if (ioctl(video, SCCSETSPDMODE, &spd_mode)) {
    perror("ioctl");
    exit(1);
  }

  switch(format){
  case SCC_RGB565:
    switch (dithertype) {
    case DEPTH16:
      rgb565_depth16(video);
      break;
    case FULL_COLOR_DITHER:
      rgb565_fullcolor(video);
      break;
    }
    break;
  case SCC_YUV422:
    switch (dithertype) {
    case ORDERED_DITHER:
      yuv422_ordered(video);
      break;
    case GRAY_DITHER:
      yuv422_gray(video);
      break;
    }
    break;
  default:
    fprintf(stderr, "bad format\n");
  }
  close(video);
}
