#include "copyright.h"
/****************************************************************** 
 *
 * graphclass.c --
 *
 *     Implementation of graph class.
 *
 *
 * Author : Dan Gunter
 * Created: 9 June 1998
 *
 ****************************************************************** 
 */
#include "values.h" /* for MAXDOUBLE */
#include "graphclass.h"

/*========= DATA =====================================================*/

extern Tcl_Interp *Et_Interp;
extern Nlv_err eobj;
extern Nlv_State nlvState; /* for checking time window only */

#define NLV_FLUSH 1
#define NLV_DONTFLUSH 0

typedef int Nlv_grMemElt;

typedef struct Nlv_grClient {
  double      *vecX, *vecY;
  long        *vecFp;
  int          nextPoint;
  char         colorIdx;
  int          hostIndex;
  char        *hostName;
  double       lastVal;
  int          graphType;
  unsigned char visible;
  int           timeOrdered;
  unsigned char autoScale;
} Nlv_grClient;

typedef struct EltArrEntry {
  Element       *addr;
  int            lastColor;
  long          *vecFp;
  unsigned char  annflag;
  int            n;
  char           sym[ NLV_SYM_LENGTH ];
} EltArrEntry;

enum { PALETTE_ELEMENT_SLOT = 3, PALETTE_CMD_LEN = 6 };
typedef struct EltPalette {
  Tcl_Obj *objvArr[ NLV_MAX_COLORS ][ PALETTE_CMD_LEN ];
  int      objvArrLength;
} EltPalette;

#define T Nlv_gr
struct T {
  Graph         *graphHandle;/* pointer to internal BLT graph information */
  int           *offsets;    /* per-keyset offsets on the y-axis */
  int          **keyPosY;    /* position of keys on y-axis, -maxTicks=invisible */
  int            numKeySets; /* number of keysets, total */
  int           *numKeys;    /* number of keys in each keyset */
  Nlv_Key      **ticks;      /* array of pointer to keys for each tick */
  int            numTicks;   /* number of ticks to display */
  Nlv_Key       *bogus_key;  /* generic placeholder for tick on graph */
  Nlv_grClient **clData;     /* array of clients thus far */
  int            numClients; /* number of clients thus far */
  int            numHosts;   /* number of "hosts"==groups==colors */
  int            newHost;    /* was the last one processed new? */
  double         latest;     /* most recent time processed */
  double         start;      /* first valid time in file (absolute) */
  unsigned int   maxEvents;
  unsigned int   maxTicks;
  EltArrEntry    **elements; /* 2-d array */
  Nlv_grMemElt **memArr;
  int            memStart[ NLV_NUM_ELEMENT_TYPES];
  int            memEnd  [ NLV_NUM_ELEMENT_TYPES ];
  unsigned char  memEmpty[ NLV_NUM_ELEMENT_TYPES];
  char          *widget;
  int            maxElements;
  double         scale;     /* powers of 10; 1 = milliseconds */
  Nlv_hi         highlight;
  EltPalette     palette;
  int            autoresize;    /* Do automatic window resizing to fit new graph elements */
  double         scaleCompress; /* Fraction to compress scaled elements on screen */
  Nlv_Bounds     window; /* Display window */

};

#define FILE_ERR_CODE 900
#define SCALAR_SIZE sizeof(double)
#define X_AXIS 0
#define Y_AXIS 1

/*========= FUNCTIONS ================================================*/

static void Nlv_grClearMapping( T this );

static int  Nlv_grGetHostIndex( T this,
				char *nm,
				Nlv_KeySet *ksPtr );

static int  Nlv_grInsertPoint( int,
			       double *,
			       double *,
			       long   *);
static int resetElementAddress( T this,
				int type,
				int idx );

static int  colorElement ( T this,
			   int type,
			   int elt,
			   int colorIdx );

char * getElementDataAsString( T this,
			       int type,
			       int idx );

void setElementData( EltArrEntry *entryPtr,
		     int axis,
		     double *newData,
		     int newDataLength );

void initElementData( T this,
		      int type,
		      int idx );

static int dashElement( T this,
			int type,
			int elt,
			int dfactor );

static double diff_from_window( T this, EltArrEntry *slot );

static int    modify_time_window( T this, double modval );

static void   freeGraphClient( Nlv_grClient *p );

static Graph * getGraphHandle( char *path );

extern Graph * Blt_GetGraphFromWindowData(Tk_Window tkwin);

/*
 *----------------------------------------------------------------------
 *
 * New -- Allocate new object.
 *
 * Calls: malloc
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

T Nlv_grNew( )
{
  int i;

  T this = (T)malloc( sizeof( struct T ) );
  this->latest     = -1.;
  this->start      = 0.;
  this->maxEvents  = 128;
  this->maxTicks   = 128;
  this->numKeySets = 0;
  this->clData     = (Nlv_grClient**)malloc( NLV_MAX_CLIENTS * sizeof(Nlv_grClient*) );
  memset( this->clData, 0, NLV_MAX_CLIENTS * sizeof(Nlv_grClient*));
  this->numClients = 0;
  this->numHosts   = 0;
  this->newHost    = 0;
  /* We need a generic bogus key, see structs.h for macro */
  MAKE_BOGUS_KEY( this->bogus_key );
  /* One array per graph type. See Init */
  this->elements   = (EltArrEntry **)malloc( NLV_NUM_ELEMENT_TYPES * sizeof(EltArrEntry));
  this->memArr     = (Nlv_grMemElt **)malloc( NLV_NUM_ELEMENT_TYPES * sizeof(Nlv_grMemElt *));
  this->maxElements= NLV_MAX_ELEMENTS;
  this->highlight  = Nlv_hiNew();
  this->scaleCompress = NLV_SCALE_COMPRESS;
  memset( this->palette.objvArr, 0, NLV_MAX_COLORS * PALETTE_CMD_LEN );

  return this;
}


/*
 *----------------------------------------------------------------------
 *
 * Init -- Do all the Tcl calls to set up graph elements and graph
 *         ahead of time.
 *
 * Calls: Tcl_Eval
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

void Nlv_grInit( T this, char *path )
{
  int i, type;
  int status;
  char *tclreturn;
  char buf[ 512 ];
  char *symName;
  Nlv_EInfo einfo;
  Tcl_Obj **objv;
  char **colorList;
  int  colorSize;

  if ( path == NULL ) 
    NLV_FATAL_S("Nlv_grInit: Graph widget path is empty");

  this->graphHandle = getGraphHandle( path );
  if ( this->graphHandle == NULL ) {
      NLV_SET_EINFO( einfo, 50, 0 );
      Nlv_errReport( eobj, &einfo,"Unable to get graph widget %s", path );
      exit( 1 );
  }

  /* Allocate and clear C resources for elements */
  for ( i=0; i < NLV_NUM_ELEMENT_TYPES; i++ ) {
    this->memEmpty[i] = 1;
    this->memStart[i] = this->memEnd[i] = 0;
    this->elements[i] = (EltArrEntry *)calloc( this->maxElements,sizeof(EltArrEntry));
    this->memArr[i]   = (Nlv_grMemElt *)calloc( this->maxElements,sizeof(Nlv_grMemElt));
  }
  Nlv_grClearMem( this );

  /* Remember the path (e.g. '.graph') in "widget" class variable */
  this->widget = (char *)malloc( strlen(path) + 1 );
  strcpy( this->widget, path );

  /*
   * Create empty graph elements of each pre-defined type.
   */
  for ( type=0; type < NLV_NUM_ELEMENT_TYPES; type++ ) {
      sprintf( buf, "CreateElements %s %d %d", path, type, this->maxElements );
      if ( TCL_OK != Tcl_Eval( Et_Interp, buf ) ) {
	  NLV_SET_EINFO( einfo, 50, 0 );
	  Nlv_errReport( eobj, &einfo,"Tcl error '%s' "
			 "while evaluating '%s'", Et_Interp->result, buf );
	  exit( 1 );
      }
      for ( i=0; i < this->maxElements; i++ ) {
	  resetElementAddress( this, type, i );
	  initElementData( this, type, i );
      }
  }


  /*
   * Initialize the array of commands to color
   * the graph elements, figuratively called the
   * "palette".
   */

  tclreturn = Tcl_GetVar2( Et_Interp, "param","color-ip", TCL_GLOBAL_ONLY );
  if ( !tclreturn )
    status = TCL_ERROR;
  else
    status = Tcl_SplitList( Et_Interp, tclreturn, &colorSize, &colorList );
  if ( status != TCL_OK ) {
    char msg[256];
    sprintf(msg,"Unable to get color list from Tcl: %s\n",Tcl_GetStringResult(Et_Interp) );
    Nlv_errPrintString( eobj, "Nlv_grInit", msg );
    exit( 1 );
  }
  for( i=0; i < colorSize; i++ ) {
    objv = this->palette.objvArr[i];
    objv[0] = Tcl_NewStringObj (path, -1);
    objv[1] = Tcl_NewStringObj ("element", -1);
    objv[2] = Tcl_NewStringObj ("configure", -1);
    objv[3] = NULL;
    objv[4] = Tcl_NewStringObj ("-color", -1);
    objv[5] = Tcl_NewStringObj (colorList[i], -1);
  }
  this->palette.objvArrLength = colorSize;
  free( colorList );

  /*
   * Make all elements an invalid color.
   */

  for( type=0; type < 2; type++ )
    for( i=0; i < this->maxElements; i++ )
      this->elements[type][i].lastColor = -1;

  /* Set the scale of the x-axis */
  tclreturn = Tcl_GetVar2( Et_Interp, "param", "_scale", TCL_GLOBAL_ONLY);
  if ( tclreturn == NULL )
    this->scale = 1;
  else
    this->scale = atof( tclreturn );

  /*
   * Highlight object
   */
  Nlv_hiInit( this->highlight );

  /*
   * Get autoresize parameter
   */
  tclreturn = Tcl_GetVar2( Et_Interp, "param", "autoresize", TCL_GLOBAL_ONLY );
  if ( NULL == tclreturn ) {
    NLV_SET_EINFO( einfo, NLV_WARN + 51, 0 );
    Nlv_errReport( eobj, &einfo,"Tcl error '%s' "
		   "while fetching param(autoresize)", Et_Interp->result );
    this->autoresize = 1;
  }
  else {
    if ( strcasecmp(tclreturn,"no")  == 0 ||
	 strcasecmp(tclreturn,"0")   == 0 ||
	 strcasecmp(tclreturn,"n")   == 0 ||
	 strcasecmp(tclreturn,"off") == 0 )
      this->autoresize = 0;
    else
      this->autoresize = 1;
  }
}

/*
 *----------------------------------------------------------------------
 *
 * getGraphHandle --
 *
 *       Get a handle to internal BLT info.
 *----------------------------------------------------------------------
 */
Graph * getGraphHandle( char *path )
{
  Tk_Window container;

  container = Tk_NameToWindow( Et_Interp, path, Tk_MainWindow(Et_Interp) );

  return Blt_GetGraphFromWindowData( container );
}

/*
 *----------------------------------------------------------------------
 *
 * resetElementAddress --
 *
 *      Query the BLT widget for the new address of this
 *      element, then place this address in the static array
 *      this->elements[][].
 *
 *      Also set element vector's capacity.
 *
 *----------------------------------------------------------------------
 */
int resetElementAddress( T this,
			 int type,
			 int idx )
{
  int            status = TCL_OK;
  Nlv_EInfo      einfo;
  char           buf[32];
  Element       *elementPtr = NULL;

  sprintf( buf, "%dt%d", type, idx );
  status = Blt_NameToElement( this->graphHandle, buf, &elementPtr );
  if ( status != TCL_OK ) {
    NLV_SET_EINFO( einfo, 100, 0 );
    Nlv_errReport( eobj, &einfo, "Cannot find element %dt%d in graph.",
		   type, idx );
  }
  else {
    this->elements[type][idx].addr = elementPtr;
  }

  return status;
}
/*
 *----------------------------------------------------------------------
 *
 * initElementData --
 *
 *    Set new element capacity, length, data, etc.
 *
 *----------------------------------------------------------------------
 */
void initElementData( T this, int type, int idx )
{
  EltArrEntry *entryPtr;

  entryPtr = &this->elements[type][idx];

  Blt_VecData(&entryPtr->addr->x) = NULL;
  Blt_VecSize(&entryPtr->addr->x) = 0;
  Blt_VecData(&entryPtr->addr->y) = NULL;
  Blt_VecSize(&entryPtr->addr->y) = 0;

  setElementData( entryPtr, X_AXIS, NULL, -1 );
  setElementData( entryPtr, Y_AXIS, NULL, -1 );
}


/*
 *----------------------------------------------------------------------
 *
 * getElementDataAsString -- Make a string with pairs of x,y coords.
 *                           The x and y are separated by a space, and
 *                           the pairs are separated by a newline.
 *                           This is the format expected by gnuplot.
 *
 * Post: Buffer is _not_ malloc-ed, so don't try to free it or
 *       make multi-threaded calls to this function.
 *
 *----------------------------------------------------------------------
 */
char * getElementDataAsString( T this, int type, int idx )
{
  static char  buf[ 4096 ]; /* returned string */
  char         val[ 64 ];   /* one value as a string */
  int          i;
  EltArrEntry *ePtr = &this->elements[type][idx];

  buf[0] = '\0';
  for ( i=0; i < ePtr->n; i++ ) {
    sprintf( val, "%lf ", Blt_VecData(&ePtr->addr->x)[i] );
    strcat( buf, val );
    sprintf( val, "%lf\n", Blt_VecData(&ePtr->addr->y)[i] );
    strcat( buf, val );
  }

  return buf;
}

/*
 *----------------------------------------------------------------------
 *
 * setElementData --
 *
 *           Set the data for the element.
 *           If newDataLength < 0, only clear the data.
 *
 *----------------------------------------------------------------------
 */
void setElementData( EltArrEntry *entryPtr,
		     int axis,
		     double *newData,
		     int newDataLength )
{
  ElemVector *vec;
  double   *target;
  double   min, max;
  int      i;
  int      bytesNeeded;

  vec = ( axis == X_AXIS ? &entryPtr->addr->x : &entryPtr->addr->y );

  if ( newDataLength < 0 ) {
    Blt_VecLength(vec) = 0;
    Blt_VecMin(vec)    = 0;
    Blt_VecMax(vec)    = 0;
    return;
  }

  bytesNeeded = newDataLength * SCALAR_SIZE;

  if ( Blt_VecSize(vec) < bytesNeeded ) {
    Blt_VecData(vec) = (double *)realloc( Blt_VecData(vec), bytesNeeded );
  }

  target = Blt_VecData(vec); 
  min = max = newData[0];

  for ( i=0; i < newDataLength; i++ ) {
    target[i] = newData[i];
    if ( target[i] < min )
      min = target[i];
    else if ( target[i] > max )
      max = target[i];
  }
    
  Blt_VecLength(vec) = newDataLength;
  Blt_VecMin(vec)    = min;
  Blt_VecMax(vec)    = max;
}

/*----------------------------------------------------------------------
 *
 * rescaleElement -- 
 *     Using the given ratio and the minimum y coordinate
 *     for the graph tick, check if the element is one of those
 *     which should be rescaled. If so, adjust its y-coordinate
 *     by the ratio and update the vector's minimum/maximum values.
 *
 *  Return: 1 if the element was rescaled, else 0.
 *
 *----------------------------------------------------------------------
 */
int rescaleElement( EltArrEntry *entryPtr, /* Entry to be rescaled */
		    double min,            /* Minimum y-value (before rescaling) */
		    double ratio,          /* Ratio to multiply by */
		    int dir,               /* 1=max, -1=min */
		    int loadLineFirstPoint ) /* Boolean - use 1st point in load line? */
{
    ElemVector *vec = &(entryPtr)->addr->y;
    double *target;
    int rescaled    = 0;
    double y, yzero;
    int index;
    int isPoint;

    switch( Blt_VecLength(vec) )
    {
     case -1:
     case 0: return 0;
     case 1: isPoint=1; break;
     default: isPoint=0;
    }

    /*
     * For points, the index is always 0.
     * For load lines, index=1 for all elements except
     * the first one on the screen, in which case index=0; this
     * is because the 0th coordinate in a load line repeats
     * the 1st coordinate of the previous load line.
     */ 
    index = ( isPoint || loadLineFirstPoint ) ? 0 : 1;
    target = Blt_VecData(vec);
    yzero = target[index] - min;

    /* 
     * Only rescale elements falling between this and the next
     * grid line.
     */
    /*    if ( yzero >= 0 && yzero <= 1 ) */
    if ( yzero > 0 && yzero < 1 )
    {
      /* Debugging */
      /* printf("rescaleElement: yzero=%lf\n", yzero ); */
      
      y = ( dir == 1 ? (ratio * yzero + min) : ( ratio * yzero + ( 1 - ratio ) + min ) ); 
      target[index] = y;

      if ( isPoint )
	Blt_VecMax(vec) = Blt_VecMin(vec) = y;
      else
	switch( dir )
	{
	case 1: if ( y > Blt_VecMax(vec) ) Blt_VecMax(vec) = y;
	  break;
	case -1: if ( y < Blt_VecMin(vec) ) Blt_VecMin(vec) = y;
	  break;
	}
      
      /* Debugging */
      /* printf("rescaleElement: rescaled a point y=%lf to y=%lf\n", (yzero + min), y ); */
      
      rescaled = 1;
    }

    return rescaled;
}

/*----------------------------------------------------------------------
 *
 * Rescale -- Rescale all (load-line or point) elements on the graph
 *            which start at a given graph tick.
 *
 * Calls: grDraw
 *
 * Return: # of rescaled graph elements ( 0..? )
 *
 *----------------------------------------------------------------------
 */
int Nlv_grRescale( T this,
		   int clientIndex,   /* Index of client for grDraw fn */
		   Nlv_KeySet *ksPtr, /* Pointer to relevant keyset info */
		   double ratio,      /* rescaling ratio */
		   int    direction ) /* 1=max moved up, -1=min moved down */
{
    Nlv_EInfo einfo;
    double y;            /* gridline y-value for elements in this keyset */
    int numScaled = 0;   /* return value */
    int etype;           /* element type */
    int eltctr;          /* element we are working on */
    double minX;         /* smallest x-axis value furthest left elt. */
    int    minXElt;      /* element which had the smallest X value */
    EltArrEntry *ePtr;   /* Pointer to element state */

    /* Debugging */
    /* printf("Nlv_grRescale: Called with ratio=%lf direction=%d\n", ratio, direction ); */

    /* y value of gridline at the bottom of load-line or points */
    y = this->offsets[ ksPtr->index ];

    etype = ksPtr->gtype;
    if ( etype == NLV_LOAD ||
	 etype == NLV_POINT )
    {
	int isPoint = ( etype == NLV_POINT ) ? 1 : 0; /* for memArr */

	minX = MAXDOUBLE;
	minXElt = -1;
	
	for ( eltctr = 0; eltctr < this->maxElements; eltctr++ )
	{
	    if ( NULL == (ePtr = &this->elements[isPoint][eltctr]) )
		break;

	    if ( ePtr->n > 0 &&
		 rescaleElement( ePtr, y, ratio, direction, 0  ) )
	    {
		double eltMinX = Blt_VecMin(&ePtr->addr->x);
		
		if ( etype = NLV_LOAD && minX > eltMinX) 
		{
		    minX = eltMinX;
		    minXElt = eltctr;
		}
		numScaled++;
	    }
	}

	/*
	 * Rescale the 1st visible point of a loadline.
	 */
	if ( etype == NLV_LOAD && minXElt > -1 )
	{
	    ePtr = &this->elements[etype][minXElt];
	    rescaleElement( ePtr, y, ratio, direction, 0 );
	}
    } /* eType == point or load */

    /* Debugging */
    /* printf("Nlv_grRescale: rescaled %d elements\n", numScaled ); */

    return numScaled;
}
/*
 *----------------------------------------------------------------------
 *
 * colorElement --
 *
 *           Change the color of an element on the graph.
 *           (unless it's already that color, in which case do
 *            nothing and return OK)
 *
 *----------------------------------------------------------------------
 */

int colorElement ( T this,
		   int type,
		   int elt,
		   int colorIdx )
{
  char buf[16];
  Tcl_Obj **objv      = this->palette.objvArr[colorIdx];
  int flags           = TCL_GLOBAL_ONLY; 
  int status          = TCL_OK;
  EltArrEntry *eltPtr = &this->elements[type][elt];

  if ( eltPtr->lastColor == colorIdx ) {
    return TCL_OK;
  }

  sprintf(buf,"%dt%d", type, elt);
  if ( objv[ PALETTE_ELEMENT_SLOT ] == NULL ) 
    objv[ PALETTE_ELEMENT_SLOT ] = Tcl_NewStringObj( buf, -1);
  else
    Tcl_SetStringObj( objv[ PALETTE_ELEMENT_SLOT ], buf, -1);
  status = Tcl_EvalObjv(Et_Interp, PALETTE_CMD_LEN, objv, flags);
  if ( status != TCL_OK ) 
    fprintf(stderr,"Failed to color element using Tcl_EvalObjv\n");
  else {
    this->elements[type][elt].lastColor = colorIdx;
    status = resetElementAddress( this, type, elt );
  }

  return status;
}



/*
 *----------------------------------------------------------------------
 *
 * Process -- 
 *
 * Calls:
 *
 * Algorithm: - Check the client index, keyset, and event # to make
 *            sure that they are in range.
 *            - Calculate actual y-index on graph. If it is > 0 (i.e.
 *            the point is visible), add the point to the vector
 *            for the current client.
 *            - FOR LINE-TYPE ONLY:
 *            If this point is less than the previous one, then
 *            invoke Draw to draw the graph element.
 *            UNLESS: If the data is time-ordered, and the event is 
 *            not the 1st, we need to insert the event in the proper 
 *            location _instead of_ drawing the line and starting a 
 *            new one.
 *
 *
 *----------------------------------------------------------------------
 */
void dump_clData( Nlv_grClient *d );

int Nlv_grProcess( T this, Nlv_GData *gd)
{
  int retval = 0;
  int           gtick, cli, np;
  double        y;
  Nlv_EInfo     einfo;
  Nlv_grClient *d;         /* Client structure for this data */

  if ( gd->clIndex  < 0 || gd->clIndex  >= this->numClients ||
       gd->eventSet < 0 || gd->eventSet >= this->numKeySets ||
       gd->event    < 0 || gd->event    >= this->numKeys[gd->eventSet] )
    return NLV_NOT_DRAWN;

  /* 
   * Find graph tick value relative to the rest of the events
   * in this keyset. -maxticks == invisible.
   */
  gtick = this->keyPosY[ gd->eventSet ][ gd->event ];
  if ( gtick == -(this->maxTicks) )
  {
      return NLV_NOT_DRAWN;
  }

  /* 
   * Move the graph tick up to its actual location
   * on the y-axis and then add the fractional eventVal.
   */
  /* printf("<dbg>gtick=%d offset=%d eventSet=%d event=%d\n",gtick,this->offsets[gd->eventSet], gd->eventSet,gd->event); */
  gtick += this->offsets[ gd->eventSet ];

  /* 
   * Compress the range of the event values
   * so stacked items don't crowd eachother on the display.
   */
  if ( gd->eventVal != 0. )
    y = gtick + this->scaleCompress * gd->eventVal + (1 - this->scaleCompress) / 2;
  else
    y = gtick;

/* DEBUG */
/* printf("event value=%lf\n", (double)gd->eventVal ); */

  cli    = gd->clIndex;
    
  /* 
   * Fill array spaces for this point:
   *    time, y-position, filepos
   */
  d = this->clData[cli];
  np = d->nextPoint;
  if ( np > (NLV_MAX_POINTS-2) ) {
    dump_clData(d);
    return NLV_NOT_DRAWN;
  }
  d->vecX[ np ] = Nlv_grGraphTime( this, gd->time );
  d->vecY[ np ] = y;
  d->vecFp[ np ] = gd->filepos;
  d->nextPoint++;


  /*
   * Draw the point, load-line, or line segment,
   * if necessary.
   */
   switch( gd->graphType ) {
      /* Always draw points */
    case NLV_POINT:
      if ( gd->eventVal != -1. ) {
	retval = Nlv_grDraw( this, cli, NLV_DONTFLUSH );
	this->latest = gd->time - this->start;
      }
      break;
      /* Draw load-lines when there are 2 points */
    case NLV_LOAD:
      if ( d->nextPoint >= 2 ) {
	retval = Nlv_grDraw( this, cli, NLV_DONTFLUSH );
	this->latest = gd->time - this->start;
      }
      else
	  retval = NLV_NOT_DRAWN;
      break;
      /* Draw lines when:
       * (0) we run out of space to store points
       * (1) the y-value goes down AND
       * (1.1) the data is not time-ordered OR
       * (1.2) the data is time-ordered and inserting a point
       *       "fills up" the list of events.
       */ 
    case NLV_LINE:
      if ( y <= d->lastVal ) {
	if ( d->timeOrdered
	     && (0 == Nlv_grInsertPoint( np, d->vecX, d->vecY, d->vecFp )) )
	  retval = NLV_NOT_DRAWN;
	else {
	  retval = Nlv_grDraw( this, cli, NLV_DONTFLUSH );
	}
	this->latest = gd->time - this->start;
      }
      break;
    }

  /* Remember the last y-value */
  d->lastVal = d->vecY[np];

  return retval;
}
/* Internal Function: dump_clData
 */
void dump_clData( Nlv_grClient *d )
{
  int i;

  printf("<dbg>client colr %d: nextPoint=%d hostName=%s lastVal=%lf "
	 "graphType=%d timeOrdered=%d\n"
	 , d->colorIdx, d->nextPoint, d->hostName, d->lastVal, d->graphType,
	 d->timeOrdered );
  printf("\nY=[ ");
  for ( i=0; i < NLV_MAX_POINTS; i++ )
    printf("%ld ",(long)(d->vecY[i]));
  printf("] \n");
}

/* 
 * Internal Function: Nlv_grInsertPoint
 * Calling  Function: Nlv_grProcess
 *
 * - First make sure this value is not already in there!
 *   If so, return 1 (one).
 * - Loop until you reach an element which occurs before this
 *   one on the y-axis, shifting up the data in vecX, vecY, and vecFp
 *   as we go.
 * - Copy our data into the space immediately before that
 *   element.
 */
