/*
   File:        splot.c
   Author(s):   Andrew Moore
   Created:     Sat Oct 25 08:23:58 EDT 2003
   Description: time Series Plot
   Copyright (c) Carnegie Mellon University
*/

#include "splot.h"

typedef struct smap
{
  agbox *agb;
  int start_row;
  int num_rows;
} smap;

smap *mk_smap(double xlo,double ylo,double xhi,double yhi,
	      int start_row,int num_rows)
{
  smap *x = AM_MALLOC(smap);

  x -> agb = mk_agbox(xlo,ylo,xhi,yhi);
  x -> start_row = start_row;
  x -> num_rows = num_rows;

  return x;
}

void free_smap(smap *x)
{
  free_agbox(x->agb);

  AM_FREE(x,smap);
}

void fprintf_smap(FILE *s,char *m1,smap *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> agb",m1);
  fprintf_agbox(s,buff,x->agb,m2);
  free_string(buff);

  buff = mk_printf("%s -> start_row",m1);
  fprintf_int(s,buff,x->start_row,m2);
  free_string(buff);

  buff = mk_printf("%s -> num_rows",m1);
  fprintf_int(s,buff,x->num_rows,m2);
  free_string(buff);
}

void psmap(smap *x)
{
  fprintf_smap(stdout,"smap",x,"\n");
}

smap *mk_copy_smap(smap *old)
{
  smap *nu = AM_MALLOC(smap);

  nu -> agb = mk_copy_agbox(old->agb);
  nu -> start_row = old -> start_row;
  nu -> num_rows = old -> num_rows;

  return nu;
}

double smap_top(smap *sm)
{
  return agbox_ref(sm->agb,YCOMP,HI);
}

double smap_bottom(smap *sm)
{
  return agbox_ref(sm->agb,YCOMP,LO);
}

double smap_height(smap *sm)
{
  return smap_top(sm) - smap_bottom(sm);
}

double smap_right(smap *sm)
{
  return agbox_ref(sm->agb,XCOMP,HI);
}

double smap_left(smap *sm)
{
  return agbox_ref(sm->agb,XCOMP,LO);
}

double smap_width(smap *sm)
{
  return smap_right(sm) - smap_left(sm);
}

int smap_max_row(smap *sm)
{
  return sm->start_row + sm->num_rows;
}

int agx_to_row(smap *sm,double agx)
{
  double z = ( agx - smap_left(sm) ) / smap_width(sm);
  double double_row = z * sm->num_rows + sm->start_row - 0.5;
  int row = (int) floor(0.5 + double_row);

  return row;
}

double row_to_agx(smap *sm,int row)
{
  double z = (0.5 + (row-sm->start_row)) / (double) sm->num_rows;
  double agx = smap_left(sm) + z * smap_width(sm);
  my_assert(agx_to_row(sm,agx) == row);
  return agx;
}

curveset *mk_curveset(int num_rows,string_array *cnum_to_name)
{
  curveset *x = AM_MALLOC(curveset);
  int num_curves = string_array_size(cnum_to_name);

  x -> row_cnum_to_height = mk_constant_dym(num_rows,num_curves,-7e77);
  x -> row_cnum_to_defined = mk_zero_imat(num_rows,num_curves);
  x -> cnum_to_name = mk_copy_string_array(cnum_to_name);
  x -> cnum_to_min_height = mk_constant_dyv(num_curves,-7.7e77);
  x -> cnum_to_max_height = mk_constant_dyv(num_curves,-7.7e77);
  x -> cnum_to_minmax_defined = mk_zero_ivec(num_curves);
  x -> max_sum_heights = 0.0;

  return x;
}

void free_curveset(curveset *x)
{
  free_dym(x->row_cnum_to_height);
  free_imat(x->row_cnum_to_defined);
  free_string_array(x->cnum_to_name);
  free_dyv(x->cnum_to_min_height);
  free_dyv(x->cnum_to_max_height);
  free_ivec(x->cnum_to_minmax_defined);

  AM_FREE(x,curveset);
}

void fprintf_curveset(FILE *s,char *m1,curveset *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> row_cnum_to_height",m1);
  fprintf_dym(s,buff,x->row_cnum_to_height,m2);
  free_string(buff);

  buff = mk_printf("%s -> row_cnum_to_defined",m1);
  fprintf_imat(s,buff,x->row_cnum_to_defined,m2);
  free_string(buff);

  buff = mk_printf("%s -> cnum_to_name",m1);
  fprintf_string_array(s,buff,x->cnum_to_name,m2);
  free_string(buff);

  buff = mk_printf("%s -> cnum_to_min_height",m1);
  fprintf_dyv(s,buff,x->cnum_to_min_height,m2);
  free_string(buff);

  buff = mk_printf("%s -> cnum_to_max_height",m1);
  fprintf_dyv(s,buff,x->cnum_to_max_height,m2);
  free_string(buff);

  buff = mk_printf("%s -> cnum_to_minmax_defined",m1);
  fprintf_ivec(s,buff,x->cnum_to_minmax_defined,m2);
  free_string(buff);

  buff = mk_printf("%s -> max_sum_heights",m1);
  fprintf_double(s,buff,x->max_sum_heights,m2);
  free_string(buff);
}

void pcurveset(curveset *x)
{
  fprintf_curveset(stdout,"curveset",x,"\n");
}

curveset *mk_copy_curveset(curveset *old)
{
  curveset *nu = AM_MALLOC(curveset);

  nu -> row_cnum_to_height = mk_copy_dym(old->row_cnum_to_height);
  nu -> row_cnum_to_defined = mk_copy_imat(old->row_cnum_to_defined);
  nu -> cnum_to_name = mk_copy_string_array(old->cnum_to_name);
  nu -> cnum_to_min_height = mk_copy_dyv(old->cnum_to_min_height);
  nu -> cnum_to_max_height = mk_copy_dyv(old->cnum_to_max_height);
  nu -> cnum_to_minmax_defined = mk_copy_ivec(old->cnum_to_minmax_defined);
  nu -> max_sum_heights = old -> max_sum_heights;

  return nu;
}

int curveset_num_curves(curveset *cs)
{
  return string_array_size(cs->cnum_to_name);
}

int curveset_num_rows(curveset *cs)
{
  return dym_rows(cs->row_cnum_to_height);
}

bool curveset_value_is_missing(curveset *cs,int row,int cnum)
{
  return imat_ref(cs->row_cnum_to_defined,row,cnum) == 0;
}

double curveset_ref(curveset *cs,int row,int cnum)
{
  my_assert(!curveset_value_is_missing(cs,row,cnum));
  return dym_ref(cs->row_cnum_to_height,row,cnum);
}

double curveset_cnum_to_max_height(curveset *cs,int cnum)
{
  my_assert(ivec_ref(cs->cnum_to_minmax_defined,cnum) != 0);
  return dyv_ref(cs->cnum_to_max_height,cnum);
}

double curveset_cnum_to_min_height(curveset *cs,int cnum)
{
  my_assert(ivec_ref(cs->cnum_to_minmax_defined,cnum) != 0);
  return dyv_ref(cs->cnum_to_min_height,cnum);
}

/* my_error if no curve with given name */
int curveset_name_to_cnum(curveset *cs,char *name)
{
  int result = find_index_in_string_array(cs->cnum_to_name,name);

  if ( result < 0 )
    my_errorf("You were looking for a plot called %s but the only\n"
	      "available plot names are: { %s }\n",
	      name,mk_string_from_string_array(cs->cnum_to_name));

  return result;
}

double curveset_max_sum_heights(curveset *cs)
{
  return cs -> max_sum_heights;
}

void curveset_set(curveset *cs,int row,int cnum,double height)
{
  double sum_heights = 0.0;
  int i;

  if ( !curveset_value_is_missing(cs,row,cnum) )
    my_error("You can only set a cell in the curveset once. You may not\n"
	     "redefine a value.\n");

  dym_set(cs->row_cnum_to_height,row,cnum,height);
  imat_set(cs->row_cnum_to_defined,row,cnum,1);

  if ( ivec_ref(cs->cnum_to_minmax_defined,cnum) == 0 )
  {
    dyv_set(cs->cnum_to_min_height,cnum,height);
    dyv_set(cs->cnum_to_max_height,cnum,height);
    ivec_set(cs->cnum_to_minmax_defined,cnum,1);
  }
  else
  {
    if ( height < dyv_ref(cs->cnum_to_min_height,cnum) )
      dyv_set(cs->cnum_to_min_height,cnum,height);
    if ( height > dyv_ref(cs->cnum_to_max_height,cnum) )
      dyv_set(cs->cnum_to_max_height,cnum,height);
  }

  for ( i = 0 ; i < curveset_num_curves(cs) ; i++ )
  {
    if ( !curveset_value_is_missing(cs,row,i) )
      sum_heights += curveset_ref(cs,row,i);
  }

  cs -> max_sum_heights = real_max(sum_heights,cs->max_sum_heights);
}


void render_bars(smap *sm,smap *key_sm,curveset *cs)
{
  agbox *agb = mk_copy_agbox(key_sm->agb);
  int row;
  print_color_key_and_shrink(agb,cs->cnum_to_name);
  for ( row = sm->start_row ; row < smap_max_row(sm) ; row++ )
  {
    double agx_prev = row_to_agx(sm,row-1);
    double agx_now = row_to_agx(sm,row);
    double agx_next = row_to_agx(sm,row+1);
    double x1 = real_max(smap_left(sm),0.45 * agx_prev + 0.55 * agx_now);
    double x2 = real_min(smap_right(sm),0.45 * agx_next+0.55 * agx_now);
    int cnum;
    double y = smap_bottom(sm);

    x2 = real_max(x1+1.0,x2);

    for ( cnum = 0 ; cnum < curveset_num_curves(cs) ; cnum++ )
    {
      double h = (curveset_value_is_missing(cs,row,cnum)) ? 0.0 : 
                 curveset_ref(cs,row,cnum);
      double ynext = y + h / curveset_max_sum_heights(cs) * smap_height(sm);
      ag_set_pen_color(color_code_to_ag_color(cnum));
      ag_box(x1,y,x2,ynext);
      y = ynext;
    }
  }
  free_agbox(agb);
}

#define NUM_EVENT_LEVELS 3

void render_events(smap *sm,sa_array *row_to_events)
{
  int level = 0;
  int row;

  for ( row = sm->start_row ; row < smap_max_row(sm) ; row++ )
  {
    string_array *sa = sa_array_ref(row_to_events,row);
    int i;
    for ( i = 0 ; i < string_array_size(sa) ; i++ )
    {
      char *s = string_array_ref(sa,i);
      double x = row_to_agx(sm,row);
      double y = smap_bottom(sm)+(level+0.5)*AG_CHAR_HEIGHT;

      ag_set_pen_color(AG_BLACK);

      ag_print(x+AG_CHAR_WIDTH,y,s);
      ag_line(x+AG_CHAR_WIDTH*0.75,y,x,y);
      ag_line(x,y,x,smap_bottom(sm));
      level = (level + 1) % NUM_EVENT_LEVELS;
    }
  }      
}

dyv *mk_cnum_to_max_height(curveset *cs,imat *same_scale_cnums)
{
  dyv *result = mk_copy_dyv(cs->cnum_to_max_height);
  bool finished = FALSE;
  int iters = 0;

  while ( !finished )
  {
    int i;

    finished = TRUE;

    for ( i = 0 ; i < imat_rows(same_scale_cnums) ; i++ )
    {
      int cnum1 = imat_ref(same_scale_cnums,i,0);
      int cnum2 = imat_ref(same_scale_cnums,i,1);
      double max1 = dyv_ref(result,cnum1);
      double max2 = dyv_ref(result,cnum2);

      if ( max1 != max2 )
      {
	double max12 = real_max(max1,max2);
	finished = FALSE;
	dyv_set(result,cnum1,max12);
	dyv_set(result,cnum2,max12);
      }
    }

    my_assert(iters <= imat_rows(same_scale_cnums));
    iters += 1;
  }

  return result;
}

dyv *mk_cnum_to_min_height(curveset *cs,imat *same_scale_cnums)
{
  dyv *result = mk_copy_dyv(cs->cnum_to_min_height);
  bool finished = FALSE;
  int iters = 0;

  while ( !finished )
  {
    int i;

    finished = TRUE;

    for ( i = 0 ; i < imat_rows(same_scale_cnums) ; i++ )
    {
      int cnum1 = imat_ref(same_scale_cnums,i,0);
      int cnum2 = imat_ref(same_scale_cnums,i,1);
      double min1 = dyv_ref(result,cnum1);
      double min2 = dyv_ref(result,cnum2);

      if ( min1 != min2 )
      {
	double min12 = real_min(min1,min2);
	finished = FALSE;
	dyv_set(result,cnum1,min12);
	dyv_set(result,cnum2,min12);
      }
    }

    my_assert(iters <= imat_rows(same_scale_cnums));
    iters += 1;
  }

  return result;
}

void render_plots(smap *sm,smap *key_sm,curveset *cs,imat *same_scale_cnums)
{
  int cnum;
  dyv *cnum_to_max_height = mk_cnum_to_max_height(cs,same_scale_cnums);
  dyv *cnum_to_min_height = mk_cnum_to_min_height(cs,same_scale_cnums);

  for ( cnum = 0 ; cnum < curveset_num_curves(cs) ; cnum++ )
  {
    bool prev_missing = TRUE;
    bool all_missing = TRUE;
    double prev_x = -77.7;
    double prev_y = -77.7;
    int row;

    ag_set_pen_color(color_code_to_ag_color(cnum));

    for ( row = sm->start_row ; row < smap_max_row(sm) ; row++ )
    {
      double x = row_to_agx(sm,row);

      if ( curveset_value_is_missing(cs,row,cnum) )
      {
	prev_missing = TRUE;
      }
      else
      {
	double height = curveset_ref(cs,row,cnum);
	double max_height = dyv_ref(cnum_to_max_height,cnum);
	double min_height = dyv_ref(cnum_to_min_height,cnum);
	double z1 = (height - min_height) / (max_height - min_height);
	double z2 = 0.05 + 0.9 *z1;
	double y = smap_bottom(sm) + z2 * smap_height(sm);

	if ( sm->num_rows < 100 )
	  ag_dot(x,y);
	else
	  ag_pixel(x,y);

	if ( !prev_missing )
	  ag_line(prev_x,prev_y,x,y);

	prev_missing = FALSE;
	prev_x = x;
	prev_y = y;
	all_missing = FALSE;
      }
    }

    if ( !all_missing )
    {
      ag_print(smap_left(key_sm)+0.5*AG_CHAR_WIDTH,prev_y,
	       string_array_ref(cs->cnum_to_name,cnum));
    }
  }	

  free_dyv(cnum_to_max_height);
  free_dyv(cnum_to_min_height);
}

void render_labels(smap *sm,string_array *row_to_label,double plots_top_y)
{
  double pixels_per_label = AG_CHAR_WIDTH * 10.0;
  double real_num_labels = smap_width(sm) / pixels_per_label;
  int num_labels = int_max(1,(int)floor(real_num_labels));
  int delta_row = (int) ceil(sm->num_rows / (double) num_labels);
  int row;
  int lightgray = AG_GRAYSCALE_BASE + 4 * NUM_GRAY_SCALES / 5;

  ag_set_pen_color(lightgray);
  ag_line(smap_left(sm),smap_top(sm),smap_right(sm),smap_top(sm));
  ag_line(smap_left(sm),plots_top_y,smap_right(sm),plots_top_y);

  ag_set_pen_color(AG_BLACK);

  if ( delta_row >= 5 && delta_row <= 8 )
    delta_row = 7;

  for ( row = sm->start_row ; row < smap_max_row(sm) ; row += 1 )
  {
    if ( row % delta_row == 0 )
    {
      char *label = string_array_ref(row_to_label,row);
      string_array *sa = mk_broken_string(label);
      int i;
      double x = row_to_agx(sm,row);
      double y = smap_top(sm)-AG_CHAR_HEIGHT;
    
      ag_set_pen_color(lightgray);

      ag_line(x+0.5 * AG_CHAR_WIDTH,y,x,y);
      ag_line(x,y,x,plots_top_y);

      ag_set_pen_color(AG_BLACK);

      for ( i = 0 ; i < string_array_size(sa) ; i++ )
      {
	double x1 = x + 0.8 * AG_CHAR_WIDTH;
	double y1 = y - (double)i * AG_CHAR_HEIGHT;
	ag_print(x1,y1,string_array_ref(sa,i));
      }

      free_string_array(sa);
    }
  }
}

splot *mk_splot(curveset *plots,string_array *row_to_label,
		curveset *bars,sa_array *row_to_events)
{
  splot *x = AM_MALLOC(splot);

  x -> plots = mk_copy_curveset(plots);
  x -> row_to_label = mk_copy_string_array(row_to_label);
  x -> bars = (bars==NULL) ? NULL : mk_copy_curveset(bars);
  x -> row_to_events = (row_to_events==NULL) ? NULL : 
                       mk_copy_sa_array(row_to_events);
  x -> same_scale_pnums = mk_imat(0,2);

  return x;
}

void free_splot(splot *x)
{
  free_curveset(x->plots);
  free_string_array(x->row_to_label);
  if ( x->bars != NULL ) free_curveset(x->bars);
  if ( x->row_to_events != NULL ) free_sa_array(x->row_to_events);
  free_imat(x->same_scale_pnums);

  AM_FREE(x,splot);
}

void fprintf_splot(FILE *s,char *m1,splot *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> plots",m1);
  fprintf_curveset(s,buff,x->plots,m2);
  free_string(buff);

  buff = mk_printf("%s -> row_to_label",m1);
  fprintf_string_array(s,buff,x->row_to_label,m2);
  free_string(buff);

  if ( x -> bars != NULL )
  {
    buff = mk_printf("%s -> bars",m1);
    fprintf_curveset(s,buff,x->bars,m2);
    free_string(buff);
  }

  if ( x -> row_to_events != NULL )
  {
    buff = mk_printf("%s -> row_to_events",m1);
    fprintf_sa_array(s,buff,x->row_to_events,m2);
    free_string(buff);
  }

  buff = mk_printf("%s -> same_scale_pnums",m1);
  fprintf_imat(s,buff,x->same_scale_pnums,m2);
  free_string(buff);
}

void psplot(splot *x)
{
  fprintf_splot(stdout,"splot",x,"\n");
}

splot *mk_copy_splot(splot *old)
{
  splot *nu = AM_MALLOC(splot);

  nu -> plots = mk_copy_curveset(old->plots);
  nu -> row_to_label = mk_copy_string_array(old->row_to_label);
  nu -> bars = (old->bars==NULL) ? NULL : mk_copy_curveset(old->bars);
  nu -> row_to_events = (old->row_to_events==NULL) ? NULL : 
                        mk_copy_sa_array(old->row_to_events);
  nu -> same_scale_pnums = mk_copy_imat(old->same_scale_pnums);

  return nu;
}

int splot_num_rows(splot *sp)
{
  return string_array_size(sp->row_to_label);
}

/*
                                             (image_width,total_height)
                                             |   (total_width,total_height)
                                             v   v
    +----------------------------------+-----+---+
    | bar key                          |     |   | barkey_height
    +----------------------------------+-----+---+
    | bars                             |     |   | bars_height
    |                                  |     |   |
    +----------------------------------+-----+---+ 
    | events                           |     |   | events_height
    |                                  |     |   |
    +----------------------------------+-----+---+ 
    | plots                            |plot |   | plots_height
    |                                  |key  |   |
    +----------------------------------+-----+---+ 
    | labels                           |     |   | labels_height
    |                                  |     |   |
    +----------------------------------+-----+---+
     (0,0)   labels_width             CK_WIDTH  ^
                                              MENU_WIDTH
*/

#define CK_WIDTH (12 * AG_CHAR_WIDTH)
#define MENU_WIDTH (12 * AG_CHAR_WIDTH)

void render_splot(splot *sp,int start_row,int num_rows)
{
  extern int Window_width;
  double total_width = (double) Window_width;
  extern int Window_height;
  double total_height = (double) Window_height;

  double barkey_height = (sp->bars==NULL) ? 0.0 : 2 * AG_CHAR_HEIGHT;
  double bars_height = (sp->bars==NULL) ? 0.0 : 4 * AG_CHAR_HEIGHT;
  double events_height = (sp->row_to_events==NULL) ? 0.0 : 
                         (NUM_EVENT_LEVELS+1) * AG_CHAR_HEIGHT;
  double labels_height = 4 * AG_CHAR_HEIGHT;
  double plots_height = total_height - barkey_height - bars_height - 
                         events_height - labels_height;
  double image_width = total_width - MENU_WIDTH;
  double labels_width = image_width - CK_WIDTH;

  double labels_top_y = labels_height;
  double plots_top_y = labels_top_y + plots_height;
  double events_top_y = plots_top_y + events_height;
  double bars_top_y = events_top_y + bars_height;

  smap *sm = NULL;
  smap *key_sm = NULL;

  set_ag_frame(0.0,0.0,total_width,total_height);

  if ( sp->bars != NULL )
  {
    key_sm = mk_smap(0.0,bars_top_y,labels_width,total_height,
		     start_row,num_rows);
    sm = mk_smap(0.0,events_top_y,labels_width,bars_top_y,
		 start_row,num_rows);

    render_bars(sm,key_sm,sp->bars);

    free_smap(sm);
    free_smap(key_sm);
    sm = NULL;
    key_sm = NULL;
  }

  if ( sp->row_to_events != NULL )
  {
    sm = mk_smap(0.0,plots_top_y,labels_width,events_top_y,
		 start_row,num_rows);

    render_events(sm,sp->row_to_events);
    free_smap(sm);
    sm = NULL;
  }

  sm = mk_smap(0.0,0.0,labels_width,labels_top_y,
	       start_row,num_rows);
  
  render_labels(sm,sp->row_to_label,plots_top_y);
  free_smap(sm);
  sm = NULL;

  sm = mk_smap(0.0,labels_top_y,labels_width,plots_top_y,
	       start_row,num_rows);
  
  key_sm = mk_smap(labels_width,labels_top_y,image_width,plots_top_y,
		   start_row,num_rows);

  render_plots(sm,key_sm,sp->plots,sp->same_scale_pnums);
  free_smap(sm);
  free_smap(key_sm);
  sm = NULL;
  key_sm = NULL;

}

void shrink_range(double *r_a,double *r_b)
{
  double w = 0.45 * (*r_b - *r_a);
  double m = (*r_a + *r_b)/2;

  *r_a = m - w;
  *r_b = m + w;
}

char *mouse_menu_choice(double agx_start,double agx_end,string_array *options,
			double *r_x,double *r_y,int *r_button)
{
  extern int Window_height;
  double total_height = (double) Window_height;
  int num_buttons = string_array_size(options);
  int i;
  char *result = NULL;

  for ( i = 0 ; i < num_buttons ; i++ )
  {
    double xlo = agx_start;
    double xhi = agx_end;
    double ylo = (1.0 - (i+1)/(double)num_buttons) * total_height;
    double yhi = (1.0 - i/(double)num_buttons) * total_height;
    int lightgray = AG_GRAYSCALE_BASE + 4 * NUM_GRAY_SCALES / 5;
    
    shrink_range(&xlo,&xhi);
    shrink_range(&ylo,&yhi);

    ag_set_pen_color(lightgray);

    ag_box(xlo,ylo,xhi,yhi);

    ag_set_pen_color(AG_BLACK);

    ag_rectangle(xlo,ylo,xhi,yhi);
    
    ag_print(xlo+AG_CHAR_WIDTH,(ylo+yhi)/2,string_array_ref(options,i));
  }

  *r_button = ag_get_xy(r_x,r_y);

  if ( *r_x >= agx_start && *r_x <= agx_end )
  {
    double z = 1.0 - *r_y / total_height;
    double real_choice = z * num_buttons;
    int choice = (int) floor(real_choice);
    result = string_array_ref(options,choice);
  }

  return result;
}

char *mk_splot_menu_choice(int start_row,int num_rows,
			   int *r_row,int *r_button)
{
  extern int Window_width;
  double total_width = (double) Window_width;
  double image_width = total_width - MENU_WIDTH;
  double labels_width = image_width - CK_WIDTH;
  double agx,agy;
  string_array *options = mk_broken_string("<---jump <---step "
					   "jump---> step---> "
					   "zoomout print quit");

  char *op = mouse_menu_choice(image_width,total_width,options,&agx,&agy,
			       r_button);

  char *result = mk_copy_string((op==NULL) ? "zoomin" : op);

  smap *sm = mk_smap(0.0,0.0,labels_width,1.0,start_row,num_rows);
  *r_row = agx_to_row(sm,agx);

  free_string_array(options);
  free_smap(sm);

  return result;
}

bopt *mk_bopt(char *psname,int count,string_array *hide_plotnames,
	      bool hide_bars,int initial_start_row,int initial_num_rows)
{
  bopt *x = AM_MALLOC(bopt);

  x -> psname = mk_copy_string(psname);
  x -> hide_plotnames = mk_copy_string_array(hide_plotnames);
  x -> hide_bars = hide_bars;
  x -> count = count;
  x -> initial_start_row = initial_start_row;
  x -> initial_num_rows = initial_num_rows;

  return x;
}

bopt *mk_bopt_from_args(int argc,char *argv[],
			int initial_start_row,int initial_num_rows)
{
  char *psname = string_from_args("psname",argc,argv,"splot");
  char *hide_string = string_from_args("hide",argc,argv,NULL);
  string_array *hide_plotnames = 
    (hide_string==NULL) ? mk_string_array(0) : mk_broken_string(hide_string);
  bool hide_bars = bool_from_args("hide_bars",argc,argv,FALSE);
  int count = 1;
  bopt *bo = mk_bopt(psname,count,hide_plotnames,hide_bars,initial_start_row,
		     initial_num_rows);
  free_string_array(hide_plotnames);
  return bo;
}

bopt *mk_default_bopt(splot *sp)
{
  char *psname = "splot";
  string_array *hide_plotnames = mk_string_array(0);
  bool hide_bars = FALSE;
  int count = 1;
  int initial_start_row = 0;
  int initial_num_rows = splot_num_rows(sp);

  bopt *bo = mk_bopt(psname,count,hide_plotnames,hide_bars,initial_start_row,
		     initial_num_rows);
  free_string_array(hide_plotnames);
  return bo;
}

void free_bopt(bopt *x)
{
  free_string(x->psname);
  free_string_array(x->hide_plotnames);

  AM_FREE(x,bopt);
}

void fprintf_bopt(FILE *s,char *m1,bopt *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> psname",m1);
  fprintf_string(s,buff,x->psname,m2);
  free_string(buff);

  buff = mk_printf("%s -> hide_plotnames",m1);
  fprintf_string_array(s,buff,x->hide_plotnames,m2);
  free_string(buff);

  buff = mk_printf("%s -> hide_bars",m1);
  fprintf_bool(s,buff,x->hide_bars,m2);
  free_string(buff);

  buff = mk_printf("%s -> count",m1);
  fprintf_int(s,buff,x->count,m2);
  free_string(buff);

  buff = mk_printf("%s -> initial_start_row",m1);
  fprintf_int(s,buff,x->initial_start_row,m2);
  free_string(buff);

  buff = mk_printf("%s -> initial_num_rows",m1);
  fprintf_int(s,buff,x->initial_num_rows,m2);
  free_string(buff);
}

void pbopt(bopt *x)
{
  fprintf_bopt(stdout,"bopt",x,"\n");
}

bopt *mk_copy_bopt(bopt *old)
{
  bopt *nu = AM_MALLOC(bopt);

  nu -> psname = mk_copy_string(old->psname);
  nu -> hide_plotnames = mk_copy_string_array(old->hide_plotnames);
  nu -> hide_bars = old -> hide_bars;
  nu -> count = old -> count;
  nu -> initial_start_row = old -> initial_start_row;
  nu -> initial_num_rows = old -> initial_num_rows;

  return nu;
}

int splot_num_plots(splot *sp)
{
  return curveset_num_curves(sp->plots);
}

int splot_num_bars(splot *sp)
{
  return curveset_num_curves(sp->bars);
}

ivec *mk_cnums_from_names(curveset *cs,string_array *names)
{
  int size = string_array_size(names);
  ivec *result = mk_ivec(size);
  int i;

  for ( i = 0 ; i < size ; i++ )
  {
    char *name = string_array_ref(names,i);
    int cnum = curveset_name_to_cnum(cs,name);
    if ( cnum < 0 )
      my_errorf("No curve in plot called %s\n",name);
    ivec_set(result,i,cnum);
  }

  return result;
}

ivec *mk_plotnums_from_plotnames(splot *sp,string_array *names)
{
  return mk_cnums_from_names(sp->plots,names);
}

double splot_row_barnum_to_value(splot *sp,int row,int barnum)
{
  return curveset_ref(sp->bars,row,barnum);
}

double splot_row_plotnum_to_value(splot *sp,int row,int plotnum)
{
  return curveset_ref(sp->plots,row,plotnum);
}

string_array *splot_row_to_events(splot *sp,int row)
{
  return sa_array_ref(sp->row_to_events,row);
}

char *splot_row_to_label(splot *sp,int row)
{
  return string_array_ref(sp->row_to_label,row);
}

ivec *mk_use_plotnums(splot *sp,bopt *bo)
{
  ivec *all_plotnums = mk_identity_ivec(splot_num_plots(sp));
  ivec *hide_plotnums = mk_plotnums_from_plotnames(sp,bo->hide_plotnames);
  ivec *siv = mk_sivec_from_ivec(hide_plotnums);
  ivec *use_plotnums = mk_sivec_difference(all_plotnums,siv);

  free_ivec(all_plotnums);
  free_ivec(hide_plotnums);
  free_ivec(siv);

  return use_plotnums;
}

splot *mk_editted_splot(splot *in_sp,bopt *bo)
{
  ivec *use_plotnums = mk_use_plotnums(in_sp,bo);
  string_array *use_plotnames = 
    mk_string_array_subset(in_sp->plots->cnum_to_name,use_plotnums);
  bool use_bars = !bo->hide_bars && in_sp->bars != NULL;
  int num_rows = splot_num_rows(in_sp);
  splot *out_sp = mk_empty_splot(num_rows,use_plotnames,
				 (use_bars)?in_sp->bars->cnum_to_name:NULL,
				 in_sp->row_to_events != NULL);
  int row;
  int i;

  for ( row = 0 ; row < num_rows ; row++ )
  {
    for ( i = 0 ; i < ivec_size(use_plotnums) ; i++ )
    {
      int in_plotnum = ivec_ref(use_plotnums,i);
      double value = splot_row_plotnum_to_value(in_sp,row,in_plotnum);

      splot_plots_set(out_sp,row,i,value);
    }

    if ( use_bars )
    {
      for ( i = 0 ; i < splot_num_bars(in_sp) ; i++ )
      {
	double value = splot_row_barnum_to_value(in_sp,row,i);
	splot_bars_set(out_sp,row,i,value);
      }
    }

    if ( in_sp -> row_to_events != NULL )
    {
      string_array *events = splot_row_to_events(in_sp,row);
      for ( i = 0 ; i < string_array_size(events) ; i++ )
	splot_add_event(out_sp,row,string_array_ref(events,i));
    }

    splot_label_set(out_sp,row,splot_row_to_label(in_sp,row));
  }

  free_imat(out_sp->same_scale_pnums);
  out_sp -> same_scale_pnums = mk_imat(0,2);

  for ( i = 0 ; i < imat_rows(in_sp->same_scale_pnums) ; i++ )
  {
    int in_pnum0 = imat_ref(in_sp->same_scale_pnums,i,0);
    int out_pnum0 = find_index_in_ivec(use_plotnums,in_pnum0);
    int in_pnum1 = imat_ref(in_sp->same_scale_pnums,i,1);
    int out_pnum1 = find_index_in_ivec(use_plotnums,in_pnum1);

    if ( out_pnum0 >= 0 )
    {
      char *in_name = string_array_ref(in_sp->plots->cnum_to_name,in_pnum0);
      char *out_name = string_array_ref(out_sp->plots->cnum_to_name,out_pnum0);
      my_assert_always(eq_string(in_name,out_name));
    }

    if ( out_pnum1 >= 0 )
    {
      char *in_name = string_array_ref(in_sp->plots->cnum_to_name,in_pnum1);
      char *out_name = string_array_ref(out_sp->plots->cnum_to_name,out_pnum1);
      my_assert_always(eq_string(in_name,out_name));
    }

    if ( out_pnum0 >= 0 && out_pnum1 >= 0 )
    {
      int new_row = imat_rows(out_sp->same_scale_pnums);
      imat_add_row(out_sp->same_scale_pnums);
      imat_set(out_sp->same_scale_pnums,new_row,0,out_pnum0);
      imat_set(out_sp->same_scale_pnums,new_row,1,out_pnum1);
    }
  }

  free_ivec(use_plotnums);
  free_string_array(use_plotnames);

  return out_sp;
}

