/****************************************************************** 
 *
 * main.c --
 *
 *	All the interfaces between the Tcl code and the C classes
 *      for NLV.
 *
 *      Below is an explanation of each procedure. In general,
 *      the naming convention is:
 *
 *         ET_COMMAND_xyz(ET_TCLARGS) 
                  -- A command which may be invoked from Tcl
 *                   as 'Xyz'.
 *
 *         Abc    -- Procedure only used within this module,
 *                   which cannot be invoked from Tcl.
 *
 *
 * $Author: mstoufer $
 * $Date: 2000/12/05 19:52:32 $
 * $Id: main.c,v 1.1.1.1 2000/12/05 19:52:32 mstoufer Exp $
 * 
 *
 * Modification history
 * --------------------
 * 21 July 1998 : Threaded the indexing. (DG)
 *
 ****************************************************************** 
 */
/* RCS identification */
static char rcsid[] = "$Id: main.c,v 1.1.1.1 2000/12/05 19:52:32 mstoufer Exp $";

#include "main.h"

/* Tcl interpreter, defined in mytkAppInit.c */
extern Tcl_Interp *Et_Interp;

/* Globals */
Nlv_State nlvState;
double slpos = -1; /* SLfunctions.. */
int initialized = 0;
int Nlv_FileSpinning = 0;
double Nlv_LastT = -1;
Nlv_err eobj;
Nlv_err eobj2;

#ifndef NLV_DEBUG_LVL
#define NLV_DEBUG_LVL -5
#endif
#define FILE_ERR_CODE 1200
#define NLV_LOGLINE_FREED -1
#define NLV_LOGLINE_NOTFREED -2

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

double ProcessLogLine( LogLine logline, int flags );
void   ReadWindow( Nlv_Bounds *bounds );

#define NLV_LOG_GRAPH   (1 << 0)
#define NLV_LOG_INDEX   (1 << 1)
#define NLV_LOG_ALL (NLV_LOG_GRAPH | NLV_LOG_INDEX )

/*
 *----------------------------------------------------------------------
 *
 * Init -- Initialize NLV.
 *
 * Input: argv[1] = home, argv[2] = logfile name
 *
 * Calls:
 *
 * Algorithm:
 *            - Initialize objects for each class.
 *            - Start threads (index thread,..)
 *            - Open log and index files.
 *            - Initialize the keysets from a file, and then
 *              place this data in C structures and invoke
 *              the graph class to map the keysets to the graph.
 *            - Initialize the graph state.
 *
 *----------------------------------------------------------------------
 */

#define HOME_ARG 1
#define FNAME_ARG 2
#define MAXELTS_ARG 3
#define NUM_ARG 4

int ET_COMMAND_Init( ET_TCLARGS )
 
