
/* Generated by Interface Builder */

#import "NXYController.h"
#import "NXYView.h"		/*  only for initializeLegendBox */
/* will get warning message from compiler if preceding line is commented out */
#import <appkit/OpenPanel.h>
#import <appkit/Matrix.h>
#import <appkit/Cell.h>
#import <math.h>

#define ALLOCSIZE 2048		/* used in malloc in readData */

@implementation NXYController

- (NXCoord *)xdata  { return x;}
- (NXCoord **)ydata { return y;}

- (int)nPoints    { return npoints;}
- (int)nCurves    { return ncurves;}

- (const char *)provideXtitle    {return [xTitle stringValueAt:0];}
- (const char *)provideYtitle    {return [yTitle stringValueAt:0];}
- (const char *)provideMaintitle    {return [mainTitle stringValueAt:0];}

- (const char *)provideCurveTitle:(int)aCurve
{
    return [legendForm stringValueAt:aCurve];
}

- (const char *)provideLegendTitle
{
    return [legendTitle stringValueAt:0];
}

- (BOOL) shouldDrawLegend
{
  if ( [legendOnOff state] ) return YES;
  else return NO;
}
- (BOOL) shouldDrawLegendBox
{
  if ( [legendBoxOnOff state] ) return YES;
  else return NO;
}

- (BOOL) shouldDrawGrid
{
  if ( [gridOnOff state] ) return YES;
  else return NO;
}

- (BOOL) shouldDrawBox
{
  if ( [borderBoxOnOff state] ) return YES;
  else return NO;
}

- (BOOL) shouldMoveLegend
{
  if ( [legendMove state] ) return YES;
  else return NO;
}

- (BOOL) doZoom
{
  if ( [zoomOnOff state] ) return YES;
  else return NO;
}

- (BOOL) xaxisLog
{
  if ( [xLinLog state] ) return YES;
  else return NO;
}

- forceXaxisLinear
{
  [xLinLog setState:0];
  [xLinLog display];
  return self;
}

- (BOOL) yaxisLog
{
  if ( [yLinLog state] ) return YES;
  else return NO;
}

- forceYaxisLinear
{
  [yLinLog setState:0];
  [yLinLog display];
  return self;
}

- (int)providelinestyle:(int)aCurve
{
    int   row;
    int   cellstate;

    for (row=0; row<N_LINE_STYLES; row++) {
      cellstate = [ [lineMatrix cellAt:row :aCurve] state];
      if (cellstate == 1) return row;
    }
    return 0;			/* for safety */
}

- (int)providesymbolstyle:(int)aCurve
{
    int   row;
    int   cellstate;

    for (row=0; row<N_SYMBOL_STYLES; row++) {
      cellstate = [ [symbolMatrix cellAt:row :aCurve] state];
      if (cellstate == 1) return row;
    }
    return 0;			/* for safety */
}

- (int)providesymbolsize{  return [symbolSize selectedCol];}
- (int)providelinethickness{  return [lineThickness selectedCol];}

- (float)provideXmin  {return [xMin floatValueAt:0];}
- (float)provideXmax  {return [xMax floatValueAt:0];}
- (float)provideXinc  {return [xInc floatValueAt:0];}
- (float)provideYmin  {return [yMin floatValueAt:0];}
- (float)provideYmax  {return [yMax floatValueAt:0];}
- (float)provideYinc  {return [yInc floatValueAt:0];}

- resetXmin:(float)aNum { [xMin setFloatValue:aNum at:0]; return self; }
- resetXmax:(float)aNum { [xMax setFloatValue:aNum at:0]; return self; }
- resetXinc:(float)aNum { [xInc setFloatValue:aNum at:0]; return self; }
- resetYmin:(float)aNum { [yMin setFloatValue:aNum at:0]; return self; }
- resetYmax:(float)aNum { [yMax setFloatValue:aNum at:0]; return self; }
- resetYinc:(float)aNum { [yInc setFloatValue:aNum at:0]; return self; }

- resetMinMax:sender
{
  [xMin setFloatValue:datamin.x at:0];
  [xMax setFloatValue:datamax.x at:0];
  [xInc setFloatValue:(datamax.x - datamin.x)/5.0 at:0];
  [yMin setFloatValue:datamin.y at:0];
  [yMax setFloatValue:datamax.y at:0];
  [yInc setFloatValue:(datamax.y - datamin.y)/5.0 at:0];
  return self;
}

