#include <unistd.h>
#include <features.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include "videodev2.h"
#include <sys/mman.h>

#include "ivtv-ext-api.h"

typedef unsigned long long W64;
static inline W64 rdtsc() {
  W64 t;
  asm volatile("rdtsc" : "=A" (t));
  return t;
}

inline int bit(unsigned int v, unsigned int i) {
  return (v >> i) & 1;
}

int get_state(int fd, struct ivtvfb_ioctl_state_info* state, struct ivtv_osd_coords* osd) {
  if (ioctl(fd, IVTVFB_IOCTL_GET_STATE, state) < 0) {
    fprintf(stderr, "IVTVFB_IOCTL_GET_STATE failed (error: %s)\n", strerror(errno));
    return -2;
  }

  printf("Framebuffer state:\n");
  printf("  Framebuffer state:       %s\n", (state->status & IVTVFB_STATUS_ENABLED) ? "ON" : "off");
  printf("  Global alpha:            %s\n", (state->status & IVTVFB_STATUS_GLOBAL_ALPHA) ? "ENABLED" : "disabled");
  printf("  Local alpha:             %s\n", (state->status & IVTVFB_STATUS_LOCAL_ALPHA) ? "ENABLED" : "disabled");
  printf("  Flicker reduction:       %s\n", (state->status & IVTVFB_STATUS_FLICKER_REDUCTION) ? "ENABLED" : "disabled");
  printf("  Global alpha value:      %d\n", (int)state->alpha);

  if (ioctl(fd, IVTVFB_IOCTL_GET_ACTIVE_BUFFER, osd) < 0) {
    fprintf(stderr, "IVTVFB_IOCTL_GET_ACTIVE_BUFFER failed (error: %s)\n", strerror(errno));
    return -2;
  }

  printf("Active buffer:\n");
  printf("  Offset:                  0x%08x\n", osd->offset);
  printf("  Maximum offset:          0x%08x\n", osd->max_offset);
  printf("  Horizontal stride:       %d 32-bit words\n", osd->pixel_stride);
  printf("  Vertical lines:          %d lines\n", osd->lines);
  printf("  Coordinates (x, y):      %d, %d\n", osd->x, osd->y);

  printf("\n");

  return 0;
}

//++MTY TODO This is specific to the CPU being tested! Get from /proc/cpuinfo, etc. instead...
#define CPU_HZ (1.6*1000*1000*1000)

void ivtv_frame_loop(int fd) {
  struct ivtvfb_ioctl_dma_host_to_ivtv_args args;
  int size = 720*480*4;
  char* buffer = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0);
  printf("frameloop: Mapped userspace buffer 0x%08x of %d bytes:\n", (unsigned long)buffer, size); fflush(stdout);
  int rc = mlock(buffer, size);
  printf("mlock rc = %d\n", rc);

  int val = 0;
  int iters = 1024;

  W64 tprev;

  int i;
  for (i = 0; i < iters; i++) {
    tprev = rdtsc();
    //printf("%lld: (iter %d) filling userspace buffer...\n", tprev, i);
    memset(buffer, val & 0xff, size);
    val++;
    args.source = buffer;
    args.dest_offset = 0;
    args.count = size;
    //printf("%lld: (iter %d) Starting DMA...\n", rdtsc(), i);
    if (ioctl(fd, IVTVFB_IOCTL_PREP_FRAME, &args) < 0) {
      fprintf(stderr, "IVTVFB_IOCTL_PREP_DMA failed (error: %s)\n", strerror(errno));
      return -3;
    }
    W64 tnow = rdtsc();
    W64 tdiff = (tnow - tprev);
    float fps = (float)CPU_HZ / (float)tdiff;
    printf("iter %d: time = %f fps\n", i, fps);
  }
}

