/*
 * This example reads an image from MMACo1k and displays the image in a
 * Xwindow
 */

#include <stdio.h>
#include <stdlib.h>
#include <xil/xil.h>

#include "ddither.h"

Atom closedownAtom;

/* - - - - - - - - - - - - - - - - - - - - - - */

#define GETOPTARGS "X:Y:B:C:H:U:V:s:I:f:i:m:x:y:D:c:gEdTt"

void 
usage(char *name, char *badparam)
{
    if (badparam)
	fprintf(stderr, "ERROR: bad parameter [%s]\n", badparam);

    fprintf(stderr, "\
Usage: %s [options]\n\n\
Legal Options:					 Default\n\
	-s %%d	- scale factor (effects both x/y) 2.0\n\
	-X %%d	- x scale factor   	          0.0\n\
	-Y %%d	- y scale factor	          0.0\n\
	-I %%d	- select input channel		  1\n\
	-f %%d	- frames to capture		  100\n\
	-i %%d	- frames to skip between capture  0\n\
        -m %%d	- max frames to buffer		  2\n\
        -x %%d	- x window location		  (random)\n\
        -y %%d	- y window location		  (random)\n\
	-D %%s	- name of o1k device		  /dev/o1k0\n\
Video Attributes:                                \n\
        -B %%d  - brightness                     none\n\
        -C %%d  - contrast                       none\n\
        -H %%d  - hue                            none\n\
        -U %%d  - saturation/gain U component    none\n\
        -V %%d  - saturation/gain V component    none\n\
        -c %%d  - colormap/dither version (0=XIL std, 1=XIL gamma corrected\n\
Legal Switches:\n\
	-E	- exit on first error  \n\
	-d	- don't display result of capture \n\
	-g	- grayscale capture\n\
	-t	- enable threshold on grayscale capture \n\
	-T	- display timestamp and frame numbers \n\
", name);

    exit(1);
}

/* - - - - - - - - - - - - - - - - - - - - - - */

/*
 * Error handler installed iff -E specified
 */
Xil_boolean 
error_handler(XilError error)
{
    xil_call_next_error_handler(error);
    fprintf(stderr, "\n***ERROR received; example exiting\n");
    exit(1);
}