int
Nlv_grInsertPoint( int np,
		   double *vecX,
		   double *vecY,
		   long   *vecF )
{
  int cur,prev, i;
  double myX = vecX[np];
  double myY = vecY[np];
  long   myF = vecF[np];
  double y = floor(myY);

  for ( i=np-1; i >= 0 && y < vecY[i]; i-- ) 
    ;
  if ( i >= 0 && floor(vecY[i]) == y )
    return 1;

  for ( cur = np-1, prev=np; 
	cur >= 0 && floor(vecY[cur]) > y;
	cur--, prev-- ) {
    vecX[prev] = vecX[cur];
    vecY[prev] = vecY[cur];
    vecF[prev] = vecF[cur];
  }
    
  vecX[prev] = myX;
  vecY[prev] = myY;
  vecF[prev] = myF;

  return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * Flush -- Flush currently pending lines, either drawing
 *          them into the graph or ignoring them.
 *
 * Calls    :
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

void Nlv_grFlush( T this, int drawFlag )
{
  int i;

  for ( i=0; i < this->numClients; i++ ) {
    if ( drawFlag )
       Nlv_grDraw( this, i, NLV_FLUSH );
    this->clData[i]->nextPoint = 0;
    this->clData[i]->lastVal = -1.0;
  }
}


/*
 *----------------------------------------------------------------------
 *
 * GraphTime -- Return graph-axis time from absolute time.
 *
 * Calls:
 *
 * Algorithm: Subtract earliest time.
 *            Multiply by scaling factor (1 = seconds, 1000=ms)
 *
 *----------------------------------------------------------------------
 */

double Nlv_grGraphTime( T this,
			double t )
{
  return (t - this->start) * this->scale;
}


/*
 *----------------------------------------------------------------------
 *
 * RealTime -- Return absolute time from graph-axis time.
 *
 * Calls:
 *
 * Algorithm: Add earliest time.
 *            Divide by scaling factor ( 1=seconds, 1000=ms)
 *
 *----------------------------------------------------------------------
 */
double Nlv_grRealTime( T this, double t )
{
  return t/this->scale + this->start;
}


/*
 *----------------------------------------------------------------------
 *
 * AddClient -- Put a new client on the graph (assign a color).
 *
 * Calls    : Tcl_GetVar2, Tcl_SplitList, strtol, malloc
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */

int Nlv_grAddClient( T this,
		     Nlv_ky keysObj,
		     Nlv_Client* cl,
		     int graphType )
{
  char *func = "Nlv_grAddClient";
  char *c;
  char **argv;
  int    argc,i;
  Nlv_grClient *newClPtr;
  char   buf[ 512 ];

  if ( this->numClients > NLV_MAX_CLIENTS )
    return -1;

  /* Allocate, initialize new client structure */
  newClPtr           = (Nlv_grClient *)malloc( sizeof(struct Nlv_grClient) );
  newClPtr->vecX      = (double *)malloc( NLV_MAX_POINTS * SCALAR_SIZE );
  newClPtr->vecY      = (double *)malloc( NLV_MAX_POINTS * SCALAR_SIZE );
  newClPtr->vecFp     = (long *)malloc( NLV_MAX_POINTS * sizeof(long) );
  newClPtr->nextPoint = 0;
  newClPtr->lastVal   = -1.0;
  newClPtr->graphType = graphType;
  newClPtr->visible   = 1;
  newClPtr->hostName  = Nlv_IpStr2HostName( Nlv_kyGetHostAlias( keysObj,cl->hostName ) );
  if ( strcmp(newClPtr->hostName,"") == 0 )
    newClPtr->hostName = strdup("?");

  /* 
   * Determine hostindex, which colors lines AND 
   * notices new hosts.
   */
  newClPtr->hostIndex = Nlv_grGetHostIndex( this, newClPtr->hostName, cl->keyPtr->ks );
  c        = Tcl_GetVar2( Et_Interp, "param", "color-ip", TCL_GLOBAL_ONLY );
  Nlv_Split( c, &argc, &argv ); 
  if ( !c || argc == 0 ) {
    free( argv[0] ); 
    free( argv );
    Nlv_errPrintString( eobj, func, "Cannot get a color for the graph legend");
    return -1;
  }
  newClPtr->colorIdx = newClPtr->hostIndex % argc;

  /*
   * Copy the boolean variable representing "time-ordered" mode
   * This variable is used in the Nlv_grProcess function.
   */
  newClPtr->timeOrdered = cl->keyPtr->ks->timeOrdered;

  /* Call TCL procedure to handle GUI addition of client */
  if ( Nlv_grIsNewHost( this, newClPtr->hostIndex ) ) {
    sprintf( buf, "AddToLegend %s %s %d",
	     Tcl_GetVar2( Et_Interp, "widget","GraphLegend", TCL_GLOBAL_ONLY ),
	     newClPtr->hostName,
	     newClPtr->hostIndex );
    if ( TCL_OK != Tcl_Eval( Et_Interp, buf ) ) {
      Nlv_errPrintString( eobj, func, "Cannot add to graph legend.");
      /* NLV_FATAL_S( buf );
	 exit( 1 );
      */
    }
  }
  /*
   * If we have an existing host,
   * copy {visibility} from any matching host
   */
  else {
    for ( i=0;
	  i < this->numClients && this->clData[i]->hostIndex != newClPtr->hostIndex;
	  i++ ) ;
    if ( i >= this->numClients ) {
      Nlv_errPrintString( eobj, func, "Not a new host, but no matching host found.");
      exit( 1 );
    }
    newClPtr->visible = this->clData[i]->visible;
  }

  /* Put new client in array */
  this->clData[ this->numClients++ ] = newClPtr;

  free( argv[0] );
  free( argv );

  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Draw -- Draw data accumulated for client, i.e. move to memory
 *         and issue drawing command to BLT graph widget.
 *
 * Return: 0=ok, other=error
 *
 * Algorithm: For calculating min and max, assume that BOTH
 *            x & y are monotonic increasing.
 *
 *----------------------------------------------------------------------
 */

int Nlv_grDraw( T this, 
		int cli,    /* client index */ 
		int flush ) /* flush flag */
{
  char *func = "Nlv_grDraw";
  int jj;
  int i, n, isPoint;
  Nlv_grClient *clPtr;
  EltArrEntry  *slot;
  char buf[ 256 ];
  Nlv_EInfo einfo;
  double distance_off_edge = 0;

  clPtr   = this->clData[cli];
  isPoint = ( clPtr->graphType == NLV_POINT ? 1 : 0 );
  slot    = &this->elements[isPoint][ this->memEnd[isPoint] ];
  if ( clPtr->nextPoint == 0 ) {
    /* Nlv_errPrintString( eobj, func, "Attempt to draw empty line."); */
    return -1;
  }
  switch( clPtr->graphType ) {
  case NLV_LINE:
  case NLV_LOAD:
    /* 
     * When we flush, draw ALL points in array,
     * instead of using the last point as the first
     * point of the next line.
     */
    if ( flush )
      n = clPtr->nextPoint;
    else {
      n = clPtr->nextPoint-1;
      /* 
       * Fill out to at least one line segment plus first
       * point of next segment
       */
      if ( n == 1 ) {
	clPtr->vecX[2] = clPtr->vecX[1];
	clPtr->vecY[2] = clPtr->vecY[1];
	clPtr->vecFp[2] = clPtr->vecFp[1];
	n = 2;
      }
    }
    break;
  case NLV_POINT:
    clPtr->vecX[1] = clPtr->vecX[0];
    clPtr->vecY[1] = clPtr->vecY[0];
    clPtr->vecFp[1] = clPtr->vecFp[0];
    n = 1;
  }
  if ( n < 1 ) {
    Nlv_grResetClientData( this, cli );
    /* Nlv_errPrintString( eobj, func, "Attempt to draw empty line."); */
    return -1;
  }

  /* If not visible, STOP now */
  if ( 0 == clPtr->visible ) {
    if ( clPtr->graphType != NLV_POINT ) {
      clPtr->vecX[0] = clPtr->vecX[ n ];
      clPtr->vecY[0] = clPtr->vecY[ n ];
      clPtr->nextPoint = 1;
    }
    else
      clPtr->nextPoint = 0;
    return NLV_NOT_DRAWN;
  }

  /* 
   * If the element 
   * which was in this slot
   * before was annotated, tell Tcl to forget it.
   */
  if ( slot->annflag ) {
    slot->annflag = 0;
    strcpy( buf, "ForgetAnnotated" );
    if ( TCL_OK != Tcl_Eval( Et_Interp, buf ) ) {
      NLV_SET_EINFO( einfo, 83, 10 );
      Nlv_errReport( eobj, &einfo, "Can't remove Annotation\n"
		     "Tcl's explanation is: '%s'",
		     Et_Interp->result);
    }
  }

  /*
   * Set element color
   */
  if ( TCL_OK != colorElement( this, isPoint, this->memEnd[isPoint], clPtr->colorIdx ) ) {
    NLV_SET_EINFO( einfo, 82, 10 );
    Nlv_errReport( eobj, &einfo, 
		   "Unable to give a color to element %dt%d: %s\n",
                   isPoint, 
		   this->memEnd[isPoint],
                   Tcl_GetStringResult(Et_Interp) );
  }

  /*
   * Set X,Y vectors
   */
  setElementData( slot, X_AXIS, clPtr->vecX, n );
  setElementData( slot, Y_AXIS, clPtr->vecY, n );

  /* 
   * Set filepos vector
   */
  if ( slot->vecFp )
    free( slot->vecFp );
  slot->vecFp = (long *)malloc(n * sizeof(long));
  memcpy( slot->vecFp, clPtr->vecFp, n*sizeof(long) );

  /*
   * Set number of elements
   */
  slot->n = n;

  /*
   * Check whether graph window should be resized
   * to accommodate this element.
   * Call AFTER "set X,Y vectors" to make sure min/max
   * have been precalculated.
   */
  if ( this->autoresize &&
      (distance_off_edge = diff_from_window( this, slot )) > 0 ) {
    if ( TCL_OK != modify_time_window( this, distance_off_edge ) ) {
      NLV_SET_EINFO( einfo, 82, 10 );
      Nlv_errReport( eobj, &einfo, 
		     "Cannot modify param(TimeWindow): %s\n",
		     Tcl_GetStringResult(Et_Interp) );
    }
  }

  /* 
   * Clear client copy of vector ..
   * ..OR re-set so next point will make a line
   */
  if ( flush )
    Nlv_grResetClientData( this, cli );
  else
    switch( clPtr->graphType ) {
    case NLV_LINE:
      clPtr->vecX[0] = clPtr->vecX[ n ];
      clPtr->vecY[0] = clPtr->vecY[ n ];
      clPtr->vecFp[0] = clPtr->vecFp[ n ];
      clPtr->nextPoint = 1;
      break;
    case NLV_LOAD:
      clPtr->vecX[0] = clPtr->vecX[ n ];
      clPtr->vecY[0] = clPtr->vecY[ n ];
      clPtr->vecFp[0] = clPtr->vecFp[ n ];
      clPtr->nextPoint = 1;
      break;
    case NLV_POINT:
      clPtr->nextPoint = 0;
      break;
    }
  
  /* 
   * Place data in mem, advance memEnd and memStart
   */
  this->memArr[isPoint][this->memEnd[isPoint]] = cli;
  this->memEnd[isPoint] = (this->memEnd[isPoint] + 1) % this->maxElements;
  /* wrapped? */
  if ( this->memEnd[isPoint] == this->memStart[isPoint] )
    this->memStart[isPoint] = ( this->memStart[isPoint] + 1 ) % this->maxElements;
  this->memEmpty[isPoint]  = 0;

  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ShiftClientData -- Move all the pointers to client data down
 *                    one notch. This keeps the client indices
 *                    consistent between this and clientsclass.
 *                    Also, as this is equivalent to deleting a
 *                    client, the number of clients needs
 *                    to be decremented.
 *
 * Algorithn: Actually, just copy the pointers :)
 *
 *----------------------------------------------------------------------
 */

void Nlv_grShiftClientData( T this, int first, int num )
{
  freeGraphClient( this->clData[first] );
  memmove( &this->clData[first], &this->clData[first+1],
	   num * sizeof(Nlv_grClient *) );
  this->numClients--;
}

/*
 * Little utility fn to free a client struct.
 */
void freeGraphClient( Nlv_grClient *p )
{
  if ( p->vecX )
    free( p->vecX );
  if ( p->vecY )
    free( p->vecY );
  if ( p->vecFp )
    free( p->vecFp );
  if ( p->hostName )
    free( p->hostName );
  free( p );
}

/*
 *----------------------------------------------------------------------
 *
 * ResetClientData -- Prepare client for new data.
 *
 * Algorithm: Simply set # points to 0.
 *
 *----------------------------------------------------------------------
 */
void Nlv_grResetClientData( T this, int clIdx ) 
{
  this->clData[clIdx]->nextPoint = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * InMem -- The bounds fall entirely within remembered points?
 *
 * Calls:
 *
 * Algorithm: Check against min/max for the elements at the memStart
 *            and memEnd positions in the array, respectively.
 *            Return non-zero if YES, zero if NO.
 *
 *----------------------------------------------------------------------
 */

int Nlv_grInMem( T this, Nlv_Bounds *b )
{
  int i, gt, nonEmpty;
  double left, right;

  nonEmpty = 0;
  for ( i=0; i < NLV_NUM_ELEMENT_TYPES; i++ ) {
    if (this->memEmpty[i])
      continue;
    nonEmpty = 1;
    left = this->elements[i][this->memStart[i]].addr->x.min;
    right = this->elements[i][(this->memEnd[i]-1 % this->maxElements)].addr->x.max;
    if ( b->x[0] < left ||
	 b->x[1] > right ) {
      return 0;
    }
    
  }
  return nonEmpty;
}

/*
 *----------------------------------------------------------------------
 *
 * ClearMem -- Clear all remembered points.
 *
 * Calls:
 *
 * Algorithm: Reset the 'memStart' and 'memEnd' pointers, after
 *            clearing all the vectors for all graph elements.
 *            Clearing is complicated slightly because this is
 *            a circular array.
 *
 *----------------------------------------------------------------------
 */

void Nlv_grClearMem( T this )
{
  int i, j, pCtr, p[4];

  for ( i=0; i < NLV_NUM_ELEMENT_TYPES; i++ ) {
    if ( this->memEmpty[i] )
      continue;
    if ( this->memStart[i] > this->memEnd[i] ) {
      p[0] = this->memStart[i];
      p[1] = this->maxElements;
      p[2] = 0;
      p[3] = this->memEnd[i];
    }
    else {
      p[0] = this->memStart[i];
      p[1] = this->memEnd[i];
      p[2] = this->memEnd[i];
      p[3] = this->memEnd[i];
    }
    for ( pCtr = 0; pCtr < 4; pCtr += 2 )
      for ( j=p[pCtr]; j < p[pCtr+1]; j++ ) {
	setElementData( &this->elements[i][j], X_AXIS, NULL, -1 );
	setElementData( &this->elements[i][j], Y_AXIS, NULL, -1 );
	/*	setElementData( &this->elements[i][j], X_AXIS, NULL, -1 ); */
	this->memArr[i][j] = -1;
      }
    this->memEnd[i]   = this->memStart[i];
    this->memEmpty[i] = 1;
  }
}

/*----------------------------------------------------------------------
 *
 * WriteGnuplot -- Write the entire buffer from 'min' to
 *                 'max' using Gnuplot.
 *
 * Calls: getElementDataAsString
 *
 * Algorithm: Sort by (a) type, and (b) color.
 *            Do this by scanning the buffer once for each color
 *            and printing out as we go.
 *            Because we're writing a command file with the command
 *            at the top, we actually need to do this whole thing
 *            twice (once writing cmd, once writing data)!
 *
 *----------------------------------------------------------------------
 */
char *
Nlv_grWriteGnuplot( T this,
		    int file_flag,    /* file=1, printer=0 */
		    char *dev_name,   /* filename or printer name */
		    char *output_fmt, /* gnuplot output format */ 
		    char *page_orient, /* ignored */
		    char *color_mode,  /* ignored */
		    double xmin,      /* bounding box for */
		    double xmax,      /* this             */
		    double ymin,      /* plot.            */
		    double ymax )     /* (x,y)-(x,y)      */
{
  FILE *fp;
  int i;
  int pass, element, type, color;
  char *str, *color_str, *title_str;
  char type_str[ 32 ];
  char temp_file[ 256 ];
  char cmd_buf[ 300 ];
  int plot_index, type_data;
  int first = 1;
  Nlv_Client *client_p;
  int matched_elt;
  char *gopts;

  /* debugging
     fprintf(stderr,"<dbg>WriteGnuplot args: dev=%s, output=%s\n",
     dev_name, output_fmt );
  */

  /* Open file */
  if ( strcmp(output_fmt,"gnuplot")  == 0 ) {
    fp = fopen( dev_name, "w" );
  }
  else {
    sprintf( temp_file,"nlv_gnuplot_%d", (int)getpid() ); 
    fp = fopen( temp_file, "w" );
  }
  if ( fp == NULL ) {
    return "Cannot open file in current directory.";
  }

  /* Grab graph title from Tcl */
  sprintf(cmd_buf, "$widget(graph,0) cget -title" );
  if ( Tcl_GlobalEval( Et_Interp, cmd_buf ) != TCL_OK )
    return "Cannot get graph title from graph widget.";
  title_str = strdup(Et_Interp->result);

  /* Header, configuring graph */
  fputs( "#\n"
	 "# Gnuplot command file\n"
	 "# Generated by NetLogger Visualization\n"
	 "# To view, run gnuplot; type: load 'file'\n"
	 "#\n"
	 "set grid xtics\n"
	 "set grid ytics\n"
	 "set key below\n"
	 ,fp );
  fprintf( fp, "set title \"%s\"\n", title_str );
  if ( strcmp(output_fmt,"gnuplot")  != 0 ) {
    fprintf ( fp, "set terminal %s\n", output_fmt );
    fprintf ( fp, "set output \"%s\"\n", dev_name );
  }
  fputs ( "set ytics (", fp );
  for ( i=0; i < this->numTicks; i++ ) {
    if ( i == 0 )
      fprintf( fp, "\"%s\" %d", Nlv_grGetLabel(this,i), i );
    else
      fprintf( fp, ",\"%s\" %d", Nlv_grGetLabel(this,i), i);
  }
  fputs (")\n", fp );
  fprintf(fp, "set noautoscale\n"
	  "set xrange [%lf:%lf]\n"
	  "set yrange [%lf:%lf]\n",
	  xmin, xmax, ymin-0.5, ymax+0.5 );
  fputs ( "plot ",fp);

  /* Main loop, 2 * NLV_MAX_COLORS passes through data */
  plot_index = 0;
  for ( pass = 0; pass < 2; pass ++ ) {
    for ( type = 0; type < NLV_NUM_ELEMENT_TYPES; type++ ) {
      type_data = 0;
      switch( type ) {
      case NLV_LOAD:
      case NLV_LINE: 
	strcpy(type_str,"lines"); 
	break;
      case NLV_POINT: 
	strcpy( type_str, "points" ); 
	break;
      }
      for ( color = 0 ; color < NLV_MAX_COLORS; color++ ) {
	matched_elt = -1;
	for ( element=0; element < this->maxElements; element++ ) {
	  /* Filter out elements which don't cross the drawing area
	   * or are not the currently searched-for color */
	  if ( color != this->elements[type][element].lastColor ||
	       Blt_VecMin(&(this->elements[type][element].addr->x)) > xmax ||
	       Blt_VecMax(&(this->elements[type][element].addr->x)) < xmin ||
	       Blt_VecMin(&(this->elements[type][element].addr->y)) > ymax ||
	       Blt_VecMax(&(this->elements[type][element].addr->y)) < ymin )
	    continue;
	  matched_elt = element;
	  if ( pass == 1 ) {
	    if ( NULL == (str = getElementDataAsString( this, type, element )) ) {
	      fprintf(stderr,"Warning: Empty element data at type=%d element=%d "
		      "color=%d\n", type,element,color);
	      continue;
	    }
	    fputs( str, fp );
	    /* Don't connect successive lines */
	    if ( type == NLV_LINE )
	      fputs( "\n", fp );
	  }
	}

	if ( matched_elt != -1 )
	{
	  type_data = 1;
	  switch( pass ) {
	  case 0:
	    if ( !first )
	      fputs( ", ", fp );
	    else
	      first = 0;
	    client_p = Nlv_clGetClientRecord( nlvState.cl,
					      this->memArr[type % NLV_NUM_ELEMENT_TYPES ][matched_elt]);
	    color_str = Nlv_grGetHostName( this, client_p->clIndex );
	    fprintf( fp, "'-' t \"%s\" with %s ",
		     (color_str ? color_str : "??"),
		     type_str );
	    break;
	  case 1:
	    fputs ( "e\n", fp ); /* extra sep = new color */
	    break;
	  default:
	    fprintf(stderr,"Internal Error: Illegal pass # while "
		    "printing to gnuplot.\n");
	  }
	  plot_index++;
	}

      } /* for color */
    } /* for type */
    if ( pass == 0 )
      fputs ( "\n", fp );
  } /* for pass */

  fclose( fp );
  free( title_str );
  str = NULL;

  /* If format is 'gnuplot', we are done */
  if ( strcmp( output_fmt, "gnuplot" ) == 0 )
    return NULL;

  /* Run gnuplot on the data. */

  if ( strcmp(output_fmt,"x11") == 0 ||
       strcmp(output_fmt,"linux") == 0 )
    gopts = strdup("-persist");  /* for screen display */
  else
    gopts = strdup("");

  if ( getenv("GNUPLOT") != NULL )
    sprintf(cmd_buf, "%s %s %s", getenv("GNUPLOT"), gopts, temp_file );
  else
    sprintf(cmd_buf,"gnuplot %s %s", gopts, temp_file );

  /* debugging
  fprintf(stderr,"<dbg>Executing command: %s\n",cmd_buf);
  */

  i = system( cmd_buf );
  if ( i != 0 )
    str = "Error executing gnuplot command.\n"
      "Try setting the environment variable GNUPLOT to the location of "
      "the gnuplot program.";

  free( gopts );

  unlink( temp_file );

  return str;
}


/*
 *----------------------------------------------------------------------
 *
 * SetStart -- Set the earliest time in the graph, so we can
 *             get relative times for successive points. If this is
 *             _not_ called, all times will be absolute.
 *
 * Calls:
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

void Nlv_grSetStart( T this, double startTime )
{
  this->start = startTime;
}

/*
 *----------------------------------------------------------------------
 *
 * GetStart -- Get the earliest time in the graph.
 *
 * Calls:
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

double Nlv_grGetStart( T this )
{
  return this->start;
}

/*
 *----------------------------------------------------------------------
 *
 * GetYBounds -- Get the y-axis graph bounds.
 *
 * Calls: malloc
 *
 * Algorithm: Y-axis goes from 0..number of ticks ('numTicks').
 *
 *----------------------------------------------------------------------
 */

Nlv_Bounds *Nlv_grGetYBounds( T this )
{
  Nlv_Bounds *b = (Nlv_Bounds *)malloc( sizeof(struct Nlv_Bounds) );

  b->x[0] = -1;
  b->x[1] = -1;
  b->y[0] = 0;
  b->y[1] = this->numTicks-1;

  return b;
}

/*
 *----------------------------------------------------------------------
 *
 * GetLabel -- Get the label for the numbered tick. Out-of-range
 *             numbers are assigned a default string.
 *
 * Calls:
 *
 * Algorithm: The ticks[] array points to the keys for each
 *            tick in the graph. This structure, in turn, holds
 *            the name of the key, which we use as the label. 
 *
 *            In order to label min/max for loadlines, we
 *            need to identify whether this is a loadline tick.
 *
 *            NOTE: Returned pointer should NOT be freed by caller!
 *
 *----------------------------------------------------------------------
 */

char *Nlv_grGetLabel( T this, int y )
{
  static char buf[16];
  char *ret;
  Nlv_KeySet *ks_p;


  if ( y >= 0 && y < this->numTicks )
  {
      ret = this->ticks[y]->name;
  }
  else
  {
      sprintf( buf, "%s", "?none?" );
      ret = buf;
  }


  return ret; 
}

/*
 *----------------------------------------------------------------------
 *
 * GetTickScale -- Return a formatted string indicating the
 *                 current scale at this tick.
 *
 *----------------------------------------------------------------------
 */
char *Nlv_grGetTickScale( T this, int y )
{
  static char buf[ 64 ];

  if ( ( y >= 0 && y < this->numTicks ) )
  {
    if ( this->ticks[y]->ks &&
	 ( this->ticks[y]->ks->gtype == NLV_LOAD ||
	   this->ticks[y]->ks->gtype == NLV_POINT )
	 && this->ticks[y]->ks->valField )
    {
	/* Debugging */
	/* printf("valMin=%lf valMax=%lf\n", this->ticks[y]->ks->valMin, this->ticks[y]->ks->valMax ); */

	sprintf( buf, "%.2lf,%.2lf", 
		 this->ticks[y]->ks->valMin,
		 this->ticks[y]->ks->valMax );
    }
    else
      buf[0] = '\0';
  }
  else
    buf[0] = '?', buf[1] = '\0';

  /* Debugging */
  /* printf("%d: Tick scale = %s\n", y, buf ); */

  return buf;
}

/*
 *----------------------------------------------------------------------
 *
 * GetLastTime -- Get time of (end of) last element drawn. 
 *
 *----------------------------------------------------------------------
 */

double Nlv_grGetLastTime( T this )
{
  return this->latest;
}

/*
 *----------------------------------------------------------------------
 *
 * ClearMapping -- De-allocate arrays to map keys to graph ticks.
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

void Nlv_grClearMapping( T this )
{
  int i;

  if ( this->numKeySets < 1 )
    return;

  free( this->offsets );
  this->offsets = NULL;
  free( this->numKeys );
  this->numKeys = NULL;
  for (i=0; i < this->numKeySets; i++ )
    free( this->keyPosY[i] );
  free( this->ticks );
  this->ticks = NULL;
  this->numKeySets = 0;
  this->numTicks   = 0;
}


/*
 *----------------------------------------------------------------------
 *
 * MapKeys -- Set up arrays so a event-set,event combo can be
 *            mapped to a graph tick, and a graph tick can be
 *            mapped to a label.
 *
 * Calls:
 *
 * Algorithm: Allocate arrays, iterate through keys and fill them
 *            in, then correct for invisible elements.
 *
 *            For the keyPosY array, -1 represents invisible and
 *            -2 represents no-such-key.
 *
 *----------------------------------------------------------------------
 */

void Nlv_grMapKeys( T this, Nlv_ky keys )
{
  int i, j, prevnum, invisctr, tickctr;
  Tcl_HashEntry  *entryPtr;
  Tcl_HashSearch  search;
  Nlv_Key        *keyPtr;
  Nlv_KeySet     *keySetPtr;
  Nlv_Key      ***tmp;

  Nlv_grClearMapping( this );

  this->numKeySets = Nlv_kyGetNumKeySets( keys );

  /* 
   * Allocate arrays, set to initial values if necc. 
   */
  this->numKeys = (int *)malloc( this->numKeySets * sizeof(int));
  this->offsets = (int *)malloc( this->numKeySets * sizeof(int) );
  this->keyPosY = (int **)malloc( this->numKeySets * sizeof(int *));
  tmp           = (Nlv_Key ***)malloc( this->numKeySets * sizeof(Nlv_Key **));
  for ( i=0; i < this->numKeySets; i++ ) {
    this->keyPosY[i] = (int *)malloc( this->maxEvents * sizeof(int) );
    tmp[i]           = (Nlv_Key **)malloc( this->maxEvents * sizeof(Nlv_Key *) );
    memset( this->keyPosY[i], -2, this->maxEvents * sizeof(int) );
  }
  this->ticks = (Nlv_Key **)malloc( this->maxTicks * sizeof(Nlv_Key *) );
  memset( this->ticks, 0, this->maxTicks * sizeof(Nlv_Key *));

  /* 
   * Put size of keysets into elements of 'offsets' and 'numKeys' arrays.
   * The 'offsets' array is further modified in the next section. 
   */
  entryPtr = Tcl_FirstHashEntry( Nlv_kyGetKeySetTbl( keys ), &search );
  while( entryPtr != NULL ) {
    keySetPtr = (Nlv_KeySet *)Tcl_GetHashValue( entryPtr );
    if ( keySetPtr->visible ) {
      this->offsets[ keySetPtr->index ] = keySetPtr->numKeys;
      this->numKeys[ keySetPtr->index ] = keySetPtr->numKeys;
    }
    else {
      this->offsets[ keySetPtr->index ] = 0;
      this->numKeys[ keySetPtr->index ] = 0;
    }
    entryPtr = Tcl_NextHashEntry( &search );
  }

  /* 
   * Put event numbers (-maxTicks = invis.) into 'keyPosY' array.
   * Put keyPtrs, temporarily, into 'tmp' array.
   */
  entryPtr = Tcl_FirstHashEntry( Nlv_kyGetKeyTbl( keys ), &search );
  while ( entryPtr != NULL ) {
    keyPtr = (Nlv_Key *)Tcl_GetHashValue( entryPtr );
    if ( keyPtr->visible == 1) {
      this->keyPosY[ keyPtr->ks->index ][ keyPtr->index ] = keyPtr->index;
      tmp[ keyPtr->ks->index ][ keyPtr->index ] = keyPtr;
    }
    else {
      this->keyPosY[ keyPtr->ks->index ][ keyPtr->index ] = -(this->maxTicks);
      this->offsets[ keyPtr->ks->index ]--;
    }
    entryPtr = Tcl_NextHashEntry( &search );
  }

  /* 
   * Calculate actual event numbers, considering invisible keys.
   * Point entries of 'ticks' array at the key.
   */
  tickctr = 0;
  invisctr = 0;
  for ( i=0; i < this->numKeySets; i++ ) {
    for ( j=0; j < this->numKeys[i]; j++ ) {
      if ( this->keyPosY[ i ][ j ] == -(this->maxTicks) ) {
	invisctr++;
      }
      else {
	this->keyPosY[ i ][ j ] -= invisctr;
	this->ticks[ tickctr++ ] = tmp[ i ][ j ];
      }
    }
  }
  this->numTicks = tickctr;

  /*
   * If we have less than 3 ticks, we have to put in
   * some bogus ones
   */
  switch( this->numTicks ) {
  case 0:
    /* Put bogus ticks on graph */
    this->ticks[0] = this->ticks[1] = this->ticks[2] = this->bogus_key;
    this->numTicks = 3;
    break;
  case 1:
    this->ticks[1] = this->ticks[0];
    /* Find visible key and shift it up by one position */
    for ( i=0; i < this->numKeySets; i++ )
      for ( j=0; j < this->numKeys[i]; j++ )
	if ( this->keyPosY[i][j] != -(this->maxTicks) ) {
	  this->keyPosY[i][j]++;
	  break;
	}
    /* Put bogus ticks on graph */
    this->ticks[0] = this->ticks[2] = this->bogus_key;
    this->numTicks = 3;
    break;
  case 2:
    this->ticks[2] = this->ticks[1];
    this->ticks[1] = this->ticks[0];
    /* Find both visible keys and shift each up by one position */
    for ( i=0; i < this->numKeySets; i++ )
      for ( j=0; j < this->numKeys[i]; j++ )
	if ( this->keyPosY[i][j] != -(this->maxTicks) )
	  this->keyPosY[i][j]++;
    /* Put bogus ticks on graph */
    this->ticks[0] = this->ticks[3] = this->bogus_key;
    this->numTicks = 4;
    break;
  default: break;
  }
    

  /* 
   * Calculate offsets, considering invisible keys. 
   */
  *this->offsets = 0;
  for ( i=1; i < this->numKeySets; i++ )
    *(this->offsets + i) = *(this->offsets + i-1) + *(this->numKeys + i-1);

  /* 
   * Free the temporary array ('tmp') that held keyPtrs 
   */
  for ( i=0; i < this->numKeySets; i++ )
    free( (Nlv_Key **)tmp[i] );
  free( (Nlv_Key ***)tmp );

  return;
}

/*
 *----------------------------------------------------------------------
 *
 * GetIndexAtTick --
 *
 *   Retrieve the actual index in the keyset, whatever keyset
 *   that may be, of a given tick. The tick is checked to make
 *   sure that it is in range. -1 is used as a "no such" return value.
 *
 *----------------------------------------------------------------------
 */

int Nlv_grGetIndexAtTick( T this, int tickNum )
{
  Nlv_Key *keyPtr;

  if ( tickNum < 0 || tickNum >= this->numTicks )
    return -1;

  keyPtr = this->ticks[tickNum]; 

  if ( keyPtr == NULL )
    return -1;

  return keyPtr->index;
}

/*
 *----------------------------------------------------------------------
 *
 * Decolor -- Make all elements in mem. dashed and black.
 *
 * Calls:
 *
 * Algorithm: Loop through, set dashes = client_num*2 + 1
 *
 *----------------------------------------------------------------------
 */

int Nlv_grDecolor( T this )
{
  char buf [512], *c;
  int i,j, dfactor;
  Nlv_EInfo einfo;
  int status = TCL_OK;
  
  c = Tcl_GetVar2( Et_Interp, "param", "DashFactor", TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG );
  if ( !c )
    status = TCL_ERROR;
  else {
    Tcl_GetInt( Et_Interp, c, &dfactor );
    for ( i=0; i < NLV_NUM_ELEMENT_TYPES; i++ )
      for ( j=this->memStart[i]; status == TCL_OK && j < this->memEnd[i]; j++ )
	status = dashElement( this, i, j, dfactor );
  }

  return status;
}

/*
 *----------------------------------------------------------------------
 *
 * Recolor -- Make the elements colored again.
 *
 * Calls:
 *
 * Algorithm: Loop through all elements.
 *
 *----------------------------------------------------------------------
 */

int Nlv_grRecolor( T this )
{
  int i,j;
  int status = TCL_OK;

  for ( i=0; i < NLV_NUM_ELEMENT_TYPES; i++ )
    for ( j=this->memStart[i]; status == TCL_OK && j < this->memEnd[i]; j++ ) {
      status = colorElement( this, i, j, this->clData[this->memArr[i][j]]->colorIdx );
      if ( status == TCL_OK )
	status = dashElement( this, i, j, 0 );
    }

  return status;
}

/*
 *----------------------------------------------------------------------
 *
 * dashElement --
 *
 *     Set the "dash-spacing" of a graph element according to the
 *     last integer argument.
 *
 *----------------------------------------------------------------------
 */

int dashElement( T this, int type, int elt, int dfactor )
{
  static char buf[512];
  int status = TCL_OK;
  int ndashes;

  ndashes = this->elements[type][elt].lastColor * dfactor;
  if ( ndashes > 256 )
    ndashes = 256;

  sprintf( buf, "%s element configure %dt%d -dashes %d",
	   this->widget,
	   type,
	   elt,
           ndashes );

  status = Tcl_Eval( Et_Interp, buf );
  if ( status != TCL_OK )
    fprintf(stderr, "Unable to make dashed line: %s", Tcl_GetStringResult(Et_Interp) );

  return status;
}


/*
 *----------------------------------------------------------------------
 *
 * MarkAsAnnotated -- Mark this element as annotated by turning on
 *                    a little flag. 
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
void
Nlv_grMarkAsAnnotated( T this, int ty, int idx )
{
  if ( ty < 0 || ty >= NLV_NUM_ELEMENT_TYPES ||
       idx < 0 || idx >= this->maxElements )
    return;
  this->elements[ty][idx].annflag = 1;
}


/*
 *----------------------------------------------------------------------
 *
 * SetClientVisible -- Set client 'visibility' on graph.
 *
 * Calls    : strcmp
 *
 * Algorithm: Linear search and set all matching host-names
 *
 *----------------------------------------------------------------------
 */

void Nlv_grSetClientVisible( T             this,
			     char         *nm,
			     unsigned char newVis )
{
  int i;

  /* printf("<dbg> set client '%s' visible = %d\n", nm, (int)newVis ); */

  for ( i=0; i < this->numClients; i++ ) {
    if ( strcmp( this->clData[i]->hostName, nm ) == 0 )
      this->clData[i]->visible = newVis;
  }
}


/*
 *----------------------------------------------------------------------
 *
 * GetClientVisible -- Get client 'visibility' on graph.
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int Nlv_grGetClientVisible( T this, int clIndex )
{
  return (int)this->clData[clIndex]->visible;
}

/*
 *----------------------------------------------------------------------
 *
 * KeepReading -- Should we continue reading, given the current window
 *                and what we know about the latest time read?
 *
 * Calls:
 *
 * Algorithm: 
 *            - NO : > N full windows past right edge
 *            - YES: otherwise.
 *
 *----------------------------------------------------------------------
 */
int Nlv_grKeepReading( T this, Nlv_Bounds *b )
{
  double cutoff, lastread;

  cutoff = b->x[1] + (NLV_EXTRA_FORWARD)*(b->x[1] -  b->x[0]);
  lastread = Nlv_grRealTime( this, (this->latest));

  return !( lastread > cutoff );
}

/*
 *----------------------------------------------------------------------
 *
 * KeepScrolling -- Should we continue scrolling the graph time,
 *                  given the current window and what we know about
 *                  the latest time read?
 *
 * Calls:
 *
 * Algorithm: 
 *            - NO : window empty
 *            - YES: otherwise.
 *
 *----------------------------------------------------------------------
 */
int Nlv_grKeepScrolling( T this, Nlv_Bounds *b )
{
  if ( this->latest > 0 && 
       b->x[1] > Nlv_grRealTime( this, (this->latest)) )
    return 0;
  return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GetHostIndex -- Get a unique index for a host name. We need to
 *                 keyset, because each keyset is allowed a set of
 *                 host aliases.
 *
 * Calls    : strcmp
 *
 * Algorithm: Linear search through client list.
 *            Set the newHost flag if not found.
 *
 *----------------------------------------------------------------------
 */
int Nlv_grGetHostIndex( T this, char *nm, Nlv_KeySet *ksPtr )
{
  int i, max;

  for ( i=0; i < this->numClients; i++ ) {
    if ( strcmp( this->clData[i]->hostName, nm ) == 0 )
      return this->clData[i]->hostIndex;
  }
  this->newHost = 1;
  return this->numHosts++;
}

/*
 *----------------------------------------------------------------------
 *
 * GetHostName -- Get host name at client index.
 *
 *----------------------------------------------------------------------
 */
char *Nlv_grGetHostName( T this, int idx )
{
  if ( idx >= 0 && idx < this->numClients )
    return this->clData[idx]->hostName;
  return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * IsNewHost -- Is the host index newly added?
 *
 * Calls    :
 *
 * Algorithm: Check the newHost flag, dummy.
 *
 *----------------------------------------------------------------------
 */
int Nlv_grIsNewHost( T this, int idx )
{
  if ( this->newHost ) {
    this->newHost = 0;
    return 1;
  }
  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * SetLatest -- Set latest time.
 *
 * Calls    :
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

void Nlv_grSetLatest( T this, double t )
{
  this->latest = t;
}

/*
 *----------------------------------------------------------------------
 *
 * SetMaxElements -- Set the maximum # of elements. This must be followed
 *                   by a call to Nlv_grInit to be effective.
 *
 * Calls    :
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

void Nlv_grSetMaxElements( T this, int max )
{
  this->maxElements = max;
}

/*
 *----------------------------------------------------------------------
 *
 * GetBufferSize -- Max-#elements * #types
 *
 *----------------------------------------------------------------------
 */

int Nlv_grGetBufferSize( T this )
{
  return (int)( this->maxElements * NLV_NUM_ELEMENT_TYPES );
}

/*
 *----------------------------------------------------------------------
 *
 * SetSymbols -- Set the graph symbols for element type index
 *               given as 1st argument to the string given as the
 *               second argument.
 *
 * Calls    : Tcl_Eval
 *
 * Algorithm: Run through configuring elements: NtE, 
 *               where N=type # (1st arg) and E=elt #
 *            After an element is configured, update our record
 *               of its address in our elements[][] array.
 *            Return 0 on success, -1 otherwise.
 *
 *----------------------------------------------------------------------
 */
int  Nlv_grSetSymbols( T this, int typeNum, char *symStr )
{
  int i;
  char buf[256];

  if ( typeNum < 0 || typeNum >= NLV_NUM_ELEMENT_TYPES )
    return -1;
  for ( i=0; i < this->maxElements; i++ ) {
    sprintf(buf,"%s element configure %dt%d -symbol %s",
	    this->widget,
            typeNum, i, symStr );
    if ( TCL_OK != Tcl_Eval(Et_Interp,buf) ) {
      fprintf(stderr,"Nlv_grSetSymbols: Cannot configure element %dt%d. Aborting.\n",
	      typeNum, i );
      return TCL_ERROR;
    }
    strcpy( this->elements[typeNum][i].sym, symStr );
  }
  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * SetHighlights -- Set the symbols for the list of file positions
 *                  to the given string
 *
 *
 * Calls    :
 *
 * Algorithm: Some optimizations are performed based on the
 *            assumption that the file positions for the list
 *            are in a much smaller range than the file positions
 *            in memory.
 *
 *            Return 0 on success, -1 otherwise.
 *
 *----------------------------------------------------------------------
 */
int  Nlv_grSetHighlights( T this,
		          long *list,
		          int   size,
		          char *symStr )
{
  int            i, j, k, m;
  int            drawnPoints;
  int            typeNum;
  int            hiNum;
  long           min, max;
  EltArrEntry   *slotPtr;
  long          *positions;
  char          *matchedListCmd;
  char           matchElt[ 8 ];
  Tcl_HashEntry *entryPtr;
  Nlv_EInfo      einfo;

   if ( size < 1 || list == NULL || symStr == NULL )
     return -1; 

   /* 
    * Prepare highlight object
    */

   Nlv_hiSetSymbol( this->highlight, symStr );
   Nlv_hiClearAll( this->highlight ); 

   for ( min = max = list[0], i=1; i < size; i++ )
     if ( min > list[i] )
       min = list[i]; 
     else if ( max < list[i] )
       max = list[i]; 

   /* 
    * Allocate matched element list 
    */

   matchedListCmd = (char *) malloc( size * 7 + 20 ); 
   strcpy( matchedListCmd, "ColorHighlights {"); 


   /*
    * Main loop: For each element, type, point: compare to
    * each file position in list.
    */

   for ( i=0; i < this->maxElements; i++ ) {
     for ( typeNum=0; typeNum < NLV_NUM_ELEMENT_TYPES; typeNum++ ) {
       slotPtr = &(this->elements[typeNum][i]); 
       positions = slotPtr->vecFp; 
       drawnPoints = slotPtr->addr->x.numValues; 
       for( j=0; j < drawnPoints && j < slotPtr->n ; j++ ) {
 	if ( positions[j] >= min && positions[j] <= max ) {
 	  for (k=0; k < size; k++) { 

 	    /* 
             * If there is a match, add a highlighted element 
 	     *  And remove the match from the input list 
             */

 	    if ( list[k] == positions[j] ) {
 	      hiNum = Nlv_hiAddCoords( this->highlight, 
 			 slotPtr->addr->x.valueArr[j], 
 			 slotPtr->addr->y.valueArr[j] );
 	      sprintf( matchElt, "%dt%d ", typeNum, i );
               Nlv_hiSetColor( this->highlight, hiNum, matchElt ); 
 	      strcat( matchedListCmd, matchElt ); 
 	      for( m = k; m < size-1; m++ ) 
 		list[m] = list[m+1]; 
 	      if ( --size == 0 ) 
 		return 0; 
 	    }
 	  }
 	}
       }
     }
   }

   strcat( matchedListCmd, "}" ); 
   if ( TCL_OK != Tcl_Eval( Et_Interp, matchedListCmd ) ) { 
     NLV_SET_EINFO( einfo, 50, 10 ); 
     Nlv_errReport( eobj, &einfo, "Tcl error while changing colors" 
 		   " for highlighted elements"); 
   } 

   Nlv_hiDraw( this->highlight ); 

   free( matchedListCmd ); 

  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ClearHighlights -- Clear all the highlighted spots on the
 *                    display.
 *
 *----------------------------------------------------------------------
 */
void Nlv_grClearHighlights( T this )
{
  Nlv_hiClearAll( this->highlight );
  Nlv_hiDraw( this->highlight );
}


/*
 *----------------------------------------------------------------------
 *
 * GetFilePos -- Get the file position given the ordered triple:
 *               (element type, element #, y-position )
 *
 * Calls    :
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

long Nlv_grGetFilePos( T this,
		       int ty,
		       int elnum,
		       int ypos )
{
  if ( ty < 0 || ty >= NLV_NUM_ELEMENT_TYPES ) {
    fprintf(stderr,"Bad element type: %d\n", ty);
    return -1L;
  }
  if ( elnum < 0 || elnum > this->maxElements ) {
    fprintf(stderr,"Bad element number: %d\n", elnum);
    return -1L;
  }
  /*
   * By commenting this out, we ignore the actual element
   * in the sequence and give only annotation for a
   * keyset at the time, using the 1st element in the keyset.
   * This is not ideal, but avoids the current problem in
   * which the vectors are missing elements.
   *
   *if ( ypos < 0 || ypos > this->elements[ty][elnum].n ) {
   *fprintf(stderr,"Element position out of range: %d\n",ypos);
   *return -1L;
   *}
   * return this->elements[ty][elnum].vecFp[ypos];
   */
  return this->elements[ty][elnum].vecFp[0];
}

/*
 *----------------------------------------------------------------------
 *
 * GetScaleFactor --
 *
 *      Get the scaling factor for display of times.
 *        1000 == milliseconds
 *        1    == seconds
 *
 * Calls    :
 *
 * Algorithm: Accessor.
 *
 *----------------------------------------------------------------------
 */

double Nlv_grGetScaleFactor( T this )
{
  return this->scale;
}

char * Nlv_grGetWidget( T this ) 
{
  return this->widget;
}

/*
 *----------------------------------------------------------------------
 *
 * ResetAllElements --
 *
 *      Reset the addresses of all elements.
 *
 * Calls    : resetElementAddress
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int Nlv_grResetAllElements( T this )
{
  int status = TCL_OK;
  int element, type;

  for ( type=0; type < NLV_NUM_ELEMENT_TYPES; type++ )
    for ( element=0; status == TCL_OK && element < this->maxElements; element++ )
      status = resetElementAddress( this, type, element );

  return status;
}
/*
 *----------------------------------------------------------------------
 *
 * UpdateDisplay --
 *
 *      Update BLT graph widget.
 *
 *----------------------------------------------------------------------
 */
void Nlv_grUpdateDisplay( T this ) 
{
 Blt_EventuallyRedrawGraph( this->graphHandle ); 
}

/*
 *----------------------------------------------------------------------
 *
 * GetGraphPtr -- 
 *
 *    Get a pointer to  the BLT graph widget used throughout
 *    graphclass.c  
 *
 *----------------------------------------------------------------------
 */
Graph *Nlv_grGetGraphPtr( T this ) 
{
  return this->graphHandle;
}

/*
 *----------------------------------------------------------------------
 *
 * GetElementColorString -- 
 *
 *    Get string representing color for element at type,index, and
 *    copy the result into the supplied buffer.
 *
 *----------------------------------------------------------------------
 */
void Nlv_grGetElementColorString( T this, int type, int idx, char *buf )
{
    char *p;
    int colorIndex;

    colorIndex = this->elements[type][idx].lastColor;
    p = (char *) Tcl_GetString( this->palette.objvArr[colorIndex][5] );
    strcpy( buf, p );
}

/*
 *----------------------------------------------------------------------
 *
 * GetAutoResize -- Get the value of the auto-resize flag.
 *
 * RTRN: 1 = yes, auto-resizing is occurring, 0 = no, 'taint.
 *
 *----------------------------------------------------------------------
 */
int Nlv_grGetAutoResize( T this )
{
  return this->autoresize;
}

/*
 *----------------------------------------------------------------------
 *
 * SetAutoResize -- Set the value of the auto-resize flag.
 *
 * PRE : The caller must have 'control' of the graph. This is not
 *       checked for by this function.
 * IN  : 1 = turn on auto-resizing, 0 = turn it off.
 * RTRN: -nil-
 *
 *----------------------------------------------------------------------
 */
void Nlv_grSetAutoResize( T this, int val )
{
  this->autoresize = val;
}

/*
 *----------------------------------------------------------------------
 *
 * GetWindow -- Get the window size.
 *
 * Calls    :
 *
 * Algorithm:  Accessor.
 *
 *----------------------------------------------------------------------
 */
Nlv_Bounds *Nlv_grGetWindow( T this )
{
  return &this->window;
}
/*
 *----------------------------------------------------------------------
 *
 * SetWindow -- Set the window size.
 *
 * Calls    :
 *
 * Algorithm:  Ignore y-axis, for now.
 *
 *----------------------------------------------------------------------
 */
void Nlv_grSetWindow( T this, Nlv_Bounds *b )
{
  this->window.x[0] = b->x[0];
  this->window.x[1] = b->x[1];
}


/*
 *----------------------------------------------------------------------
 *
 * private: diff_from__window --
 *
 *   Compare input to width of graph's time window 
 *   (stored in Nlv_an class).
 *
 * PRE: input slot is valid
 * IN : this, slot
 * OUT: -nil-
 * RTN: line length - time window width
 *
 *----------------------------------------------------------------------
 */
double
diff_from_window( T this, EltArrEntry *slot )
{
  Nlv_Bounds *boundsPtr;
  double      width;
  ElemVector *inp_data;

  double      inp_width;

  inp_data  = (ElemVector *) &slot->addr->x ;
  inp_width = Nlv_grRealTime(this, Blt_VecMax(inp_data)) - Nlv_grRealTime(this, Blt_VecMin(inp_data));
  width     = this->window.x[1] - this->window.x[0];
  /* printf("<dbg>line width %lf - window width %lf = %lf\n",inp_width, width, (inp_width - width) ); */

  return inp_width - width;
}

/*
 *----------------------------------------------------------------------
 *
 * private: modify_time_window --
 *
 *   Add given value, in the same units as the current time window,
 *   to the current time window by stretching the RIGHT edge. 
 *
 *   Update Tcl's variable param(TimeWindow).
 *
 * PRE: Et_Interp is valid (global)
 * IN : this, addend
 * OUT: window is modified in C and Tcl
 * RTN: Tcl return integer (TCL_OK, TCL_ERROR)
 *
 *----------------------------------------------------------------------
 */

int
modify_time_window( T this, double modval )
{
  int r;
  char buf[128];

  sprintf( buf, "IncreaseTimeWindow %lf", modval );
  /* printf("<dbg>modify_time_window tcl command: %s\n", buf ); */
  r = Tcl_Eval( Et_Interp, buf );

  return r;
}

#undef T
#undef FILE_ERR_CODE

