#define	GRAPH_VERBOSE
/*
 * Copyright © 1993, 1999, 2000, 2001 Free Software Foundation, Inc.
 * 
 * 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, 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 software; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * $Id: graph.c,v 1.22 2001/02/13 23:38:05 danny Exp $
 *
 * This file contains the functions to maintain the internal graphing
 * data structures in Oleo.
 */

#undef	GRAPH_VERBOSE

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef	WITH_DMALLOC
#include <dstdlib.h>
#endif

#include <ctype.h>
#include <stdlib.h>

#include "global.h"
#include "graph.h"
#include "cmd.h"
#include "line.h"
#include "io-term.h"
#include "info.h"
#include "cell.h"
#include "regions.h"
#include "ref.h"
#include "io-utils.h"

char * graph_axis_name [graph_num_axis] =
{
  "x", "y"
};

static char * graph_plot_styles [] =
{
  "lines",
  "points",
  "linespoints",
  "impulses",
  "dots",
   0
};

int graph_ornt_row_magic[graph_num_pair_orientations] = { 0, 1, 0 };
int graph_ornt_col_magic[graph_num_pair_orientations] = { 1, 0, 0 };

enum graph_axis
chr_to_axis (int c)
{
  if (isupper (c))
    c = tolower (c);
  switch (c)
    {
    case 'x':
      return graph_x;
    case 'y':
      return graph_y;
    }
  io_error_msg ("Unkown graph-axis `%c' (try `x' or `y')", c);
  return graph_x;		/* not reached, actualy. */
}

char *
line_to_q_char (struct line line)
{
  static struct line tmp_line;
  char *str;
  
  str = line.buf;

  set_line (&tmp_line, "\"");
  while (*str)
    {
      switch (*str)
	{
	case '"':
/* gnuplot doesn't like escaped quotation marks in titles anyway.
 * If you want to support them, you'll have to protect against
 * progressive buildup, since this stores the post-escaping string
 * back into the original char.
 */
/*        catn_line (&tmp_line, "\\\"", 2); */
	  break;
	default:
	  catn_line (&tmp_line, str, 1);
	  break;
	}
      ++str;
    }
  catn_line (&tmp_line, "\"", 1);

  set_line(&line, tmp_line.buf);

  return line.buf;
}

char *
char_to_q_char (char *str)
{
  char *tmp;

  static struct line tmp_line;
  set_line (&tmp_line, str);
  tmp = line_to_q_char (tmp_line);
  return (tmp);
}

void
graph_set_axis_title (int axis_c, char * title)
{
  enum graph_axis axis = chr_to_axis (axis_c);
#ifdef	GRAPH_VERBOSE
  fprintf(stderr, "graph_set_axis_title(%c,%s)\n", axis_c, title);
#endif
  set_line (&Global->PlotGlobal->graph_axis_title [axis], char_to_q_char (title));
}

char *
graph_get_axis_title (int axis_c)
{
  enum graph_axis axis = chr_to_axis (axis_c);
  return Global->PlotGlobal->graph_axis_title[axis].buf;
}

void
graph_set_logness (int axis_c, int explicit, int newval)
{
  enum graph_axis axis = chr_to_axis (axis_c);
  static struct line msg_buf;

  if (!explicit)
    newval = !Global->PlotGlobal->graph_logness [axis];
  else
    newval = (newval > 0);

  Global->PlotGlobal->graph_logness [axis] = newval;
  sprint_line (&msg_buf, "%slogarithmic %s%s",
	       ((Global->PlotGlobal->graph_logness [graph_x] || Global->PlotGlobal->graph_logness [graph_y])
		? "" : "no"),
	       Global->PlotGlobal->graph_logness[graph_x] ? "x" : "",
	       Global->PlotGlobal->graph_logness[graph_y] ? "y" : "");
#if 0
  io_info_msg ("set %s", msg_buf.buf);
#endif
}

int
graph_get_logness(int axis_c)
{
  enum graph_axis axis = chr_to_axis (axis_c);
  return Global->PlotGlobal->graph_logness [axis];
}

void
graph_check_range (char * val)
{
  if (says_default (val))
    return;
  while (isspace (*val)) ++val;
  if (*val == '-') ++val;
  while (isspace (*val)) ++val;
  if (!isdigit (*val))
    io_error_msg
      ("Illegal range specifier %s (should be a number or `def').", val);
  else
    {
      while (*val)
	if (isspace (*val) || isdigit (*val))
	  ++val;
	else
	  break;
      while (isspace (*val)) ++val;
      if (*val == '.') ++val;
      while (*val)
	if (isspace (*val) || isdigit (*val))
	  ++val;
	else
	  io_error_msg
	    ("Illegal range specifier %s (should be a number or `def').", val);
    }
	    
}


void
graph_set_axis_lo (int axis_c, char * val)
{
  enum graph_axis axis = chr_to_axis (axis_c);
#ifdef	GRAPH_VERBOSE
  fprintf(stderr, "graph_set_axis_lo(%c,%s)\n", axis_c, val);
#endif
  graph_check_range (val);
  set_line (&Global->PlotGlobal->graph_rng_lo [axis], val);
  Global->PlotGlobal->XYMin[axis] = atof(val);
  Global->PlotGlobal->graph_axis_symbols [axis].lr = NON_ROW;
}


void
graph_set_axis_hi (int axis_c, char * val)
{
  enum graph_axis axis = chr_to_axis (axis_c);
#ifdef	GRAPH_VERBOSE
  fprintf(stderr, "graph_set_axis_hi(%c,%s)\n", axis_c, val);
#endif
  graph_check_range (val);
  set_line (&Global->PlotGlobal->graph_rng_hi [axis], val);
  Global->PlotGlobal->XYMax[axis] = atof(val);
  Global->PlotGlobal->graph_axis_symbols [axis].lr = NON_ROW;
}

void
graph_default_axis_labels (int axis_c)
{
  enum graph_axis axis = chr_to_axis (axis_c);
  Global->PlotGlobal->graph_axis_labels [axis].lr = NON_ROW;
}

int
graph_check_style (char * name)
{
  int x =
    words_member (graph_plot_styles, parray_len (graph_plot_styles), name);
  if (x < 0)
    io_error_msg
      ("Invalid line style %s. (Try M-x describe-function set-graph-style).");
  return x;
}


void
graph_set_style(int data_set, char * style)
{
  if ((data_set < 0) || (data_set >= NUM_DATASETS))
    io_error_msg
      ("set-graph-style -- data-set out of range: %d (should be in [0..%d])",
       data_set, NUM_DATASETS);
#if 0
  {
  int x = graph_check_style (style);
  set_line (&Global->PlotGlobal->graph_style[data_set], graph_plot_styles [x]);
  }
#endif
}

void
graph_set_data_title (int data_set, char * title)
{
  if ((data_set < 0) || (data_set >= NUM_DATASETS))
    io_error_msg
      ("set-graph-title -- data-set out of range: %d (should be in [0..%d])",
       data_set, NUM_DATASETS);
#ifdef	GRAPH_VERBOSE
  fprintf(stderr, "graph_set_data_title(%d,%s)\n", data_set, title);
#endif
  set_line (&Global->PlotGlobal->graph_title [data_set], title);
}

char *
graph_get_data_title(int data_set)
{
  if ((data_set < 0) || (data_set >= NUM_DATASETS))
    return NULL;
  return Global->PlotGlobal->graph_title[data_set].buf;
}

void
plotutils_set_data(int data_set, struct rng *rng)
{
  if ((data_set < 0) || (data_set >= NUM_DATASETS))
    io_error_msg
      ("set-graph-data -- data-set out of range: %d (should be in [0..%d])",
       data_set, NUM_DATASETS);
  Global->PlotGlobal->graph_data[data_set] = *rng;
}

void
graph_set_data(int data_set, struct rng * rng)
{
  if ((data_set < 0) || (data_set >= NUM_DATASETS))
    io_error_msg
      ("set-graph-data -- data-set out of range: %d (should be in [0..%d])",
       data_set, NUM_DATASETS);
  Global->PlotGlobal->graph_data[data_set] = *rng;
}

struct rng
graph_get_data(int data_set)
{
	return Global->PlotGlobal->graph_data[data_set];
}


void
graph_presets (void)
{
  enum graph_axis axis;

  if (using_curses) {
	if (getenv("DISPLAY")) {
		/*
		 * We're using curses in an X environment, let's
		 * default to Tektronix on stdout.
		 */
		plotutils_tek();
		plotutils_set_filename("-");
	} else {
		graph_postscript ("#plot.ps", 'd', 'm', "TimesRoman", 12);
	}
  } else {
#if 0
    graph_x11_mono ();
#endif
  }

    for (axis = graph_x; axis < graph_num_axis; ++axis)
      {
	int axis_c = graph_axis_name [axis][0];
	Global->PlotGlobal->graph_logness [axis] = 0;
	graph_set_axis_title (axis_c, "");
	graph_set_axis_lo (axis_c, "def");
	graph_set_axis_hi (axis_c, "def");
	graph_set_axis_title (axis_c, "");
	graph_default_axis_labels (axis_c);
      }
}

void
graph_clear_datasets (void)
{
  int x;
  for (x = 0; x < NUM_DATASETS; ++x)
    {
      graph_set_style(x, "lines");
      graph_set_data_title(x, "");
      Global->PlotGlobal->graph_data[x].lr = NON_ROW;
    }
}


void
init_graphing (void)
{
  PlotInit();

  graph_presets ();
  graph_clear_datasets ();

  XYxAuto = XYyAuto = 1;
}

void
for_pairs_in (struct rng * rng, enum graph_pair_ordering order, fpi_thunk thunk, void * frame)
{
  CELLREF r;
  CELLREF c;
  enum graph_pair_orientation ornt = order % graph_num_pair_orientations;
  enum graph_ordering dir = ORDER_OF_PAIRS(order);
  int r_inc = 1 + graph_ornt_row_magic [ornt];
  int c_inc = 1 + graph_ornt_col_magic [ornt];
  if (dir == graph_rows)
    {
      r = rng->lr;
      while (1)
	{
	  c = rng->lc;
	  while (1)
	    {
	      CELLREF y_r = r + graph_ornt_row_magic [ornt];
	      CELLREF y_c = c + graph_ornt_col_magic [ornt];
	      CELL * cell = find_cell (y_r, y_c);
	      thunk (frame, cell, y_r, y_c);
	      if ((rng->hc - c) < c_inc)
		break;
	      c += c_inc;
	    }
	  if ((rng->hr - r) < r_inc)
	    break;
	  r += r_inc;
	}
    }
  else
    {
      c = rng->lc;
      while (1)
	{
	  r = rng->lr;
	  while (1)
	    {
	      CELLREF y_r = r + graph_ornt_row_magic [ornt];
	      CELLREF y_c = c + graph_ornt_col_magic [ornt];
	      CELL * cell = find_cell (y_r, y_c);
	      thunk (frame, cell, y_r, y_c);
	      if ((rng->hr - r) < r_inc)
		break;
	      r += r_inc;
	    }
	  if ((rng->hc - c) < c_inc)
	    break;
	  c += c_inc;
	}
    }
}

static char *graph_plot_title = NULL;

void
graph_set_title(char *t)
{
	if (graph_plot_title)
		free(graph_plot_title);
	graph_plot_title = strdup(t);
#ifdef	GRAPH_VERBOSE
	fprintf(stderr, "graph_set_title(%s)\n", t);
#endif
}

char *
graph_get_title(void)
{
	return	graph_plot_title;
}

void
graph_set_axis_auto(int axis, int set)
{
	if (axis == 0  || axis == 1)
		Global->PlotGlobal->XYAuto[axis] = set;
	else {
		io_error_msg("Invalid graph axis %d, must be 0 or 1", axis);
	}
#ifdef	GRAPH_VERBOSE
	fprintf(stderr, "graph_set_axis_auto(%d,%d)\n", axis, set);
#endif
}

int
graph_get_axis_auto(int axis)
{
	if (axis == 0  || axis == 1)
		return Global->PlotGlobal->XYAuto[axis];

	io_error_msg("Invalid graph axis %d, must be 0 or 1", axis);
	return 1;
}

double
graph_get_axis_lo(int axis)
{
	if (axis < 0 || axis > graph_num_axis) {
		io_error_msg("graph_get_axis_lo : invalid graph axis %d, must be 0 or 1", axis);
		return 0.0;
	}
	return Global->PlotGlobal->XYMin[axis];
}

double
graph_get_axis_hi(int axis)
{
	if (axis < 0 || axis > graph_num_axis) {
		io_error_msg("graph_get_axis_hi: invalid graph axis %d, must be 0 or 1", axis);
		return 0.0;
	}
	return Global->PlotGlobal->XYMax[axis];
}

void
graph_set_linetooffscreen(int set)
{
	Global->PlotGlobal->LineToOffscreen = set;
#ifdef	GRAPH_VERBOSE
	fprintf(stderr, "graph_set_linetooffscreen(%d)\n", set);
#endif
}

int
graph_get_linetooffscreen(void)
{
	return Global->PlotGlobal->LineToOffscreen;
}

/*
 * Axis tick marks
 */
void
graph_set_axis_ticks(int axis, int ticktype, char *fmt)
{
#ifdef	GRAPH_VERBOSE
	fprintf(stderr, "graph_set_axis_ticks(%d, %d, %s)\n", axis, ticktype, fmt);
#endif

	if (axis < 0 || axis > graph_num_axis) {
		io_error_msg("graph_set_axis_ticks: invalid graph axis %d, must be 0 or 1", axis);
		return;
	}

	Global->PlotGlobal->ticktype[axis] = ticktype;
	Global->PlotGlobal->tickformat[axis] = fmt;
}

int
graph_get_axis_ticktype(int axis)
{
	if (axis < 0 || axis > graph_num_axis) {
		io_error_msg("graph_get_axis_ticktype: invalid graph axis %d, must be 0 or 1", axis);
		return 0;
	}

	return Global->PlotGlobal->ticktype[axis];
}

char *
graph_get_axis_tickformat(int axis)
{
	if (axis < 0 || axis > graph_num_axis) {
		io_error_msg("graph_get_axis_tickformat: invalid graph axis %d, must be 0 or 1", axis);
		return 0;
	}

	return Global->PlotGlobal->tickformat[axis];
}

void
graph_postscript (char * file, int kind, int spectrum, char * font, int pt_size)
{
  if (isupper (kind))
    kind = tolower (kind);
  if (isupper (spectrum))
    spectrum = tolower (spectrum);
  if (!index ("led", kind))
    io_error_msg
      ("Incorrect postscript graph type %c (should be one of l, e, or c)",
       kind);
  if (!index ("mc", spectrum))
    io_error_msg
      ("Incorrect postscript color type %c (should be either m or c)",
       spectrum);
#if 0
  sprint_line (&graph_term_cmd,
               "postscript %c %c %s %d  # Postscript",
               kind, spectrum, char_to_q_char (font), pt_size);
  set_line (&graph_output_file, file);
#endif
}

/*
 * Show a GNU Plotutils chart.
 *
 * In the Motif interface, ...
 * The client parameter is the function (from plot.c) which will
 * handle the plotting. We pass it to RedrawPlotutilsWindow() too.
 */
void
graph_plot(void)
{
#ifdef	HAVE_LIBPLOT
	FILE	*fp = NULL;
	int	havepipe = 0;

	if (Global->PlotGlobal->output_file) {
	    /* Treat stdout ("-") case */
	    if (strcmp(Global->PlotGlobal->output_file, "-") == 0)
		fp = stdout;
	    else if (Global->PlotGlobal->output_file[0] == '|') {
		/* FIX ME treat pipe */
		havepipe = 1;
	    } else {
		fp = fopen(Global->PlotGlobal->output_file, "w");
	    }
	}

	if (fp == NULL) {
		io_error_msg("Cannot open file '%s'", Global->PlotGlobal->output_file);
		return;
	}

	PuPlot(Global->PlotGlobal->graph_type, Global->PlotGlobal->device, fp);

	if (havepipe) {
		/* FIX ME */
	} else if (fp == stdout && using_curses) {
		;	/* Don't do a thing, curses would die. */
	} else {
		fclose(fp);
	}
#endif
}

void
plotutils_set_device(enum graph_device d)
{
	Global->PlotGlobal->device = d;
}

void
plotutils_metaplot(void)
{
	Global->PlotGlobal->device = GRAPH_METAFILE;
}

void
plotutils_illustrator(void)
{
	Global->PlotGlobal->device = GRAPH_ILLUSTRATOR;
}

void
plotutils_fig(void)
{
	Global->PlotGlobal->device = GRAPH_FIG;
}

void
plotutils_x_mono(void)
{
	Global->PlotGlobal->device = GRAPH_X_MONO;
}

void
plotutils_x_color(void)
{
	Global->PlotGlobal->device = GRAPH_X;
}

void
plotutils_png(void)
{
	Global->PlotGlobal->device = GRAPH_PNG;
}

void
plotutils_gif(void)
{
	Global->PlotGlobal->device = GRAPH_GIF;
}

void
plotutils_pcl(void)
{
	Global->PlotGlobal->device = GRAPH_PCL;
}

void
plotutils_hpgl(void)
{
	Global->PlotGlobal->device = GRAPH_HPGL;
}

void
plotutils_tek(void)
{
	Global->PlotGlobal->device = GRAPH_TEK;
}

void
plotutils_regis(void)
{
	Global->PlotGlobal->device = GRAPH_REGIS;
}

void
plotutils_make_info(void)
{
#ifdef	HAVE_LIBPLOT
  struct info_buffer	*ib = find_or_make_info ("graphing-parameters");
  enum graph_axis	axis;
  int			x;

  clear_info (ib);

  print_info(ib, "");
  switch (Global->PlotGlobal->graph_type) {
	case GRAPH_XY:
		print_info(ib, "Graph type is XY plot.");
		break;
	case GRAPH_BAR:
		print_info(ib, "Graph type is BAR chart.");
		break;
	case GRAPH_PIE:
		print_info(ib, "Graph type is PIE chart.");
		break;
  }
  print_info(ib, "");
  print_info(ib,
	"Parameter		Value");
  print_info(ib, "");
  switch (Global->PlotGlobal->device) {
  case GRAPH_TEK:
    print_info(ib, "output type		%s", "Tektronix");
    break;
  case GRAPH_X:
  case GRAPH_X_MONO:
    print_info(ib, "output type		%s", "X window");
    break;
  case GRAPH_PNG:
    print_info(ib, "output type		%s", "PNG");
    break;
  case GRAPH_GIF:
    print_info(ib, "output type		%s", "GIF");
    break;
  case GRAPH_METAFILE:
    print_info(ib, "output type		%s", "GNU metafile");
    break;
  case GRAPH_ILLUSTRATOR:
    print_info(ib, "output type		%s", "Adobe Illustrator");
    break;
  case GRAPH_FIG:
    print_info(ib, "output type		%s", "FIG");
    break;
  case GRAPH_PCL:
    print_info(ib, "output type		%s", "PCL");
    break;
  case GRAPH_HPGL:
    print_info(ib, "output type		%s", "HP GL");
    break;
  case GRAPH_POSTSCRIPT:
    print_info(ib, "output type		%s", "PostScript");
    break;
  case GRAPH_REGIS:
    print_info(ib, "output type         %s", "ReGIS");
    break;
  case GRAPH_NONE:
  default:
    print_info(ib, "output type		???");
    break;
  };

  if (Global->PlotGlobal->output_file)
    print_info(ib,
       "output file		%s",
	Global->PlotGlobal->output_file);
  
  for (axis = graph_x; axis <= graph_y; ++axis)
    print_info(ib,
       "%s-axis title		%s",
       graph_axis_name[axis], Global->PlotGlobal->graph_axis_title[axis].buf);

  print_info(ib,
       "logarithmic axes	%s",
       (Global->PlotGlobal->graph_logness[graph_x]
	? (Global->PlotGlobal->graph_logness[graph_y] ? "x,y" : "x")
	: (Global->PlotGlobal->graph_logness[graph_y] ? "y" : "-neither-")));

  for (axis = graph_x; axis <= graph_y; ++axis) {
	print_info(ib,
		"%s-axis range		[%s..%s]",
	   graph_axis_name [axis],
	   Global->PlotGlobal->graph_rng_lo[axis].buf, Global->PlotGlobal->graph_rng_hi[axis].buf,
	   Global->PlotGlobal->graph_rng_hi[axis].buf, Global->PlotGlobal->graph_rng_hi[axis].buf);
    }

  for (axis = graph_x; axis <= graph_y; ++axis) {
      if (Global->PlotGlobal->graph_axis_labels[axis].lr != NON_ROW)
	print_info(ib,
	   	"%s-axis labels		%s",
	   graph_axis_name[axis],
	   range_name(&Global->PlotGlobal->graph_axis_labels[axis]));
    }

    for (x = 0; x < NUM_DATASETS; ++x)
      if (Global->PlotGlobal->graph_data [x].lr != NON_ROW)
      {
	print_info (ib, "");
	print_info (ib,"Data Set %d%s%s",
		    x,
		    Global->PlotGlobal->graph_title[x].buf[0] ? " entitled " : "",
		    Global->PlotGlobal->graph_title[x].buf);
	print_info (ib,"  data for this set: %s",
		    range_name (&Global->PlotGlobal->graph_data[x]));
	print_info (ib,"  style for this set: %s",
		    (Global->PlotGlobal->style[x] == GRAPH_STYLE_DEFAULT) ? "default" :
		    (Global->PlotGlobal->style[x] == GRAPH_STYLE_LINES) ? "lines" :
		    (Global->PlotGlobal->style[x] == GRAPH_STYLE_MARKS) ? "marks" :
		    (Global->PlotGlobal->style[x] == GRAPH_STYLE_BOTH) ? "both" :
		    (Global->PlotGlobal->style[x] == GRAPH_STYLE_NONE) ? "none" : "???"
		);
	print_info (ib,"");
      }
#endif
}

void
plotutils_set_axis_labels(int axis_c, struct rng * rng)
{
  enum graph_axis axis = chr_to_axis (axis_c);
  Global->PlotGlobal->graph_axis_labels [axis] = *rng;
}

void
plotutils_set_filename(char *file)
{
  if (Global->PlotGlobal->output_file)
	free(Global->PlotGlobal->output_file);
  Global->PlotGlobal->output_file = strdup(file);
}

void plotutils_xy(void)
{
	Global->PlotGlobal->graph_type = GRAPH_XY;
	io_info_msg("Graph type is now XY chart");
}

void plotutils_pie(void)
{
	Global->PlotGlobal->graph_type = GRAPH_PIE;
	io_info_msg("Graph type is now PIE chart");
}

void plotutils_bar(void)
{
	Global->PlotGlobal->graph_type = GRAPH_BAR;
	io_info_msg("Graph type is now BAR chart");
}

void plotutils_set_graph_type(enum graph_type gt)
{
	Global->PlotGlobal->graph_type = gt;
}


syntax highlighted by Code2HTML, v. 0.9.1