main(int argc, char **argv)
{
    XilSystemState state;
    XilImage o1k_image, o1k_luma, display;
    XilImage rescaled_image, scaled_image;
    unsigned int width, height, nbands;
    int winx = 0;
    int winy = 0;
    int xyflag = 0;
    XilDataType datatype;
    Display *xdisplay;
    Window xwindow;
    XEvent event;
    int display_depth;
    XilLookup yuv_to_rgb;
    XilLookup colorcube;
    XilLookup graylookup;
    XilDitherMask mask;
    XilColorspace cspace_ycc601;
    XilColorspace cspace_rgb709;
    int frames;
    int total_frames = 100;
    int image_skip = 0;
    int max_buffers = -1;
    float doscale   = 2.0;
    float doscale_x = 0.0;
    float doscale_y = 0.0;
    float scale_factor;
    float scale_factor_x;
    float scale_factor_y;
    int grayscale = 0;
    Xil_boolean thresholdEnable = FALSE;
    float rescale_values[3];
    float offset_values[3];
    double t;
    struct timeval t0, t1;
    int value;
    char *type;
    int port = 1;
    Xil_boolean nodisplay = FALSE;
    Xil_boolean error_out = FALSE;
    char *devname = "/dev/o1k0";
    int c;

    Xil_boolean timestampts_wanted = FALSE;
    hrtime_t* timeStamp_ptr = NULL;
    hrtime_t  timeStamp_last;
    hrtime_t  timeStamp_cur;
    unsigned int frame_number = 0;

    /*
     * 512 is not a valid value for any of the following.
     * If value is not 512 after parsing cmd line args,
     * then we know to set the desired value.
     */
    int brightness = 512;
    int contrast   = 512;
    int hue        = 512;
    int gain_u     = 512;
    int gain_v     = 512;
    
    /* GRAYSCALE */
    int		  GS_Threshold_min = 32;
    int		  GS_Threshold_max = 253;

    float low[2];
    float high[2];
    float map[2];

    XilImage         image8           = NULL;
    XilImage         image8b          = NULL;
    XilImage         image8c          = NULL;
    
    low[0]  = 0.0;
    high[0] = GS_Threshold_min;
    map[0]  = GS_Threshold_min;
    low[1]  = GS_Threshold_max;
    high[1] = 255.0;
    map[1]  = GS_Threshold_max;

    while ((c = getopt(argc, argv, GETOPTARGS)) != EOF)
	switch (c) {
	case 's':
	    doscale = atof(optarg);
        if (doscale < 1.0)
            doscale = 1.0;
	    break;
	case 'X':
	    doscale_x = atof(optarg);
        if (doscale_x < 1.0)
            doscale_x = 1.0;
	    break;
	case 'Y':
	    doscale_y = atof(optarg);
        if (doscale_y < 1.0)
            doscale_y = 1.0;
	    break;
	case 'I':
	    port = atoi(optarg);
	    break;
	case 'D':
	    devname = optarg;
	    break;
	case 'i':
	    image_skip = atoi(optarg);
	    break;
	case 'm':
	    max_buffers = atoi(optarg);
	    break;
	case 'x':
	    winx = atoi(optarg);
	    xyflag = 1;
	    break;
	case 'y':
	    winy = atoi(optarg);
	    xyflag = 1;
	    break;
	case 'f':
	    total_frames = atoi(optarg);
	    break;
	case 'c':
	    dither_version = atoi(optarg);
	    break;
    case 'g':
     	    grayscale = 1;
	        break;
    case 't':
     	    thresholdEnable = TRUE;
	        break;
    case 'T':
     	    timestampts_wanted = TRUE;
	        break;
	case 'd':
	    nodisplay = TRUE;	/* useful for timing raw capture speed */
	    break;
	case 'E':
	    error_out = TRUE;
	    break;
    
       case 'B':
            brightness = atoi(optarg);
            if (brightness < -128)
              brightness = -128;
            if (brightness > 127)
              brightness = 127;
	    break;
        case 'C':
            contrast = atoi(optarg);
            if (contrast < 0)
              contrast = 0;
            if (contrast > 511)
              contrast = 511;
	    break;
        case 'H':
            hue = atoi(optarg);
            if (hue < -128)
              hue = -128;
            if (hue > 127)
              hue = 127;
	    break;
        case 'U':
            gain_u = atoi(optarg);
            if (gain_u < 0)
              gain_u = 0;
            if (gain_u > 511)
              gain_u = 511;
	    break;
        case 'V':
            gain_v = atoi(optarg);
            if (gain_v < 0)
              gain_v = 0;
            if (gain_v > 511)
              gain_v = 511;
	    break;
	case '?':
	default:
	    usage(argv[0], NULL);
	    break;
	}

    /* expect no args left over */
    if (argv[optind]) {
	usage(argv[0], argv[optind]);
    }

    state = xil_open();
    if (state == NULL) {
      fprintf(stderr, "unable to open xil library\n");
      exit(1);
    }
    
    if (error_out)
      xil_install_error_handler(state, error_handler);
    
    {
      XilDevice device;
      
      if (!(device = xil_device_create(state, "MMACo1k"))) {
        fprintf(stderr, "Unable to create a device object\n");
        xil_close(state);
        exit(1);
      }
      
      xil_device_set_value(device, "DEVICE_NAME", (void *) devname);
      xil_device_set_value(device, "PORT_V", (void *) port);
      if (image_skip)
        xil_device_set_value(device, "IMAGE_SKIP", (void *) image_skip);
      if (max_buffers >= 0)
        xil_device_set_value(device, "MAX_BUFFERS", (void *) max_buffers);
            
      /*
       * Before the device is created, we can set these values
       * via xil_device_set_value(). After the device is created
       * with xil_create_from_device(), you would use the call
       * to xil_set_device_attribute() on the device image to
       * set these values.
       */
      if (brightness != 512)
        xil_device_set_value(device, "BRIGHTNESS", (void *) brightness);
      if (contrast != 512)
        xil_device_set_value(device, "CONTRAST", (void *) contrast);
      if (hue != 512)
        xil_device_set_value(device, "HUE", (void *) hue);
      if (gain_u != 512)
        xil_device_set_value(device, "CHROMA_GAIN_U", (void *) gain_u);
      if (gain_v != 512)
        xil_device_set_value(device, "CHROMA_GAIN_V", (void *) gain_v);

      if (!(o1k_image = xil_create_from_device(state, "MMACo1k",
                                               device))) {
        fprintf(stderr, "failed to open MMACo1k device\n");
        xil_close(state);
        exit(1);
      }
      xil_device_destroy(device);
    }
    
    {
      int format;
      
      xil_get_device_attribute(o1k_image, "FORMAT_V", (void **) &format);
      if (format == 0) {
        fprintf(stderr, "Unknown video format, exiting.\n");
        xil_close(state);
        exit(1);
      }
    }
    
    
    xil_get_info(o1k_image, &width, &height, &nbands, &datatype);
    
    
    if (grayscale) {
      o1k_luma = xil_create_child(o1k_image, 0, 0, width, height, 0, 1);      
    }
    else {
      rescaled_image = xil_create(state, width, height, nbands, XIL_BYTE);
    }
    
    if (doscale) {
      scale_factor = 1.0 / doscale;

      /* use scale_factor above if doscale_x not set */
      if (!doscale_x)
          scale_factor_x = scale_factor;
      else
          scale_factor_x = 1.0 / doscale_x;

      /* use scale_factor above if doscale_y not set */
      if (!doscale_y)
          scale_factor_y = scale_factor;
      else
          scale_factor_y = 1.0 / doscale_y;

      width = (int) (((float) width * scale_factor_x) + 0.5);
      height = (int) (((float) height * scale_factor_y) + 0.5);

      if (!grayscale) {
        scaled_image = xil_create(state, width, height, nbands, XIL_BYTE);
      }
    }
    else {
      scale_factor_x = scale_factor_y = scale_factor = 1.0;
    }
    if (grayscale) {
        /* create a 1-band images with the scaled dimensions for capture threshold */
        image8      = xil_create(state, width, height, 1, XIL_BYTE);
        image8b     = xil_create(state, width, height, 1, XIL_BYTE);
        image8c     = xil_create(state, width, height, 1, XIL_BYTE);
    }
    
    if (nodisplay) {
      /* for simplicity's sake, the 'nodisplay' option assumes a 1-banded */
      /* XIL_BYTE destination image */
      display = xil_create(state, width, height, 1, XIL_BYTE);
      display_depth = 8;
    }
    else {
      XSizeHints hints;
      int port;
      char *format;
      char titlebar[1024];
      
      /* xlib window creation */
      xdisplay = XOpenDisplay(NULL);
      if (!xdisplay) {
        fprintf(stderr, "Unable to connect to server\n");
        xil_close(state);
        exit(1);
      }
      display_depth = DefaultDepth(xdisplay, DefaultScreen(xdisplay));
      xwindow = XCreateSimpleWindow(xdisplay, DefaultRootWindow(xdisplay),
                                    winx, winy, width, height, 0, 0, 0);
      if (!xwindow) {
        fprintf(stderr, "Unable to create window\n");
        xil_close(state);
        exit(1);
      }
      if (xyflag) {
        hints.flags = USPosition;
        hints.x = winx;
        hints.y = winy;
      }
      else {
        hints.flags = 0;
      }
      xil_get_device_attribute(o1k_image, "FORMAT", (void **) &format);
      xil_get_device_attribute(o1k_image, "PORT_V", (void **) &port);
      sprintf(titlebar, "%s: Port %d [%s]",
              (devname ? devname : "/dev/o1k0"), port, format);
      
      XSetStandardProperties(xdisplay, xwindow, titlebar, "o1k_display", 0,
                             argv, argc, &hints);
      
      /* if the user tries to shut down the window using the menu */
      if (closedownAtom = XInternAtom(xdisplay, "WM_DELETE_WINDOW", False))
        XSetWMProtocols(xdisplay, xwindow, &closedownAtom, 1);
      
      /* list window events used */
      XSelectInput(xdisplay, xwindow, ExposureMask | ButtonPressMask);
      
      /* make the window visible */
      XMapWindow(xdisplay, xwindow);
      
      /* wait for the window to be mapped (an Expose event) */
      do
        XNextEvent(xdisplay, &event);
      while (event.xany.type != Expose);
      
      display = xil_create_from_window(state, xdisplay, xwindow);
    }
    
    if (display_depth == 8) {
      if (grayscale) {
        if (!nodisplay) {
          XilLookup grayramp;
          int num_entries = 256;
          int i;
          
          Xil_unsigned8 *graydata = malloc(3 * num_entries);
          
          for (i = 0; i < num_entries; i++) {
            graydata[i * 3 + 2] = graydata[i * 3 + 1] = graydata[i * 3] = i;
          }
          grayramp = xil_lookup_create(state, XIL_BYTE, XIL_BYTE,
                                       3, num_entries, 0,
                                       graydata);
          init_cmap(grayramp, xdisplay, xwindow, 0);
        }
      }
      else {
        get_dither_info(state, rescale_values, offset_values, &mask,
                        &yuv_to_rgb, &colorcube);
        if (!nodisplay) {
            init_cmap(yuv_to_rgb, xdisplay, xwindow, -1);
            xil_lookup_set_offset(colorcube, xil_lookup_get_offset(yuv_to_rgb));
        }
      }
    }
    else if (display_depth == 24) {
      if (grayscale) {
        int i, t;
        Xil_unsigned8 *data = malloc(256 * 3);
        
        if (data == NULL) {
          fprintf(stderr, "Out of memory\n");
          xil_close(state);
          exit(1);
        }
        for (i = 0, t = 0; i < 256; i++, t += 3) {
          data[t] = i;
          data[t + 1] = i;
          data[t + 2] = i;
        }
        graylookup = xil_lookup_create(state, XIL_BYTE, XIL_BYTE,
                                       3, 256, 0, data);
      }
      else {
        cspace_ycc601 = xil_colorspace_get_by_name(state, "ycc601");
        cspace_rgb709 = xil_colorspace_get_by_name(state, "rgb709");
        if (doscale)
          xil_set_colorspace(scaled_image, cspace_ycc601);
        else
          xil_set_colorspace(o1k_image, cspace_ycc601);
        xil_set_colorspace(display, cspace_rgb709);
      }
    }
    else {
      fprintf(stderr, "visual depth of %d is not supported\n", display_depth);
      xil_close(state);
      exit(1);
    }
    
    xil_get_device_attribute(o1k_image, "TIMESTAMP", (void*)&timeStamp_ptr);

    for (frames = 0; frames < total_frames; frames++) {
      
      if (grayscale) {
        if (display_depth == 24) {
          
          /*
           * even if scale_factor is 1, this combination of xil_scale()
           * & xil_lookup() is faster than
           * xil_lookup(o1k_luma,display,graylookup)
           */
          xil_scale(o1k_luma, image8, "nearest",
                    scale_factor_x, scale_factor_y);
          if (thresholdEnable){
              xil_threshold(image8,  image8b, &low[0], &high[0], &map[0]);
              xil_threshold(image8b, image8c, &low[1], &high[1], &map[1]);
              xil_lookup(image8c, display, graylookup);

              xil_toss(image8);
              xil_toss(image8b);
              xil_toss(image8c);
          } else {
              xil_lookup(image8, display, graylookup);
              xil_toss(image8);
          }

        }
        else {		/* display_depth is 8 */
          
          /*
           * even if scale_factor is 1, xil_scale() is faster than
           * xil_copy(o1k_luma,display) for this case
           */
          xil_scale(o1k_luma, image8, "nearest",
                    scale_factor_x, scale_factor_y);
          if (thresholdEnable){
              xil_threshold(image8,  image8b, &low[0], &high[0], &map[0]);
              xil_threshold(image8b, image8c, &low[1], &high[1], &map[1]);          
              xil_copy(image8c, display);
              xil_toss(image8);
              xil_toss(image8b);
              xil_toss(image8c);
          } else {
              xil_copy(image8, display);
              xil_toss(image8);
          }

          if (nodisplay)
            xil_sync(display);

        }
      }
      else {
        if (display_depth == 24) {
          if (doscale) {
            xil_scale(o1k_image, scaled_image, "nearest",
                      scale_factor_x, scale_factor_y);
            xil_color_convert(scaled_image, display);
            xil_toss(scaled_image);
          }
          else {
            xil_color_convert(o1k_image, display);
          }
        }
        else {		/* display_depth is 8 */
          xil_rescale(o1k_image, rescaled_image, rescale_values,
                      offset_values);
          if (doscale) {
            xil_scale(rescaled_image, scaled_image, "nearest",
                      scale_factor_x, scale_factor_y);
            xil_ordered_dither(scaled_image, display, colorcube, mask);
            xil_toss(scaled_image);
          }
          else {
            xil_ordered_dither(rescaled_image, display,
                               colorcube, mask);
          }
          if (nodisplay)
            xil_sync(display);
          xil_toss(rescaled_image);
        }
      }
      /* don't include overhead of first frame in frame rate calculation */
      if (frames == 0)
        gettimeofday(&t0, NULL);
      
      if (!nodisplay) {
        while (XPending(xdisplay)) {
          XNextEvent(xdisplay, &event);
          
          if (event.xany.type == ClientMessage &&
              ((XClientMessageEvent *) & event)->data.l[0] == closedownAtom) {
            goto cleanup;
            
          }
          else if (event.xany.type == ButtonPress) {
            goto done;
            
          }
          else if (event.xany.type != Expose) {
            fprintf(stderr, "event %d\n", event.xany.type);
          }
        }
      }

       if (timestampts_wanted){
          timeStamp_cur  = *timeStamp_ptr;
          xil_get_device_attribute(o1k_image, "FRAME_NUMBER", (void*)&frame_number);
          printf("frame[%.4d] cur stamp: %llx from last: %lld msec\n",
                 frame_number,
                 timeStamp_cur,
                 (timeStamp_cur - timeStamp_last)/1000000);
          timeStamp_last = timeStamp_cur;
      }
    }
    
done:

    gettimeofday(&t1, NULL);
    t = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
    printf("%d frames in %g seconds, %g frames/second\n",
	   frames - 1, t, (frames - 1) / t);
    
cleanup:

    if (!nodisplay) {
      /* must destroy display image before calling XCloseDisplay */
      xil_destroy(display);
      
      XCloseDisplay(xdisplay);
    }
    
    xil_close(state);
    
    return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - */
