/* Takes data on stdin, plots it online.
   Copyright (C) 1998  Barak Pearlmutter <bap@cs.unm.edu>
   Started with code skeleton by Rob McMullen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/* TO DO:
 *
 * make producer quit when graph and other thread are dead
 *
 * deal with sync issues on realloc of xdata/ydata
 *
 * histogram bins are labeled by their left hand side, and bin counts are not
 * summetric about zero
 *
 * deal with expanding of histogram range more gracefully
 *
 * variable resolution bins tracking data density?
 *
 * allow command-line specification of histogram parameters

Line Styles

   For each list in the SciPlot widget, one of the following styles may
   be used to draw the lines that connect each point in the list:

   Style Name Description
   XtLINE_NONE no line (only points drawn)
   XtLINE_SOLID solid line (the default)
   XtLINE_DOTTED dotted line
   XtLINE_WIDEDOT widely spaced dotted line

Point Styles

   The SciPlot widget is capable of drawing markers at each data point.
   There are 19 predefined point marker definitions:

   Style Name Description
   XtMARKER_NONE no point marker drawn
   XtMARKER_CIRCLE open circle
   XtMARKER_SQUARE square
   XtMARKER_UTRIANGLE triangle (pointing up)
   XtMARKER_DTRIANGLE triangle (pointing down)
   XtMARKER_LTRIANGLE triangle (pointing left)
   XtMARKER_RTRIANGLE triangle (pointing right)
   XtMARKER_DIAMOND diamond
   XtMARKER_HOURGLASS hourglass shape
   XtMARKER_BOWTIE bowtie shape
   XtMARKER_FCIRCLE filled variants of the above...
   XtMARKER_FSQUARE
   XtMARKER_FUTRIANGLE
   XtMARKER_FDTRIANGLE
   XtMARKER_FLTRIANGLE
   XtMARKER_FRTRIANGLE
   XtMARKER_FDIAMOND
   XtMARKER_FHOURGLASS
   XtMARKER_FBOWTIE

void SciPlotListSetStyle(w,list_id,pcolor,pstyle,lcolor,lstyle)

 */

#define _REENTRANT

int debug = 0;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <pthread.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <SciPlot.h>
#include <SciPlotUtil.h>

#define HIST_N_BINS 200
#define HIST_MIN -1.0
#define HIST_MAX 1.0
#define HIST_FLOOR 0.1001

float hist_min = HIST_MIN;
float hist_max = HIST_MAX;
float hist_n_bins = HIST_N_BINS;
float hist_bin_size;
float hist_floor = HIST_FLOOR;