/* bo may be NULL meaning "use all default options */
void browse_splot(splot *input_sp,bopt *input_bo)
{
  bopt *bo = (input_bo == NULL) ? mk_default_bopt(input_sp) : 
                                  mk_copy_bopt(input_bo);
  splot *sp = mk_editted_splot(input_sp,bo);
 int start_row = bo -> initial_start_row;
  int num_rows = bo -> initial_num_rows;
  bool finished = FALSE;

  while ( !finished )
  {
    int button,row;
    int rows_on_left = int_max(1,num_rows/10);
    char *option;

    printf("Start_row = %d, num_rows = %d\n",start_row,num_rows);

    ag_on("");
    render_splot(sp,start_row,num_rows);

    printf("Click on plot to ZOOM IN\n");
    printf("Else click on menu\n");

    option = mk_splot_menu_choice(start_row,num_rows,&row,&button);
    ag_off();

    printf("option = %s\n",option);

    if ( eq_string(option,"<---jump") )
    {
      printf("Moving left by %d row(s)\n",rows_on_left);
      start_row = int_max(0,start_row - rows_on_left);
      my_assert_always(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"<---step") )
    {
      rows_on_left = 1;
      printf("Moving left by %d row(s)\n",rows_on_left);
      start_row = int_max(0,start_row - rows_on_left);
      my_assert(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"jump--->") )
    {
      printf("Moving right by %d row(s)\n",rows_on_left);
      start_row = int_min(splot_num_rows(sp) - num_rows,
			  start_row + rows_on_left);
      my_assert(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"step--->") )
    {
      rows_on_left = 1;
      printf("Moving right by %d row(s)\n",rows_on_left);
      start_row = int_min(splot_num_rows(sp) - num_rows,
			  start_row + rows_on_left);
      my_assert(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"zoomin") )
    {
      printf("Zooming in\n");
      num_rows = int_max(1,(3 * num_rows)/4);
      start_row = row - num_rows/2;
      start_row = int_max(0,start_row);
      start_row = int_min(start_row,splot_num_rows(sp)-num_rows);
      my_assert(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"zoomout") )
    {
      start_row -= num_rows/2;
      num_rows *= 2;
      num_rows = int_min(splot_num_rows(sp),num_rows);
      start_row = int_max(0,start_row);
      start_row = int_min(start_row,splot_num_rows(sp)-num_rows);

      my_assert(start_row >= 0 && start_row+num_rows <= splot_num_rows(sp));
    }
    else if ( eq_string(option,"print") )
    {
      char *filename = mk_printf("%s-%d.ps",bo->psname,bo->count);
      ag_on(filename);
      render_splot(sp,start_row,num_rows);
      ag_off();
      printf("Saved series in file %s\n",filename);
      bo->count += 1;
      free_string(filename);
    }
    else if ( eq_string(option,"quit") )
      finished = TRUE;
    else
      printf("Ignoring unrecognized command %s\n",option);

    free_string(option);
  }
  free_bopt(bo);
  free_splot(sp);
}

/*********** splot construction **************/

splot *mk_empty_splot(int num_rows,string_array *plot_to_name,
		      string_array *barsegment_to_name, /* Maybe NULL meaning
                                                           "no bars" */
		      bool use_events)
{
  curveset *plots = mk_curveset(num_rows,plot_to_name);
  string_array *row_to_label = mk_constant_string_array(num_rows,"?");
  curveset *bars = (barsegment_to_name==NULL) ? NULL :
                   mk_curveset(num_rows,barsegment_to_name);
  sa_array *row_to_events = (!use_events) ? NULL :
                            mk_array_of_zero_length_string_arrays(num_rows);
  splot *sp = mk_splot(plots,row_to_label,bars,row_to_events);

  free_curveset(plots);
  free_string_array(row_to_label);
  if ( bars != NULL ) free_curveset(bars);
  if ( row_to_events != NULL ) free_sa_array(row_to_events);

  return sp;
}
  
void splot_plots_set(splot *sp,int row,int plot_line_number,double value)
{
  curveset_set(sp->plots,row,plot_line_number,value);
}

void splot_bars_set(splot *sp,int row,int bar_segment_number,double value)
{
  if ( sp->bars == NULL )
    my_error("splot_bars_set: You cannot set a bar segment because your\n"
	     "splot structure does not have bars defined");
  curveset_set(sp->bars,row,bar_segment_number,value);
}

void splot_label_set(splot *sp,int row,char *label)
{
  string_array_set(sp->row_to_label,row,label);
}

void splot_add_event(splot *sp,int row,char *event_name)
{
  string_array *events;

  if ( sp->row_to_events == NULL )
    my_error("splot_add_event: You cannot add an event because your\n"
	     "splot structure does not have events defined");

  events = sa_array_ref(sp->row_to_events,row);
  add_to_string_array(events,event_name);
}

void splot_plots_same_scale(splot *sp,int pnum1,int pnum2)
{
  int imrow = imat_rows(sp->same_scale_pnums);
  imat_add_row(sp->same_scale_pnums);
  imat_set(sp->same_scale_pnums,imrow,0,pnum1);
  imat_set(sp->same_scale_pnums,imrow,1,pnum2);
}

int splot_name_to_pnum(splot *sp,char *name)
{
  return curveset_name_to_cnum(sp->plots,name);
}

void splot_plots_same_scale_using_names(splot *sp,char *name,
					char *same_scale_name)
{
  int pnum1 = splot_name_to_pnum(sp,name);
  int pnum2 = splot_name_to_pnum(sp,same_scale_name);
  splot_plots_same_scale(sp,pnum1,pnum2);
}

void splot_plots_set_using_name(splot *sp,int row,char *name,double value)
{
  int pnum = splot_name_to_pnum(sp,name);
  splot_plots_set(sp,row,pnum,value);
}