{ 
  static int num_times_called = 0;
  char       buf[ 256 ];
  char       oneline[ NLV_MAXLINE ];
  double     tm;
  sigset_t   blocked_sigs;

  if ( argc != NUM_ARG ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl Init command.");
    return -1;
  }

  switch( num_times_called++ ) {
  case 0:
    /* Threads stuff */
#ifdef sun
    thr_setconcurrency( NLV_NUM_THREADS );
#endif
    
    /* Init "objects" */
    nlvState.ix = (struct Nlv_ix*)Nlv_ixNew();
    nlvState.fi = (struct Nlv_fi*)Nlv_fiNew();
    nlvState.pa = (struct Nlv_pa*)Nlv_paNew();
    nlvState.cl = (struct Nlv_cl*)Nlv_clNew();
    nlvState.gr = (struct Nlv_gr*)Nlv_grNew();
    nlvState.ky = (struct Nlv_ky*)Nlv_kyNew();
    eobj        = (struct Nlv_err*)Nlv_errNew();
    eobj2       = (struct Nlv_err*)Nlv_errNew();
    
    /* Set the debugging level */
    Nlv_errSetDebug( eobj, NLV_DEBUG_LVL );
    Nlv_errSetDebug( eobj2, NLV_DEBUG_LVL );
    
    /* Read keyset from file */
    sprintf( buf, "%s/%s", argv[HOME_ARG], "nlv-keys.cfg" );
    Nlv_kyFromFile( nlvState.ky, buf );
    
    /* Init the graph */
    printf("Please wait a few moments for graph initialization..\n");
    do {
      int max = atoi(argv[MAXELTS_ARG]);
      if ( max < 5 ) {
	fprintf(stderr,"Warning: Less than 5 graph elements "
		"specified with 'MaxGraphElements' in nlv-main.cfg: %d ."
		"Defaulting to %d\n", max, NLV_MAX_ELEMENTS );
	max = NLV_MAX_ELEMENTS;
      }
      Nlv_grSetMaxElements( nlvState.gr, max );
      Nlv_grInit( nlvState.gr, ".graph" );
    } while(0);
    
    /* Put the data into C structures and the graph class */
    Nlv_clSetKeyData( nlvState.cl, nlvState.ky );
    Nlv_grMapKeys( nlvState.gr, nlvState.ky );
    break;

  case 1:
    /* Open file(s) */
    Nlv_fiOpen( nlvState.fi, argv[FNAME_ARG] );
    
    /* Read one line of data from file */
    printf("Getting time for first line in file..\n");
    memset(oneline,0,sizeof(oneline));
    do {
      Nlv_fiGetLine( nlvState.fi, &oneline[0]);
      if ( oneline[0] == '\0' ) {
	NLV_FATAL_S("Cannot read line from log file");
	exit(1);
      }
      Nlv_paGetLineTime( oneline, &tm );
    }  while ( tm < 0 );
    Nlv_fiSeek( nlvState.fi, 0L );
    Nlv_ixSetStart( nlvState.ix, tm );
    Nlv_grSetStart( nlvState.gr, tm );
    
    initialized = 1;
    break;
  }

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexerInit -- Start indexing thread with a call from Tcl.
 *
 * Input:
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_IndexerInit( ET_TCLARGS )
 
{
  Nlv_ixArgs *ixarg;  /* indexer thread argument structure */ 
  char       *d_ival; /* string for density interval from Tcl */
  int        status;
  Nlv_EInfo  dbginfo;
  Nlv_EInfo  einfo;

  NLV_SET_EINFO( dbginfo, NLV_DEBUG_LVL, 0 );

  if ( argc != 2 ) {
    sprintf( Et_Interp->result, "Wrong number of arguments in IndexerInit" );
    return TCL_ERROR;
  }

  /* grab density interval from Tcl */
  if ( !(d_ival = Tcl_GetVar2( Et_Interp, "param", "summaryinterval", TCL_GLOBAL_ONLY )) ) {
    NLV_SET_EINFO( einfo, FILE_ERR_CODE, 0 );
    Nlv_errReport( eobj, &einfo, "Unable to get summary-line interval from Tcl" );
    NLV_FATAL_S("Error while initializing indexer");
    return TCL_ERROR;
  }
  Nlv_errReport( eobj2, &dbginfo, "Using summary interval of %d ms", atoi(d_ival) );

  if ( 0 != Nlv_ixInit( nlvState.ix, 1.0, atoi(d_ival), argv[1] ) ) {
    NLV_FATAL_S("Error while initializing indexer");
    return TCL_ERROR;
  }

  ixarg = (Nlv_ixArgs *)malloc( sizeof(Nlv_ixArgs) );
  ixarg->obj = nlvState.ix;
  printf("Starting indexer thread..\n");
  status = pthread_create( &(nlvState.ixTid), 
			   Nlv_ixGetTattr(nlvState.ix), 
			   Nlv_ixRun, 
			   (void *)ixarg );
  if ( status != 0 ) {
    NLV_FATAL_I( status );
    return TCL_ERROR;
  }

  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * FileHandler -- Read data from the file, parse, and graph it. The
 *                signature of this procedure is dictated by Tcl.
 * Input:
 *
 * Calls: ProcessLogLine
 *
 * Algorithm:
 *             - If we are way off the window, do nothing (return)
 *             - Read from log file.
 *                * When we hit EOF, do a slow spin
 *                * On wakeup from the spin, try to jump ahead in the
 *                  file.
 *             - Let ProcessLogLine do all the dirty work.
 *
 *----------------------------------------------------------------------
 */
void FileHandler( ClientData clientData,
		  int mask )
{
  double        t, 
                real_t,
                win_t;
  LogLine       logline;
  Nlv_EInfo     einfo, 
                dbginfo;
  Nlv_Bounds   *winBoundsPtr;
  char          wakeup[ 128 ];
  static char   buf[ NLV_MAXLINE ];

  if ( ! Nlv_grKeepReading(nlvState.gr, Nlv_grGetWindow(nlvState.gr)) ) {
    select_sleep( 0, NLV_SPIN_LENGTH );
    return;
  }

  if ( Nlv_FileSpinning == 1 ) {
    select_sleep( 0, NLV_SPIN_LENGTH );
  }

  logline = buf;
  *logline = '\0';
  Nlv_fiGetLine( nlvState.fi, logline );
  if ( *logline == '\0' ) {
    Nlv_FileSpinning = 1;
    return;
  }
  if ( ProcessLogLine(logline, NLV_LOG_ALL) < 0 )
    return;

  /*
   * Tell the graph scroller in nlv.tcl to
   * wakeup if (a) it is paused on eof when data arrives,
   * or (b) if the distance between this data and the
   * last data is > NLV_SKIP_WIN*window.
   */
  real_t       = Nlv_grGraphTime( nlvState.gr, t );
  winBoundsPtr = Nlv_grGetWindow(nlvState.gr);
  win_t        = winBoundsPtr->x[1] - winBoundsPtr->x[0];
  if ( Nlv_FileSpinning > 0 ) {
    /* printf("<dbg>wakeup %lf because file was spinning\n", real_t); */
    sprintf( wakeup, "WakeUp %lf", real_t - win_t / 2 );
    Tcl_Eval( Et_Interp,wakeup );
    Nlv_FileSpinning = 0;
  }
  else if ( Nlv_LastT > -0.9 &&
            (real_t - Nlv_LastT) > ( NLV_SKIP_WIN * win_t ) ) {
    /* printf("<dbg>wakeup %lf because we're skipping data (Nlv_LastT=%lf,win_t=%lf)\n", real_t, Nlv_LastT, win_t); */
    sprintf( wakeup, "WakeUp %lf %lf", real_t, Nlv_LastT );
    Tcl_Eval( Et_Interp, wakeup );
    Nlv_LastT = real_t;
  }
  else if ( Nlv_LastT < real_t ) {
    /* printf("<dbg>Nlv_LastT = %lf win_t=%lf real_t=%lf\n", Nlv_LastT, win_t, real_t ); */
    Nlv_LastT = real_t;
  }
}


/*
 *----------------------------------------------------------------------
 *
 * LastFileTime-- Return the last parse-able time in a file. 
 *
 * Input:
 *
 * Calls: Nlv_fiLineStartR, Nlv_fiSeek, Nlv_fiSeekEOF, Nlv_fiGetPos,
 *        Nlv_paProcess, Nlv_paGetFields, Nlv_clProcess,
 *        Nlv_clGetGraphData, Nlv_clGetAnalysisData, malloc, free 
 *
 * Algorithm:
 *             - If the 1st line of the file has not yet been read,
 *               return '0' (we can't make a relative time yet..)
 *             - Start at EOF
 *            1- Find the last EOL
 *             - Read line after EOL
 *             - If line was complete, parse it
 *             - Else set pos=(last-EOL - 1), goto 1
 *             - Repeat until successful or no more EOL (error).
 *             - Convert time to graph-axis time before returning.
 *             - If this is < old param(LastFileTime), return old-val
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_LastFileTime( ET_TCLARGS )
 
{
  LogLine      logline;
  static char  logbuf[ NLV_MAXLINE ];
  Nlv_Fields  *fldPtr;
  Nlv_GData   *gdataPtr;
  Nlv_Client  *clPtr;
  char *oldStr;
  long pos, origpos;
  double time         = -1.;
  double old;
  Nlv_EInfo einfo;

  /* Remember where we are, then go to EOF */
  origpos = Nlv_fiGetPos( nlvState.fi );
  Nlv_fiSeekEOF( nlvState.fi );

  /* Main loop: Until we get a real time */
  while( time < 0. && Nlv_fiLineStartR( nlvState.fi ) == 0 ) {
    pos = Nlv_fiGetPos( nlvState.fi );
    logline = logbuf;
    *logline = '\0';
    Nlv_fiGetLine( nlvState.fi, logline );
    if ( *logline != '\0' )
      Nlv_paGetLineTime2( logline, &time );
    if ( time < 0. )
      Nlv_fiSeek( nlvState.fi, pos-2 );
  }

  Nlv_fiSeek( nlvState.fi, origpos );
  time = Nlv_grGraphTime( nlvState.gr, time );
  printf("LastFileTime=%lf\n",time);
  sprintf( Et_Interp->result, "%lf", time );
  return TCL_OK;  
}

/*
 *----------------------------------------------------------------------
 *
 * LastReadTime -- Return time of last data read.
 *
 * Input:
 *
 * Calls: Nlv_grGetLastTime
 *
 * Algorithm: Request info from graph class.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_LastReadTime( ET_TCLARGS )
 
{
  double t = Nlv_grGetLastTime( nlvState.gr );
  sprintf( Et_Interp->result, "%lf", t );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexTime -- Position of index thread, in terms of time.
 *
 * Input:
 *
 * Calls    : Nlv_ixGetShowPos
 *
 * Algorithm: Get the position, render to seconds, and return.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_IndexTime( ET_TCLARGS )
{
  double t = Nlv_ixGetShowPos( nlvState.ix );
  sprintf( Et_Interp->result, "%lf", t );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SlowdownDisplay -- YES/NO answer
 *
 * Input:
 *
 * Calls    : Nlv_grKeepScrolling, Nlv_grGetWindow
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_SlowdownDisplay( ET_TCLARGS )
{
  if ( 0 == Nlv_grKeepScrolling(nlvState.gr, Nlv_grGetWindow(nlvState.gr)) )
    sprintf( Et_Interp->result, "%d", 1 );
  else
    sprintf( Et_Interp->result, "%d", 0 );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PauseFile -- Pause the file currently being "played" on NLV.
 *
 * Input:
 *
 * Calls: Tk_CreateFileHandler
 *
 * Algorithm: Put a mask of '0' in Tk_CreateFileHandler.
 *            Flush the buffers of half-drawn lines.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_PauseFile( ET_TCLARGS )
 
{ 
  Tk_CreateFileHandler( Nlv_fiGetFd( nlvState.fi ), 0, FileHandler, NULL );
  Nlv_FileSpinning = 0;
  Nlv_grFlush( nlvState.gr, 1 );
  Nlv_ixIncreasePriority( nlvState.ix );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GraphFlush -- 
 *
 * Input:
 *
 * Calls: 
 *
 * Algorithm: Flush the buffers of half-drawn lines.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_GraphFlush( ET_TCLARGS )
 
{ 
  Nlv_grFlush( nlvState.gr, 1 );
  return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 *  GraphToggleAutoResize --
 *
 *  Algorithm: Request info from graph class.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_GraphToggleAutoResize( ET_TCLARGS )
{
  if ( ! Nlv_grGetAutoResize(nlvState.gr) ) 
    Nlv_grSetAutoResize(nlvState.gr, 1);
  else
    Nlv_grSetAutoResize(nlvState.gr, 0);
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PlayFile -- Play the file currently paused in NLV.
 *
 * Input:
 *
 * Calls: Tk_CreateFileHandler
 *
 * Algorithm: Put a mask of TK_READABLE in Tk_CreateFileHandler.
 *            Flush line buffers (no draw).
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_PlayFile( ET_TCLARGS )
{ 
  Tk_CreateFileHandler( Nlv_fiGetFd( nlvState.fi ), TK_READABLE, FileHandler, NULL );
  Nlv_grFlush( nlvState.gr, 0 );
  Nlv_grSetLatest( nlvState.gr, -1. );
  Nlv_ixNormalPriority( nlvState.ix );
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * RemapKeys -- Map the keys again.
 *
 * Input:
 *
 * Calls: Nlv_kyKeysFromTcl, Nlv_grMapKeys
 *
 * Algorithm: Set the keys from Tcl vars into the C
 *            data structure, then remap.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_RemapKeys( ET_TCLARGS )
 
{
  Nlv_kyKeysFromTcl( nlvState.ky );
  Nlv_grMapKeys( nlvState.gr, nlvState.ky );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetKeys -- Put the user-modifiable aspects of the keysets
 *               into appropriate Tcl variables. This is called by
 *               keydlg.tcl, which gives the user buttons to 
 *               modify the keys with
 *
 * Input: std.
 *
 * Calls: Nlv_kyKeysToTcl
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_GetKeys( ET_TCLARGS )
{
  Nlv_kyKeysToTcl( nlvState.ky );
}

/*
 *----------------------------------------------------------------------
 *
 *
 * JumpTime
 *
 * Input :
 * Output: Time to jump to, or 0
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_JumpTime( ET_TCLARGS )
 
{ 
  double t = Nlv_ixGetJumpTime( nlvState.ix );

  if ( t > 0 ) {
    t = Nlv_grGraphTime( nlvState.gr, t );
    sprintf(Et_Interp->result,"%lf",t);
    Nlv_ixClearJumpTime( nlvState.ix );
  }
  else
    Et_Interp->result="0";
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * WindowChanged -- Stuff to do when the window changes,
 *                     while playing the file forwards.
 *
 * Input    : argv[1]=lower bound  ) time of event
 *            argv[2]=upper bound  )
 *            argv[3]=update mode: 'D'ynamic (playing), 'S'tatic (paused).
 *
 * Calls    :
 *
 * Algorithm: 
 *             - Set the new window. (analysis routine has only copy!)
 *             - If this is static mode, read in data for window, in
 *               dynamic mode the data is already there.
 *             - Analyze window.
 *             - Clear either stale lines (dynamic) or all lines (static).
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_WindowChanged( ET_TCLARGS )

{
  Nlv_Bounds b, relB;
  Nlv_EInfo dbginfo;

  NLV_SET_EINFO( dbginfo, NLV_DEBUG_LVL, 0 );

  if ( argc != 4 ) {
    sprintf( Et_Interp->result, "Wrong number of arguments in WindowChanged" );
    return TCL_ERROR;
  }

  sscanf( argv[1], "%lf", &relB.x[0] );
  sscanf( argv[2], "%lf", &relB.x[1] );
  b.x[0] = Nlv_grRealTime( nlvState.gr, relB.x[0] );
  b.x[1] = Nlv_grRealTime( nlvState.gr, relB.x[1] );

  switch ( *argv[3] ) {
  case 'D' :
    Nlv_errReport( eobj, &dbginfo, "Dynamic window changed" );
    Nlv_grSetWindow( nlvState.gr, &b );
    break;
  case 'S':
    Nlv_errReport( eobj, &dbginfo, "Static window changed" );
    Nlv_grSetWindow( nlvState.gr, &b );
    if ( !Nlv_grInMem( nlvState.gr, &relB ) ) {
      Nlv_grClearMem( nlvState.gr );
      Nlv_errReport( eobj, &dbginfo, "call ReadWindow" );
      ReadWindow( &b );
      Nlv_errReport( eobj, &dbginfo, "done ReadWindow" );
    }
    Nlv_LastT = -1.;
    break;
  default:
    sprintf(Et_Interp->result, "Illegal WindowChanged flag, must be 'S' or 'D'" );
    return TCL_ERROR;
  }

  /*  fprintf(stderr,"<dbg>update display\n"); */
  Nlv_grUpdateDisplay( nlvState.gr );

  Nlv_errReport( eobj, &dbginfo, "Exiting WindowChanged" );

  return TCL_OK;

}