int main(int argc, char* argv[]) {
  if (argc < 2) {
    fprintf(stderr, "Syntax is:\n");
    fprintf(stderr, " ivtvfbctl /dev/fbX [-on|-off] [-globalalpha|-noglobalalpha] [-localalpha|-nolocalalpha] [-flicker|-noflicker] [-alpha<alpha>]\n");
    return -1;
  }

  int fd;
  char* filename = argv[1];
  if ((fd = open(filename, O_RDWR)) < 0) {
    fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
    return -2;
  }


  struct ivtvfb_ioctl_state_info state;
  struct ivtv_osd_coords osd;

  int rc = get_state(fd, &state, &osd);
  if (rc) 
    return rc;

  int do_bltcopy = 0;
  int do_prep_dma = 0;

  int i;
  for (i = 2; i < argc; i++) {
    if (!strcmp(argv[i], "-on"))
      state.status |= IVTVFB_STATUS_ENABLED;
    else if (!strcmp(argv[i], "-off"))
      state.status &= ~IVTVFB_STATUS_ENABLED;
    else if (!strcmp(argv[i], "-globalalpha"))
      state.status |= IVTVFB_STATUS_GLOBAL_ALPHA;
    else if (!strcmp(argv[i], "-noglobalalpha"))
      state.status &= ~IVTVFB_STATUS_GLOBAL_ALPHA;
    else if (!strcmp(argv[i], "-localalpha"))
      state.status |= IVTVFB_STATUS_LOCAL_ALPHA;
    else if (!strcmp(argv[i], "-nolocalalpha"))
      state.status &= ~IVTVFB_STATUS_LOCAL_ALPHA;
    else if (!strcmp(argv[i], "-flicker"))
      state.status |= IVTVFB_STATUS_FLICKER_REDUCTION;
    else if (!strcmp(argv[i], "-noflicker"))
      state.status &= ~IVTVFB_STATUS_FLICKER_REDUCTION;
    else if (!strcmp(argv[i], "-alpha")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -alpha\n"); return -3; }
      state.alpha = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-osdoffset")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -osdoffset\n"); return -3; }
      osd.offset = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-osdstride")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -osdstride\n"); return -3; }
      osd.pixel_stride = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-osdlines")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -osdline\n"); return -3; }
      osd.lines = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-osdx")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -osdx\n"); return -3; }
      osd.x = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-osdy")) {
      if (++i >= argc) { fprintf(stderr, "Missing parameter to -osdy\n"); return -3; }
      osd.y = strtol(argv[i], NULL, 0);
    } else if (!strcmp(argv[i], "-bltcopy")) {
      do_bltcopy = 1;
    } else if (!strcmp(argv[i], "-prepdma")) {
      do_prep_dma = 1;
    } else {
      fprintf(stderr, "Invalid option '%s'\n", argv[i]);
      return -3;
    }
  }

  if (do_bltcopy) {
    printf("ivtvfbctl: Doing BLT copy...\n"); fflush(stdout);
    struct ivtvfb_ioctl_blt_copy_args args;
    args.x = 50;
    args.y = 50;
    args.width = 620;
    args.height = 380;
    args.source_offset = 0;
    args.source_stride = 720;
    if (ioctl(fd, IVTVFB_IOCTL_BLT_COPY, &args) < 0) {
      fprintf(stderr, "%s: IVTVFB_IOCTL_BLT_COPY failed (error: %s)\n", filename, strerror(errno));
      return -3;
    }
  } else if (do_prep_dma) {
    ivtv_frame_loop(fd);
  } else {
    if (ioctl(fd, IVTVFB_IOCTL_SET_STATE, &state) < 0) {
      fprintf(stderr, "%s: IVTVFB_IOCTL_SET_STATE failed (error: %s)\n", filename, strerror(errno));
      return -3;
    }

    if (ioctl(fd, IVTVFB_IOCTL_SET_ACTIVE_BUFFER, &osd) < 0) {
      fprintf(stderr, "%s: IVTVFB_IOCTL_SET_ACTIVE_BUFFER failed (error: %s)\n", filename, strerror(errno));
      return -3;
    }
  }

  rc = get_state(fd, &state, &osd);
  if (rc) 
    return rc;

  close(fd);

  return 0;
}
