/*
   OpenGL Waveform Viewer

   Copyright (c) 2001 by Tobin Fricke <tobin@splorg.org>
   University of California at Berkeley

   Created May 27, 2001
*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <GL/glut.h>
#include <GL/gl.h>

/*

  options:
  
  stretch or enlarge?
  zoom in or zoom out?
  drag with contents?
  dynamic rescaling?

*/

int enlarge_on_resize = 1;

double *waveform = NULL;
int n_points = 0;

int pixels_width = 300;
int pixels_height = 300;

#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)

int x_min = 0;   //first point
int x_max = -1;  // last point

double selected_xmin = 0;
double selected_xmax = 0;

int mouse_x;
int mouse_y;
int mouse_state = 0;
int mouse_scrolling = 0;
int mouse_selecting = 0;
int scrolldown_x = 0;

int delta_x;

void display(void) {
  
  double ymin;
  double ymax;
  int xmin, xmax;

  if (x_max < x_min)
    x_max = min(n_points,100);

  xmin = x_min + delta_x;
  xmax = x_max + delta_x;
  glClear(GL_COLOR_BUFFER_BIT);


  //xmin = max(xmin,0);
  //xmax = min(xmax,n_points);

  // scale the display to fit the data
  ymin=waveform[xmin];
  ymax=waveform[xmax];
  for (int i=xmin; i<xmax; i++) 
    if ((i>=0) && (i<n_points)) {
      ymin = min(ymin,waveform[i]);
      ymax = max(ymax,waveform[i]);
    }
  
  if (mouse_selecting) {
    // blue background
    glColor3f(0.0,0.0,1.0);
    glBegin(GL_QUADS);
    glVertex2f(selected_xmin,1.0);
    glVertex2f(selected_xmax,1.0);
    glVertex2f(selected_xmax,0.0);
    glVertex2f(selected_xmin,0.0);
    glEnd();
  }

  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINES);
  // plot the data
  for (int i=xmin; i<xmax-1; i++){
    if ((i >= 0) && (i<n_points-1)) {
      glVertex2f((i-xmin)*1.0/(xmax - xmin - 1),(waveform[i] - ymin)/(ymax - ymin)); 
      glVertex2f((i-xmin+1)*1.0/(xmax - xmin - 1),(waveform[i+1] - ymin)/(ymax - ymin)); 
    }
  }
  glEnd();
  glFinish();
  glutSwapBuffers();
}

void load_waveform(char *filename, int *n_points, double **waveform) {
  FILE *file;
  double temp;
  file = fopen(filename,"r");
  if (!file) return;
  while (1 == fscanf(file,"%lf",&temp)) {
    (*n_points) ++;
    *waveform = (double *)realloc(*waveform, *n_points * sizeof(double));
    (*waveform)[*n_points - 1] = temp;   
  }
  fclose(file);
  return;
}


void resize(int x,int y) {
  
  if (enlarge_on_resize) 
    x_max = (int)(x_min + (x_max - x_min)*((double)x/(double)pixels_width));
  
  pixels_width = x;
  pixels_height = y;
  glViewport(0,0,x,y);
}

void mouse_move(int x, int y) {       
  if (mouse_scrolling) {
    delta_x = (int)((scrolldown_x - x) * (double)(x_max - x_min)/pixels_width);
    glutPostRedisplay();
  }
  if (mouse_selecting) {
    selected_xmax = (double)x/pixels_width;
    printf("Selected from %d to %d: %d samples (%0.2f seconds)\n",
	   (int)(x_min+(x_max-x_min)*selected_xmin), 
	   (int)(x_min+(x_max-x_min)*selected_xmax),
	   (int)(fabs(selected_xmax-selected_xmin)*(x_max - x_min)),
	   fabs(selected_xmax-selected_xmin)*(x_max - x_min)/20.0);

    glutPostRedisplay();
  }

}

void swap_double(double *a, double *b) {
  double c;
  c = *a;
  *a = *b;
  *b = c;
}

void mouse(int button, int state, int x, int y) { 
  mouse_x = x;
  mouse_y = y;
  mouse_state = state;

  // printf("button %d state %d\n",button,state);
  if (button == 0) {
    mouse_scrolling = (state == 0);
    if (!mouse_scrolling) {
      x_min += delta_x;
      x_max += delta_x;
      delta_x = 0;
    } else
      scrolldown_x = mouse_x;
  } else
  if (button == 2) {
    mouse_selecting = (state == 0);
    if (mouse_selecting) {
      selected_xmin = (double)x/pixels_width;
      selected_xmax = (double)x/pixels_width;
      printf("selecting at %f\n",(double)x/pixels_width);
    } else {
      int old_xmin, old_xmax;

      if (selected_xmin > selected_xmax) 
	swap_double(&selected_xmin, &selected_xmax);
      
      old_xmin = x_min;
      old_xmax = x_max;

      // zoom out

      /*
      x_min = old_xmin / (selected_xmax - selected_xmin);
      x_max = old_xmax / (selected_xmax - selected_xmin);
      */

      // zoom in
      
      x_min = old_xmin + (int)(selected_xmin*(old_xmax - old_xmin));
      x_max = old_xmin + (int)(selected_xmax*(old_xmax - old_xmin));
      if (abs(x_max - x_min) < 5) {
	x_max = x_min+20;
      }
      glutPostRedisplay();
    }
    
  }

  mouse_move(x,y);
}

void key(unsigned char k, int x, int y) {
  return;
}


int main(int argc, char **argv) {

  char *filename;

  if (argc > 1) 
    filename = argv[1];
  else
    filename = "mydata";

  load_waveform(filename,&n_points,&waveform);

  if (n_points < 1) {
    printf("Waveform file (\"%s\") not found: making up some random data.\n",filename); 
    n_points = 2000;
    waveform = (double*)calloc(n_points,sizeof(double));
    for (int i=0; i<n_points; i++) waveform[i]=(double)random()/RAND_MAX;
  }

  printf("loaded %d data points\n",n_points);

  x_min = 0;
  x_max = n_points;

  //for (int i=0; i<n_points; i++) 
  //  printf("%d\t%d\n",i,waveform[i]);

  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
  glutCreateWindow("Waveform viewer");
  glutDisplayFunc(display);

  glutReshapeFunc(resize);
  glutMouseFunc(mouse);
  glutMotionFunc(mouse_move);
  
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0,1,0,1,0,100);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  glutMainLoop();
}