/*
 *----------------------------------------------------------------------
 *
 * SetGraphSymbols -- Set the graph symbols for an element type.
 *
 * Input: argv[1] = element type index
 *        argv[2] = new symbol
 *
 * Calls: Nlv_grSetSymbols( type, name )
 *
 * Algorithm: Call the "graphclass" function to do all the work.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_SetGraphSymbols( ET_TCLARGS )

{
  int n;

  if ( argc != 3 ) {
    sprintf( Et_Interp->result, "%s", "Wrong number of arguments to SetGraphSymbols" );
    return TCL_ERROR;
  }
  n = atoi(argv[1]);
  if ( n < 0 || n >= NLV_NUM_ELEMENT_TYPES ) {
    sprintf(Et_Interp->result,"In SetGraphSymbols: Element type number out of range (%d).", n );
    return TCL_ERROR;
  }
  if ( 0 != Nlv_grSetSymbols(nlvState.gr, n, argv[2]) ) {
    sprintf(Et_Interp->result,"In SetGraphSymbols: Internal function to set symbols failed." );
    return TCL_ERROR;
  }
  return TCL_OK;
}




/*
 *----------------------------------------------------------------------
 *
 * SetVisibleClients -- Set the visible clients from TCL variables.
 *
 * Input    :
 *
 * Calls    : Tcl_GetVar2
 *
 * Algorithm: The list of client names is in argv[1]
 *            for each of these set visibility according to
 *            param(server,<name>) value.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_SetVisibleClients( ET_TCLARGS )

{
  char *func = "SetVisibleClients";
  int i, vis;
  char *visStr;
  char  buf[ 256 ];
  int    nmc;
  char **nmv;
  Nlv_EInfo einfo;

  if ( argc != 2 ) {
    sprintf( Et_Interp->result, "%s", "Wrong number of arguments to SetVisibleClients" );
    return TCL_ERROR;
  }

  Tcl_SplitList( Et_Interp, argv[1], &nmc, &nmv );
  for ( i=0; i < nmc; i++ ) {
    if ( !nmv[i] ) {
      NLV_SET_EINFO( einfo, FILE_ERR_CODE+5, 0 );
      Nlv_errReport( eobj, &einfo, 
		     "Unexpected NULL client name");
      continue;
    }
    sprintf( buf, "%s,%s", "server", nmv[i] );
    if ( (NULL==
	  (visStr = Tcl_GetVar2( Et_Interp, "param", buf, TCL_GLOBAL_ONLY ))) ||
	 ( TCL_OK != Tcl_GetBoolean( Et_Interp, visStr, &vis )) ) {
      Nlv_errPrintString( eobj, func, "Cannot get client visibility."); 
      Nlv_errFatalTcl( eobj, Et_Interp, buf );
    }
    Nlv_grSetClientVisible( nlvState.gr, nmv[i], (unsigned char)vis );
  }

  free( nmv );

  return TCL_OK;

}

/*
 *----------------------------------------------------------------------
 *
 * ClearCachedLines -- Clear memory cache, so that the next redraw is
 *                        forced to go to the file and fill in a blank
 *                        slate.
 *
 * Input    : <ignored>
 *
 * Calls    : Nlv_grClearMem
 *
 * Algorithm: Wrapper.
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_ClearCachedLines( ET_TCLARGS )

{
  Nlv_grClearMem( nlvState.gr );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * YAxisMax -- Return the highest y-axis value on the graph.
 *
 * Input:
 *
 * Calls: Nlv_grGetYBounds
 *
 * Algorithm: Use only y-component of Nlv_Bounds structure.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_YAxisMax( ET_TCLARGS )

{
  Nlv_Bounds *b;

  b = Nlv_grGetYBounds( nlvState.gr );
  sprintf( Et_Interp->result, "%.1lf", b->y[1] );
  free( b );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TickLabel -- Get the string label for a graph tick on the
 *                 y-axis.
 *
 * Input: argv[1] = Number of tick for which label is needed.
 *
 * Calls: Tcl_GetInt, Nlv_grGetLabel
 *
 * Algorithm: Request data from graph class. It is assumed that
 *            the returned string is NOT dynamically allocated.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_TickLabel( ET_TCLARGS )

{
  int n;

  if ( !initialized )
    return;

  if ( argc != 2 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl TickLabel command.");
    return TCL_ERROR;
  }
  n     = atoi(argv[1]);
  sprintf( Et_Interp->result, "%.*s", NLV_TICKLABELWIDTH,
	   Nlv_grGetLabel( nlvState.gr, n ) );

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TickLabel -- Get the string label for a graph tick's scale on the
 *              y-axis.
 *
 * Input: argv[1] = Number of tick for which label is needed.
 *
 * Calls: Tcl_GetInt, Nlv_grGetTickScale
 *
 * Algorithm: Request data from graph class. It is assumed that
 *            the returned string is NOT dynamically allocated.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_TickScale( ET_TCLARGS )
{
  int n;

  if ( !initialized )
    return;

  if ( argc != 2 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl TickLabel command.");
    return TCL_ERROR;
  }
  n     = atoi(argv[1]);
  sprintf( Et_Interp->result, "%.*s", NLV_TICKLABELWIDTH,
	   Nlv_grGetTickScale( nlvState.gr, n ) );

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Decolor -- Make all the graph elements a variety of dash patterns.
 *
 * Input:
 *
 * Calls: Nlv_grDecolor
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_Decolor( ET_TCLARGS )

{
  return Nlv_grDecolor( nlvState.gr );
}


/*
 *----------------------------------------------------------------------
 *
 * Recolor -- Put the colors back in all the graph elements.
 *
 * Input:
 *
 * Calls: Nlv_grRecolor
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_Recolor( ET_TCLARGS )

{
  return Nlv_grRecolor( nlvState.gr );
}

/*
 *----------------------------------------------------------------------
 *
 * SetFilePosSymbols -- Show highlighted bookmarks
 *
 * Input:
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_SetFilePosSymbols( ET_TCLARGS )
 
{
  char  *symbol;
  char **posStr;
  int    i;
  long  *list;
  int    listSize;

  if ( argc != 3 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl SetFilePosSymbols command.");
    return TCL_ERROR;
  }
  Tcl_SplitList( Et_Interp, argv[1], &listSize, &posStr );
  if ( listSize <= 0 )
    return TCL_OK;
  list = (long *)malloc( listSize *sizeof(long) );
  for( i=0; i < listSize; i++ )
    list[i] = atol( posStr[i] );
  symbol = argv[2];
  if ( Nlv_grSetHighlights( nlvState.gr, list, listSize, symbol ) < 0 )
  {
    free( posStr );
    free( list );
    return TCL_ERROR;
  }
  free( list );
  free( posStr );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ClearAllHighlights -- Clear all highlighted elements on
 *                          the screen to make way for the new ones. 
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_ClearAllHighlights( ET_TCLARGS )
 
{
   Nlv_grClearHighlights( nlvState.gr );
   return TCL_OK; 
}

/*
 *----------------------------------------------------------------------
 *
 * SkipToNextData -- Skip to next piece of data in file.
 *
 * Input:
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_SkipToNextData( ET_TCLARGS )
{
  
  double t     = Nlv_grRealTime( nlvState.gr, strtod( argv[1], NULL ));
  double tlast = Nlv_ixGetLastTime( nlvState.ix );
  long   tinc  = Nlv_ixGetIncrement( nlvState.ix );
  long   apos, prevpos;

  while( (t < ( tlast - tinc ))  && 
	 (prevpos = Nlv_ixGetPosition( nlvState.ix, t )) != -1 &&
	 (apos = Nlv_ixGetPosition( nlvState.ix, t+tinc )) == prevpos ) {
    t += tinc;
  }
  t = Nlv_grGraphTime( nlvState.gr, t );
  sprintf(Et_Interp->result,"%lf",t);

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SLHighest -- Return value of highest summary-line element.
 *
 * Input: <none>
 *
 * Calls: Nlv_ixDGetHighest
 *
 * Algorithm: Accessor, as this value is cached by the indexer
 *            as it enters summary line (or "density", as it calls it)
 *            values.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_SLHighest( ET_TCLARGS )
 
{ 
  sprintf( Et_Interp->result, "%ld", Nlv_ixDGetHighest( nlvState.ix ) );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SLScanStart -- Prepare to start scanning elements in the summary
 *                line.
 *
 * Input: (Real)time -- "graph time" (i.e. start at 0) at which
 *                      to begin the scan
 *
 * Calls: Nlv_ixDIndexAtTime
 *
 * Algorithm: 
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_SLScanStart( ET_TCLARGS )
 
{
  int r;
  double reltime;

  if ( slpos != -1 ) {
    NLV_FATAL_S("Scan already in progress in Tcl SLScanStart command.");
    return TCL_ERROR;
  }
  if ( argc != 2 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl SLScanStart command.");
    return TCL_ERROR;
  }
  reltime = atof( argv[1] );
  if ( reltime < 0 ) {
    NLV_FATAL_S("Argument is not a number in Tcl SLScanStart command.");
    return TCL_ERROR;
  }
  r = Nlv_ixDIndexAtTime( nlvState.ix, reltime );
  if ( r < 0 ) {
    sprintf(Et_Interp->result,"%s","Time out of range");
    return TCL_ERROR;
  }

  slpos = (double)r; /* assign to global variable */

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SLScanNext -- Get the next element from the summary line.
 *
 * Input: (Real)skip -- # of element to jump forwards. this makes it
 *                      easy to "sample" a large number of elements
 *                      for a fixed number of slots.
 *
 * Calls: Nlv_ixDGetCount, floorf
 *
 * Algorithm: The returned value is the average of all the slots
 *            between the current and new positions
 *
 *----------------------------------------------------------------------
 */