void
usage(char *s)
{
  printf("\
Plot a curve, with constant updates as data is genereated.  Runs as a
separate process taking points through its stdin.

Usage: %s [options...] < data

    Options:
\t--help, --usage, -h\tthis message
\t--version\tprint version information and exit
\t--debug, -d\t\tenable debugging output
\t--xy, -X\t\texpect x,y pairs on input, not sequential y's
\t--histogram, -H\t\thistogram
\t--no-autoexpand\t\tdon't grow x range of histogram to data
\t--n-bins=<number>\thow many bins in the histogram
\t--h-floor=<number>\tnumber with which to initialize histogram bins
\t--polar, -p\t\tpolar, not default rectangular
\t--title, -t \"Graph Title\"
\t--xlabel, -x \"X Axis Label\"
\t--ylabel, -y \"Y Axis Label\"
\t--legend, -l \"Name of Curve\"
\t--xlog\t\t\tuse log-scale for x axis
\t--ylog\t\t\tuse log-scale for y axis
\t--no-point\t\tdo not mark the datapoints
\t--no-line\t\tdo not link the datapoints

    Sample invocations:
\tyes | gawk '{print NR*NR*cos(NR/10)}' | head -1000 | %s
\ttail -f some-log-file | gawk '{print $2}' | %s
\tyes | gawk '{print sqrt(rand())}' | %s --histogram

\tFILE *plot_fd = popen(\"%s\", \"w\");
\tfor (...)
\t  fprintf(plot_fd, \"%%f\\n\", next_y_value);\n", s, s, s, s, s);
}

float *volatile xdata = NULL;
float *volatile ydata = NULL;

volatile int n = 0;
int old_n;


Bool xy_plot = False;		/* accept x,y pairs rather than just y's */
Bool histogram = False;
Bool histogram_autoexpand = True;
Bool no_line = False;
Bool no_point = False;

pthread_t prod;
volatile Bool producer_alive = True;
volatile Bool consumer_alive = True;

XtAppContext app_con;
int line;


inline int
hist_bin(float y, float hist_min, float hist_bin_size)
{
  return floor((y - hist_min) / hist_bin_size);
}

inline float
calc_hist_bin_size(float hist_min, float hist_max, int hist_n_bins)
{
  return (hist_max - hist_min)/(hist_n_bins - 1);
}


inline float hist_sum(float y1, float y2)
{
  if (y1 == (float)hist_floor)
    return y2;
  if (y2 == (float)hist_floor)
    return y1;
  return y1+y2;
}


static void *
producer(void *ignored)
{
  double x, y;
  int r;

  while (1) {

    if (xy_plot) {

      r = scanf("%lf", &x);
      if (!consumer_alive) break;

      if (r == EOF) break;

      if (r != 1) {
	fprintf(stderr, "warning: invalid x value at %d\n", n);
	continue;
      }
    } else {
      x = n+1;
    }

    r = scanf("%lf", &y);
    if (!consumer_alive) break;

    if (r == EOF) {
      if (debug) printf("notice: eof on input, producer terminated\n");
      break;
    }
  
    if (r != 1) {
      fprintf(stderr, "warning: invalid y value at %d\n", n);
      continue;
    }

    if (histogram) {
      int i;

    again:

      i = hist_bin(y, hist_min, hist_bin_size);

      if (i < 0 || i >= hist_n_bins) {
	if (histogram_autoexpand) {
	  float old_hist_min = hist_min;
	  float old_hist_bin_size = hist_bin_size;
	  int j;

	  hist_bin_size *= 2;
	  hist_min *= 2;
	  hist_max *= 2;
      
	  for (j=0; j<hist_n_bins; j++)
	    xdata[j] *= 2;

	  for (j=0; j<hist_n_bins; j++) {
	    int k = hist_bin(xdata[j], old_hist_min, old_hist_bin_size);
	    
	    if (k >= 0 && k < hist_n_bins-1)
	      ydata[j] = hist_sum(ydata[k], ydata[k+1]);
	    else
	      ydata[j] = hist_floor;
	  }

	  if (debug)
	    printf("expanded range %g to %g\n", hist_min, hist_max);

	  goto again;

	} else {

	  if (debug) {
	    printf("warning: %g outside bounds %g to %g, ignored.\n",
		   y, hist_min, hist_max);

	  }
	}
      } else {
	if (ydata[i] < 1)
	  ydata[i] = 1;
	else
	  ydata[i] += 1;
	n += 1;
      }

    } else {
      xdata = realloc(xdata, (n+1)*sizeof(*xdata));
      ydata = realloc(ydata, (n+1)*sizeof(*ydata));

      if (!xdata || !ydata) {
	fprintf(stderr, "memory allocation failure - producer terminated!\n");
	break;
      }

      if (debug) printf("Read %g\n", y);
      xdata[n] = x;
      ydata[n] = y;
      n += 1;
    }
  }
  
  producer_alive = False;
  return 0;
}

void
set_hist_xdata(float *xdata, int n_bins, float min_bin, float bin_size)
{
  int i;
  for (i=0; i<n_bins; i++)
    xdata[i] = min_bin + i*bin_size;
}

/* ARGSUSED */
static void 
Update(XtPointer client_data, XtIntervalId * idp)
{
  Widget plot = (Widget) client_data;

  if (debug) printf("update\n");

  /* change this if to a while, if you want the white hashies! */ 
  if (n > old_n) {
    while (n > old_n) {
      while (n > old_n) {
	old_n = n;
    
	if (debug) {
	  printf("Consuming to %d", old_n);
	  if (histogram)
	    printf("\n");
	  else
	    printf(", got %g\n", ydata[old_n-1]);
	}
      }

      if (debug) printf("blam\n");
      SciPlotListUpdateFromFloat(plot, line,
				 (histogram ? hist_n_bins : old_n),
				 xdata, ydata);
    }

    if (debug) printf("replot\n");

    if (SciPlotQuickUpdate(plot)) {
      SciPlotUpdate(plot);
    }

    /* this seems to cause nasty white line hashing mode */
    /* if (n > old_n) continue; */
  }

  if (debug) printf("reschedule\n");

  if (producer_alive || n > old_n)
    XtAppAddTimeOut(app_con,500,Update,plot);
  else {
    pthread_join(prod, NULL);
    if (debug) fprintf(stderr, "Producer is dead - polling terminated.\n");
  }
}


int
main(int argc, char **argv)
{
  Widget toplevel, dummy, plot;
  char *legend = "curve";
  char *title = "Online Plot";
  char *xlabel = "x";
  char *ylabel = "y";
  Bool polar_plot = False;
  Bool xlog = False;
  Bool ylog = False;
  
  while (1)
    {
      static struct option long_options[] =
      {
	{"help", 0, 0, 'h'},
	{"usage", 0, 0, 'h'},
	{"version", 0, 0, 'v'},
	{"debug", 0, 0, 'd'},
	{"xy", 0, 0, 'X'},
	{"histogram", 0, 0, 'H'},
	{"n-bins", 1, 0, 'N'},
	{"no-autoexpand", 0, 0, 'n'},
	{"h-floor", 1, 0, 'F'},
	{"polar", 0, 0, 'p'},
	{"title", 1, 0, 't'},
	{"xlabel", 1, 0, 'x'},
	{"ylabel", 1, 0, 'y'},
	{"legend", 1, 0, 'l'},
	{"xlog", 0, 0, 'A'},
	{"ylog", 0, 0, 'B'},
	{"no-line", 0, 0, 'q'},
	{"no-point", 0, 0, 'r'},
	{0,0,0,0}
      };
      int option_index = 0;
      int c = getopt_long_only(argc, argv, "hdXHpt:x:y:l:",
			       long_options, &option_index);
      if (c == -1) break;
      switch (c)
	{
	case 0:
	  printf ("option %s", long_options[option_index].name);
	  if (optarg)
	    printf (" with arg %s", optarg);
	  printf ("\n");
	  break;

	case 'h':
	  usage(argv[0]);
	  return 0;
	case 'v':
	  printf("plotonline (BAP hack) sciplot library interface 1.1\n");
	  exit(0);
	case 'd':
	  debug += 1;
	  break;
	case 'X':
	  xy_plot = True;
	  break;
	case 'H':
	  histogram = True;
	  break;
	case 'n':
	  histogram_autoexpand = False;
	  break;
	case 'N':
	  {
	    char *endptr;
	    hist_n_bins = strtol(optarg, &endptr, 0);
	    if (*endptr != '\0' || errno || hist_n_bins <= 0) {
	      fprintf(stderr, "error: invalid number of bins `%s'\n", optarg);
	      exit(2);
	    }
	  }
	  break;
	case 'F':
	  {
	    char *endptr;
	    hist_floor = strtod(optarg, &endptr);
	    if (*endptr != '\0' || errno) {
	      fprintf(stderr, "error: invalid histogram floor `%s'\n", optarg);
	      exit(2);
	    }
	  }
	  break;
	case 'p':
	  polar_plot = True;
	  break;
	case 't':
	  title = optarg;
	  break;
	case 'x':
	  xlabel = optarg;
	  break;
	case 'y':
	  ylabel = optarg;
	  break;
	case 'l':
	  legend = optarg;
	  break;
	case 'A':
	  xlog = True;
	  break;
	case 'B':
	  ylog = True;
	  break;
	case 'q':
	  no_line = True;
	  break;
	case 'r':
	  no_point = True;
	  break;

	case '?':
	  exit(1);

	default:
	  printf ("?? getopt returned character code 0%o ??\n", c);
	}
    }

  if (optind < argc) {
    fprintf(stderr, "%s: too many arguments.\n", argv[0]);
    exit(2);
  }

  if (no_point && no_line) {
    fprintf(stderr, "warning: drawing neither marks nor lines?\n");
  }

  old_n = n;

  if (histogram) {
    int i;
    hist_bin_size = calc_hist_bin_size(HIST_MIN, HIST_MAX, hist_n_bins);
    xdata = malloc(hist_n_bins*sizeof(*xdata));
    ydata = malloc(hist_n_bins*sizeof(*xdata));
    set_hist_xdata(xdata, hist_n_bins, HIST_MIN, hist_bin_size);
    for (i=0; i<hist_n_bins; i++)
      ydata[i] = 0.2;
  } else {
    xdata = malloc(1024*sizeof(*xdata));
    ydata = malloc(1024*sizeof(*xdata));
  }
  
  if (pthread_create(&prod, NULL, producer, 0)) {
    fprintf(stderr, "Error: cannot spawn thread.\n");
    exit(2);
  }

  toplevel = XtAppInitialize(&app_con, "SciPlot",
			     NULL, 0, &argc, argv, NULL, NULL,
			     0);

  dummy =
    XtVaCreateManagedWidget("dummy",
			    /* sciplotWidgetClass, toplevel, */
			    coreWidgetClass, toplevel,

			    XtNchartType, (polar_plot
					   ? XtPOLAR
					   : XtCARTESIAN),
			    XtNdegrees, True,
			    XtNwidth, 1,
			    XtNheight, 1,
			    XtNplotTitle, title,
			    XtNxLabel, xlabel,
			    XtNyLabel, ylabel,
			    XtNshowLegend, False,
			    XtNxLog, xlog,
			    XtNyLog, ylog,

			    XtNdrawMajor, False,
			    XtNdrawMinor, False,

			    NULL);
  /*
    plotWidget= XtVaCreateManagedWidget("plot2",
	  sciplotWidgetClass, parent,
          XtNheight, 250,
          XtNwidth, 250,
          XtNplotTitle, "Demo of Plot Widget",
          XtNxLabel, "X Axis (units)",
          XtNyLabel, "Y Axis (units)",
          XtNchartType, XtPOLAR,
          XtNdegrees, True,
          NULL);
  */

  XtSetMappedWhenManaged(toplevel, False);

  plot = SciPlotDialog(toplevel, title);

  line = SciPlotListCreateFromFloat(plot,
				    (histogram ? hist_n_bins : n),
				    xdata, ydata, legend);
  
  SciPlotListSetStyle(plot, line,
		      1, no_point ? XtMARKER_NONE : XtMARKER_FCIRCLE,
		      1, no_line ? XtLINE_NONE : XtLINE_SOLID);

  XtRealizeWidget(toplevel);

  SciPlotUpdate(plot);

  XtAppAddTimeOut(app_con,500,Update,plot);

  XtAppMainLoop(app_con);
  if (debug) fprintf(stderr, "exited XtAppMainLoop\n");
  consumer_alive = False;

  return 0;
}