- drawPlotButton:(int)state
{
  if (state==0) {
    [plotButton highlight:NO];	/* will display normal title */
  }
  if (state==1) {
    [plotButton highlight:YES];	/* will display alternate title */
  }
  return self;
}

- drawPlot:sender
{
    if (!x) return self;

    [self drawPlotButton:1];	/* display "Plotting" */
    [canvas display];
    [self drawPlotButton:0];	/* display "Plot" */
    return self;
}

// Allocate enough memory and read the data points
/*
 * WARNING: This code will go into an infinite loop if there is illegal
 * crud in the data file (such as a comma, for example).  The culprit is
 * the "fscanf" below; we will put up with this for now, but we should
 * make this more robust.
 */
- (int) readData:(FILE *)aDataStream
{
    BOOL    inword = NO;
    char    c;
    int     j, size = ALLOCSIZE;
    int     oldncurves = ncurves; /* for linestyle matrix column deleting */

    /* set plot button title "Reading" */
    [plotButton setAltTitle:"Reading"];
    [plotButton highlight:YES];
    NXPing();			/* force plotButton redraw */

    /* First, free x and y here if there's already data in them */
    if (x) {
      free( (void *)x );
      for (j = 0; j < ncurves; j++) {
	free( (void *)(*(y+j)) );
      }
      free( (void *)y );
    }

    /* Figure out the number of curves in the file by reading characters  */
    /* until a newline is encountered; the number of curves is one less   */
    /* than the number of words found.                                    */
    /* For now we assume the input file is an ascii file.                 */
    ncurves = -1;
    while (1) {
      c = getc(aDataStream);
      if (c == '\n') {
	break;			/* breaks out of while loop */
      }
      if ((inword==NO) && !(c==' ' || c=='\t')) {
	ncurves++;
	inword = YES;
      }
      if ((inword==YES) && (c==' ' || c=='\t')) {
	inword = NO;
      }
    }
    if (ncurves == -1) {	/* couldn't find "\n", give up */
      return 0;
    }

    /* Now read the data into memory */
    rewind(aDataStream);
    
    y = (NXCoord **)malloc( ncurves * sizeof(NXCoord *) );
    x = (NXCoord *)malloc( size * sizeof(NXCoord) );
    for (j = 0; j < ncurves; j++) {
      *(y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
    }
    npoints = 0;
    while(1) {
      if( (fscanf(aDataStream, "%f", x+npoints)) == EOF ) {
	break;			/* breaks out of the while loop */
      }
      for (j = 0; j < ncurves; j++) {
	fscanf(aDataStream, "%f", *(y+j)+npoints);
      }
      npoints++;
      if (npoints == size) {		/* get more memory */
	size += ALLOCSIZE;
	x = (NXCoord *)realloc(x, size * sizeof(NXCoord));
	for (j = 0; j < ncurves; j++) {
	  *(y+j) = (NXCoord *)realloc( *(y+j), size * sizeof(NXCoord) );
	}
      }      
    }
    /* Adjust the lineMatrix, symbolMatrix, and legendForm */
    [self adjustPanels:oldncurves :ncurves];

    /* reset plot button */
    [plotButton setAltTitle:"Plotting"];
    [plotButton highlight:NO];
    return npoints;
}

/* Might want to make sure INLINE_MATH is defined when math.h is included */

- checkLinLog
{
  /* Check x and y axes -- do a heuristic test for log/lin.
   * The test employed comes from M. Merriam and xyplot.
   */
  double    scale, linsum, logsum;
  register  double tmp;
  int       i, j;

  /* First test x axis.  */
  if (datamax.x > 0.0  &&  datamin.x > 0.0  && datamax.x != datamin.x) {
    scale = fabs( (double)datamax.x - (double)datamin.x );
    linsum = 0.0;
    for (j=1; j<npoints; j++) {
      tmp = ( (double)x[j]-(double)x[j-1] )/(double)scale; /* avoid overflow */
      linsum += tmp*tmp;
    }
    scale = log10( (double)datamax.x/(double)datamin.x );
				/* what if datamax.x<datamin.x? */
    logsum = 0.0;
    for (i=1; i<npoints; i++) {
      tmp = log10( (double)x[i]/(double)x[i-1] ) / scale;
      logsum += tmp*tmp;
    }
    if (linsum < logsum) {
      [xLinLog setState:0];	/* linear axis */
    }
    else {
      [xLinLog setState:1];	/* logarithmic axis */
    }
  }
  else {
    [xLinLog setState:0];	/* linear */
  }
  /* Now test y axis */
  if (datamax.y > 0.0  &&  datamin.y > 0.0  && datamax.y != datamin.y) {
    scale = fabs( (double)datamax.y - (double)datamin.y );
    linsum = 0.0;
    for (j=0; j<ncurves; j++) {
      for (i=1; i<npoints; i++) {
	tmp = ( (double)*(*(y+j)+i) - (double)*(*(y+j)+i-1) )
	                    / scale; /* avoid overflow */
	linsum += tmp*tmp;
      }
    }
    scale = log10((double)datamax.y/(double)datamin.y);
				/* what if datamax.y<datamin.y? */
    logsum = 0.0;
    for (j=0; j<ncurves; j++) {
      for (i=1; i<npoints;i++) {
	tmp = log10( (double)*(*(y+j)+i)/(double)*(*(y+j)+i-1) ) / scale;
	logsum += tmp*tmp;
      }
    }
    if (linsum < logsum) {
      [yLinLog setState:0];	/* linear axis */
    }
    else {
      [yLinLog setState:1];	/* logarithmic axis */
    }
  }
  else {
    [yLinLog setState:0];	/* linear */
  }
  [xLinLog display];
  [yLinLog display];
  return self;
}

- adjustPanels:(int)oldn :(int)newn
{
  /*
   * Resize the linestyle matrix, the symbolstyle matrix, and
   * the legend form.
   * Also arrange to select and highlight only the top button in
   * each column of the linestyle and symbolstyle matrices.
   * This code could stand to be cleaned up.
   */
  char formtitle[80];
  int j, k;

  if (oldn >= newn) {	/* have to delete some columns and revise the remaining */
    for (j=oldn-1; j>=newn; j--) {
      [lineText removeColAt:j andFree:YES]; /* Titles on Columns  */
      [symbolText removeColAt:j andFree:YES]; /* Titles on Columns  */
      [lineMatrix removeColAt:j andFree:YES];
      [symbolMatrix removeColAt:j andFree:YES];
      [legendForm removeEntryAt:j];
    }
    for (j=0; j<newn; j++) {
      if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [lineMatrix cellAt:0 :j] setState:1];
      if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [symbolMatrix cellAt:0 :j] setState:1];
      for (k=1; k<N_LINE_STYLES; k++) {
	[ [lineMatrix cellAt:k :j] setState:0];
      }
      for (k=1; k<N_SYMBOL_STYLES; k++) {
	[ [symbolMatrix cellAt:k :j] setState:0];
      }
      sprintf(formtitle, "Curve %d", j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle];
      [[symbolText cellAt:0 :j] setStringValue:formtitle];
    }
    [legendTitle setStringValue:"Legend" at:0];
    [lineText sizeToCells];	/* Titles on Columns */
    [symbolText sizeToCells];	/* Titles on Columns */
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [ [lineText window] display];   /* Titles on Columns*/
    [ [symbolText window] display]; /* Titles on Columns*/
    [ [lineMatrix window] display];   /* redraw the whole window so extra */
    [ [symbolMatrix window] display]; /* columns get erased               */
    [ [legendForm window] display];
  }
  if ( (oldn == 0) && (newn > 1) ) { /* can only happen first time */
    for (j=1; j<newn; j++) {
      [lineText addCol];
      [symbolText addCol];
      [lineMatrix addCol];
      if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [lineMatrix cellAt:0 :j] setState:1];
      /* highlight 1st row new column -- this occurs automatically */
      [symbolMatrix addCol];
      if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [symbolMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      sprintf(formtitle, "Curve %d:", j+1);
      [legendForm addEntry:formtitle];
      sprintf(formtitle, "Curve %d", j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle];
      [[symbolText cellAt:0 :j] setStringValue:formtitle];
    }
    [lineText sizeToCells];	/* Titles on Columns */
    [symbolText sizeToCells];	/* Titles on Columns */
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [lineText display];
    [symbolText display];
    [lineMatrix display];	/* don't need to redraw the window here */
    [symbolMatrix display];
    [legendForm display];
  }
  if ( (oldn != 0) && (oldn < newn) ) { /* must add columns */
    for (j=0; j<oldn; j++) {
      if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [lineMatrix cellAt:0 :j] setState:1];
      if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [symbolMatrix cellAt:0 :j] setState:1];
      for (k=1; k<N_LINE_STYLES; k++) {
	[ [lineMatrix cellAt:k :j] setState:0];
      }
      for (k=1; k<N_SYMBOL_STYLES; k++) {
	[ [symbolMatrix cellAt:k :j] setState:0];
      }
      sprintf(formtitle, "Curve %d", j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle];
      [[symbolText cellAt:0 :j] setStringValue:formtitle];
    }
    for (j=oldn; j<newn; j++) {
      [lineText addCol];
      [symbolText addCol];
      [lineMatrix addCol];
      [ [lineMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      [symbolMatrix addCol];
      [ [symbolMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      sprintf(formtitle, "Curve %d:", j+1);
      [legendForm addEntry:formtitle];
      sprintf(formtitle, "Curve %d", j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle];
      [[symbolText cellAt:0 :j] setStringValue:formtitle];
    }
    [legendTitle setStringValue:"Legend" at:0];
    [lineText sizeToCells];	/* Titles on Columns */
    [symbolText sizeToCells];	/* Titles on Columns */
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [lineText display];		/* Titles on Columns */
    [symbolText display];	/* Titles on Columns */
    [lineMatrix display];	/* don't need to redraw the window here */
    [symbolMatrix display];
    [legendForm display];
  }
  [lineThickness setState:1 at:0:0]; /* reset line thickness to thin */
  [symbolSize setState:1 at :0:2];   /* reset symbol size to medium */
  [symbolSize display];
  [lineThickness display];
  return self;
}

// Go through the data set and find values for datamin.x,
// datamax.x, datamin.y, datamax.y
- findMinAndMax
{
    if (x)  {
      int	i, j;
      datamin.x = datamax.x = x[0];
      for (i = 1; i < npoints; i++)  {
	if (x[i] < datamin.x)
	  datamin.x = x[i];
	else if (x[i] > datamax.x)
	  datamax.x = x[i];
      }
      datamin.y = datamax.y = *(*(y+0)+0);
      for (j = 0; j < ncurves; j++) {
	for (i = 0; i < npoints; i++) {
	  if (*(*(y+j)+i) < datamin.y)
	    datamin.y = *(*(y+j)+i);
	  else if (*(*(y+j)+i) > datamax.y)
	    datamax.y = *(*(y+j)+i);
	}
      }
    }
// reset min and max here (may want to change the placement of this)
    [self resetXmin:datamin.x];
    [self resetXmax:datamax.x];
    [self resetXinc:(datamax.x - datamin.x)/5.0];
    [self resetYmin:datamin.y];
    [self resetYmax:datamax.y];
    [self resetYinc:(datamax.y - datamin.y)/5.0];

    return self;
}


// Use the OpenPanel object to get a filename
- open:sender
{
    char const	*fileTypes[2] = {0,0};	// this type is all ASCII files (?)
//    char const	*fileTypes[2] = {"xyp",0};
//          this would be only files with an xyp extension
    char	fname[1024];

    id openPanel = [OpenPanel new];
    if ([openPanel runModalForTypes:fileTypes])  {
      strncpy(fname, [openPanel filename], 1024);
      [self openFile:fname];
    }
    return self;
}

- openFile:(char *)dataFile
{
    FILE	*dataStream;

    if ((dataStream = fopen(dataFile, "r")) == NULL)  {
      NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
      return self;
    }

    if ([self readData:dataStream] == 0)  {
      NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
		      NULL, NULL, dataFile);
      fclose(dataStream);
      return self;
    }
    fclose(dataStream);

    [canvas initializeLegendBox];

    [self findMinAndMax];

    /* Check for linear or log on x and y axes */
    [self checkLinLog];

    [self drawPlot:self];
    
    return self;
}

@end