#define MYMAX(a,b) ((a)>(b)?(a):(b))
int ET_COMMAND_SLScanNext( ET_TCLARGS )
 
{ 
  double skip;
  int i;
  int fl_slpos   = (int)floor(slpos);
  int ceil_slpos = fl_slpos+1;
  int fl_skip;    
  int ceil_skip;
  double max, cnt;

  if ( slpos == -1 ) {
    NLV_FATAL_S("Scan not started in Tcl SLScanNext command.");
    return TCL_ERROR;
  }
  if ( argc != 2 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl SLScanNext command.");
    return TCL_ERROR;
  }
  skip = atof( argv[1] );
  if ( skip == 0 ) {
    NLV_FATAL_S("Argument is not a number in Tcl SLScanNext command.");
    return TCL_ERROR;
  }
  
  fl_skip   = (int)floor(slpos+skip);
  ceil_skip = fl_skip+1;

  /*
   * Get highest density from current slpos
   * to slpos+skip
   */
  max = 0;
  cnt = 0;
  if ( slpos != ceil_slpos )
    max = Nlv_ixDGetCount( nlvState.ix, fl_slpos );
  for( i = ceil_slpos; i < fl_skip; i++ ) {
    cnt = Nlv_ixDGetCount(nlvState.ix, i);
    max = MYMAX( max, cnt );
  }
  if ( (slpos + skip) != fl_skip ) {
    cnt = Nlv_ixDGetCount( nlvState.ix, fl_skip );
    max = MYMAX( max, cnt );
  }

  /*
   * Increment slpos for next iteration
   */
  slpos += skip;

  /*
   * Return average value, rounded down to
   * the nearest integer.
   */
  sprintf( Et_Interp->result, "%d", (int)floor( max ) );

  return TCL_OK;
}
#undef MYMAX

/*
 *----------------------------------------------------------------------
 *
 * SLScanDone -- Indicate that the scan of the summary line is done.
 *
 * Input: <none>
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_SLScanDone( ET_TCLARGS )
 
{ 
  if ( slpos == -1 ) {
    NLV_FATAL_S("Scan not started in Tcl SLScanDone command.");
    return TCL_ERROR;
  }
  slpos = -1;
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SLSize -- Return the size of the summary line array, i.e. how many
 *           "bars" [elements with a height] are in it, UP TO the given
 *           time argument.
 *
 * Input: <none>
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_SLSize( ET_TCLARGS )
 
{
  double reltime;

  if ( argc != 2 ) {
    NLV_FATAL_S("Wrong number of arguments in Tcl SLScanStart command.");
    return TCL_ERROR;
  }
  reltime = atof( argv[1] );
  sprintf( Et_Interp->result, "%d", Nlv_ixDGetSize( nlvState.ix, reltime ) );
  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetAnnotation -- Get the field names and values for annotation
 *                  of a given graph element.
 *
 * Input: argv[1] = element name in <type>t<index> format
 *        argv[2] = y-position (integer)
 *
 * Output: A list of field name, value pairs to be displayed.
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int ET_COMMAND_GetAnnotation( ET_TCLARGS )

{
  int eType;          /* Element type # */
  int eIndex;         /* Element index # */
  int y;              /* Location on y-axis */
  long filepos;       /* Returned file position */
  char *annStr;       /* Annotation result string to give to Tcl */
  char buf[128];      /* Buffer for commands & numbers */
  long origpos;       /* File position when we started */
  Nlv_fi fiObj;       /* File class object */
  LogLine logline;    /* Log line */
  static char logbuf[NLV_MAXLINE]; /* Log line buffer */
  Nlv_Fields *fldPtr; /* Fields in log line */
  Nlv_Key *keyPtr;    /* Pointer to key data assoc with logline */
  Nlv_KeySet *ksPtr;  /* Pointer to keyset data assoc with logline */
  char **alist;       /* Annotation fields */
  int anum;           /* # of annotation fields */
  int colidx;         /* 'hostindex' or coloring index for this keyset */
  int i,j;            /* Loop counters */

  fiObj   = NULL;
  annStr  = Tcl_Alloc( 1024 );
  *annStr = '\0';

  if ( argc != 3 ) {
    strcpy(annStr ,"Wrong # of args");
    goto annError;
  }
  /* This can happen if the user clicks on the zoombox,
   * so just return an error.
   */
  if ( 2 !=  sscanf(argv[1], "%dt%d", &eType, &eIndex) ) {
    return TCL_ERROR;
  }

  y = Nlv_grGetIndexAtTick( nlvState.gr, atoi(argv[2]) );
  if ( y == -1 ) {
    strcpy( annStr,"No index for tick");
    goto annError;
  }

  filepos = Nlv_grGetFilePos( nlvState.gr, eType, eIndex, y );
  /*  fprintf(stderr,"<dbg>find type=%d index=%d y=%d\n",eType,eIndex,y); */
  if ( filepos == -1 ) {
    strcpy(annStr,"Cannot find element");
    goto annError;
  }

  /* 
   * Get the data at byte #'filepos' in the file
   * and format it.
   */

  fiObj   = nlvState.fi;
  logline = logbuf, *logline = '\0';
  origpos = Nlv_fiGetPos( fiObj );
  Nlv_fiSeek( fiObj, filepos );
  Nlv_fiGetLineNoSave( fiObj, logline );
  if ( *logline == '\0' ) {
    strcpy(annStr,"Cannot read from file");
    goto annError;
  }
  if ( (fldPtr = Nlv_paProcess( nlvState.pa, logline )) == NULL ) {
    strcpy(annStr,"Cannot parse file data");
    goto annError;
  }
  /* Find the keyset assoc. with this log line */
  keyPtr = Nlv_clGetKeyPtr( nlvState.cl, fldPtr, &i );
  if ( NULL == keyPtr ) {
    strcpy(annStr,"Cannot find keyset assoc. with log line");
    goto annError;
  }
  ksPtr = keyPtr->ks;

  /*
   * Now that we're sure the line is OK,
   * get the color for the element and add it to the string.
   */

  Nlv_grGetElementColorString( nlvState.gr, eType, eIndex, buf );
  strcat( annStr, buf );

  /*
   * Flag the element so we can turn off the
   * visual marking of it when we erase it from the
   * screen (otherwise we will un-mark another element!)
   */

  Nlv_grMarkAsAnnotated( nlvState.gr, eType, eIndex );
  /* Get the list of annotation fields for the keyset */
  alist = ksPtr->annFields;
  anum  = ksPtr->numAnnFields;

  /* 
   * For each annotation field, add the appropriate data
   * to our result string, annStr.
   * This is O(N^2), but N is always small.
   */

  for ( i=0; i < anum; i++ ) {
    if (strcasecmp( alist[i],"time" ) == 0) {
     char tbuf[ 32 + 6 ];
     sprintf( tbuf, " Time(s) %.6lf", Nlv_clGetTime( fldPtr ) ); 
     strcat( annStr, tbuf );
    }
    else 
    for ( j=0; j < fldPtr->num; j++ ) {
      if ( 0 == strcasecmp( alist[i], FIELD_NAME(fldPtr,j) ) ) {
	strcat( annStr, " " );
	strcat( annStr, FIELD_NAME(fldPtr,j) );
	strcat( annStr, " " );
	strcat( annStr, FIELD_VAL(fldPtr,j) );
      }
    }
  }
  Nlv_fiSeek( fiObj, origpos );

  /* return this whole mess to Tcl */
  Tcl_SetResult( Et_Interp, annStr, TCL_DYNAMIC );
  return TCL_OK;

annError:
  fprintf(stderr,"GetAnnotation: %s\n", annStr);
  if ( fiObj )
    Nlv_fiSeek( fiObj, origpos );
  Tcl_SetResult( Et_Interp, annStr, TCL_DYNAMIC );
  return TCL_ERROR;

}
/*
 *----------------------------------------------------------------------
 *
 * PrintGnuplot -- Print using gnuplot as a filter, to a gnuplot-supported
 *                 device.
 *
 * Input: file/printer flag, file name|printer name, gnuplot format,
 *        color-mode, page orientation
 *
 * Calls: Nlv_grWriteGnuplot
 *
 * Algorithm: Get bounds from Tcl, then call graphclass to do the work.
 *
 *----------------------------------------------------------------------
 */
int 
ET_COMMAND_PrintGnuplot( ET_TCLARGS )
{
  int   file_flag; /* 0=printer, 1=file */
  char *device_name;
  char *gnuplot_format;
  char *page_orient;
  char *color_mode;
  
  double b[4];
  int axis, bound;
  char *axes[] = { "xaxis", "yaxis" };
  char *bounds[] = { "min", "max" };
  char cmdbuf[ 256 ];
  char *error_str = NULL;

  if ( argc != 6 ) {
    sprintf(interp->result,"%d arguments expected, %d given", 3, argc );
    return TCL_ERROR;
  }

  file_flag      = atoi( argv[1] );
  device_name    = argv[2];
  gnuplot_format = argv[3];
  page_orient    = argv[4];
  color_mode     = argv[5];

  /* debugging
  fprintf(stderr, "<dbg> Print gnuplot: "
	  "file=%d device=%s format=%s orient=%s color=%s\n",
	  file_flag, device_name, gnuplot_format, page_orient, color_mode );
  */

  for ( axis = 0; axis < 2; axis ++ ) {
    for ( bound = 0; bound < 2; bound++ ) {
      sprintf( cmdbuf, "$widget(graph,0) %s cget -%s", axes[axis], bounds[bound]);
      NLV_SAFE_GLOBAL_EVAL(Et_Interp, cmdbuf );
      b[2*axis + bound] = atof(Et_Interp->result);
    }
  }

  error_str = Nlv_grWriteGnuplot( nlvState.gr,
				  file_flag,
				  device_name,
				  gnuplot_format,
				  page_orient,
				  color_mode,
				  b[0], b[1], b[2], b[3] );

  if ( error_str != NULL ) {
    sprintf(Et_Interp->result, "While writing Gnuplot: %s", error_str );
    return TCL_ERROR;
  }


  return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Exit -- Cleanly exit NLV.
 *
 * Input:
 *
 * Calls:
 *
 * Algorithm: Close log and input files.
 *
 *----------------------------------------------------------------------
 */

int ET_COMMAND_Exit( ET_TCLARGS )
 
{ 
  void *s;

  Nlv_fiClose( nlvState.fi );
  /*  pthread_cancel( nlvState.ixTid );
      pthread_join( nlvState.ixTid, NULL );
  */
  printf("NLV is exiting now.\n");
  exit(0);
}

/*================================ internal ================================*/



/*
 *----------------------------------------------------------------------
 *
 * ProcessLogLine -- Parse and process log line. Return time of
 *                   line, or -1.
 *
 * Input: the raw data
 *        flags for slightly different processing:
 *          NLV_LOG_GRAPH   : Pass the data on to graphing routine.
 *
 * Calls: [many]
 *
 * Algorithm: 
 *             - Parse raw data.
 *             - Get parsed fields and make them into internal data 
 *               in the clients class.
 *             - Get graph data from clients class.
 *             - Set start time (if necc.), update index file
 *             - Process the graph data in the graph class.
 *
 *             The first line must, at the least, always be processed
 *             right after clProcess(), because we need to figure the
 *             relative time for subsequent graph points.
 *
 *----------------------------------------------------------------------
 */
double ProcessLogLine( LogLine logline, int flags )
{
  int x;
  Nlv_Fields  *fldPtr;
  Nlv_GData   *gdataPtr;
  Nlv_Client  *clPtr;
  double      retval;
  char b[ 16 ];
  Nlv_EInfo dbginfo, einfo;

  NLV_SET_EINFO( dbginfo, NLV_DEBUG_LVL, 0 );

  /* Separate the logline into field=value pairs */
  fldPtr = Nlv_paProcess( nlvState.pa, logline );
  if ( fldPtr == NULL )
    goto error;

  /* Find client and make graph data. */
  gdataPtr = Nlv_clProcess( nlvState.cl, fldPtr );
  if ( gdataPtr == NULL )
    goto error;

  /* If we added a client, add to graphclass as well */
  clPtr = Nlv_clNewClient( nlvState.cl );
  if ( clPtr != NULL ) 
    Nlv_grAddClient( nlvState.gr, nlvState.ky, clPtr, gdataPtr->graphType );

  retval = gdataPtr->time;

  /* Graph the data */
  if ( flags & NLV_LOG_GRAPH )
  { 
    if ( Nlv_grProcess( nlvState.gr, gdataPtr ) == NLV_NOT_DRAWN )
	retval = (double)NLV_NOT_DRAWN;
  }

  return retval;

 error:
  return NLV_NOT_DRAWN;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadWindow -- Read the data for one graph window.
 *
 * Input: 
 *
 * Calls: Nlv_ixGetPosition, Nlv_ixGetLastPosition, Nlv_fiGetPos,
 *        Nlv_fiSeek, Nlv_fiGetLine, ProcessLogLine
 *
 *----------------------------------------------------------------------
 */

void ReadWindow( Nlv_Bounds *bounds )
{
    void calculateWindow( Nlv_Bounds *bPtr, double *le, double *re );
    long    startPos, origPos, linePos;
    LogLine logline;
    static char logbuf[ NLV_MAXLINE ];
    double  time = -1.;
    double  leftEdge, rightEdge;
    int     i, first;
    char    buf[ 256 ];
    Nlv_EInfo dbginfo, einfo;
    int    ctr, maxCtr;
    
    NLV_SET_EINFO( dbginfo, NLV_DEBUG_LVL, 0 );
    Nlv_errReport( eobj, &dbginfo, "Enter ReadWindow" );
    
    calculateWindow( bounds, &leftEdge, &rightEdge );

    /* 
     * Get starting position
     * If index position fails, try again on the theory
     * that the indexing thread will "catch up" a lot
     * faster than we can read through ourselves.
     * If we're waiting for the indexing thread, set its
     * priority to be higher (so it is quicker!), to avoid priority
     * inversion.
     */
    
    Nlv_errReport( eobj, &dbginfo, "Get StartPos" );
    if ( !nlvState.firstLine ) {
	int prioUp = 0;
	while ( (startPos = Nlv_ixGetPosition( nlvState.ix, leftEdge )) < 0 ) {
	    Nlv_errReport( eobj, &dbginfo, "ixGetPosition for %f returned %l", leftEdge, startPos  );
	    if ( !prioUp ) {
		Nlv_ixIncreasePriority( nlvState.ix );
		prioUp = 1;
	    }
	    sleep(1);
	}
	if ( prioUp )
	    Nlv_ixNormalPriority( nlvState.ix );
    }
    else
	startPos = 0L;
    Nlv_errReport( eobj, &dbginfo, "startpos=%l", startPos );
    origPos = Nlv_fiGetPos( nlvState.fi );
    Nlv_fiSeek( nlvState.fi, startPos );
    
    /* Read and graph window */
    
    Nlv_errReport( eobj, &dbginfo,
		   "Read and graph window [ %f , %f ]",
		   leftEdge, rightEdge );
    /* 
     * For timing window refreshes:
     *  stopwatch(0);
     */
    Nlv_grFlush( nlvState.gr, 0 );
    ctr = 0;
    maxCtr = Nlv_grGetBufferSize( nlvState.gr );
    /* debugging */
    /* printf("leftEdge = %lf rightEdge = %lf\n", leftEdge, rightEdge ); */
    while ( time < rightEdge && ctr < maxCtr ) {
	logline = logbuf, *logline = '\0';
	Nlv_fiGetLine( nlvState.fi, logline );
	if ( *logline == '\0' )
	    break;
	time = ProcessLogLine( logline, NLV_LOG_ALL );
	if ( time >= (bounds->x)[0] && (int)time != NLV_NOT_DRAWN ) 
	{
	    ctr++;
	    /* printf("ctr++: %s\n",&logline[93]); */
	}
    }
    /* debugging */
    /* printf("Stopped drawing time=%lf counter=%d\n", time, ctr ); */
    /* 
     * Print timing on window refresh:
     * fprintf(stderr, "<dbg>time = %g\n", stopwatch(0) );
     */
    Nlv_errReport( eobj, &dbginfo, "Flush and exit" );
    Nlv_grFlush( nlvState.gr, 1 );   
}

/*
 * Set left, right edges of attempted windows.
 */
void calculateWindow( Nlv_Bounds *boundsPtr,
		      double     *leftEdgePtr,
		      double     *rightEdgePtr )
{
    double w, left, right;

    w = (boundsPtr->x)[1] - (boundsPtr->x)[0];
    left =  (boundsPtr->x)[0] - w * NLV_EXTRA_LEFT;
    if ( left < 0 )
	left = 0;
    right = (boundsPtr->x)[1] + w * NLV_EXTRA_RIGHT;

    *leftEdgePtr = left;
    *rightEdgePtr = right;
}

/*================================ calculations ============================*/
#undef CNUM
#undef FILE_ERR_CODE
