/* Test application for ContourView.m class */
/* Izumi Ohzawa 92-5-22 */

#import <appkit/appkit.h>
#import "ContourPlotApp.h"
#import "ContourView.h"
#import "CurveView.h"
#import "version.h"
#import <stdio.h>
#import <stdlib.h>
#import <math.h>

#define PI	3.141592654


@implementation ContourPlotApp

-appDidInit:sender
{

    static NXDefaultsVector CPDefaults = {
	    {"SaveDir", "~"},
	    {NULL}
    };    
    NXRegisterDefaults(APPNAME, CPDefaults);


    zdata = NULL;

    // #### Some default settings which may be overridden later. ####
    nxInterp = 60;
    nyInterp = 60;
    xgstep = ygstep = 1.0;
    manual_scaling = 0;		// auto
    zrange_in_file = 0;
    min_zval = 0.0;
    max_zval = 1.0;
    xt = yt = NULL;
    xcurve = ycurve = NULL;

    [NXApp setDelegate:self];
    openPanel = [OpenPanel new];	// just caching
    [brightCWell setColor:NX_COLORRED];
    [darkCWell setColor:NX_COLORBLUE];
    [bgCWell setColor:NX_COLORWHITE];
    [lineCWell setColor:NX_COLORBLACK];
    [self colorWellsAction:self];	// reflect color well settings

    [versionField setStringValue: VERSION];

    [xposTextField setFloatingPointFormat:YES left:3 right:2];
    [yposTextField setFloatingPointFormat:YES left:3 right:2];
    [maxZplotField setFloatingPointFormat:YES left:3 right:2];
    [minZplotField setFloatingPointFormat:YES left:3 right:2];
    [maxZdataField setFloatingPointFormat:YES left:3 right:2];
    [minZdataField setFloatingPointFormat:YES left:3 right:2];


//  [contourView setDebugEnable:YES];		// only for debugging
    [contourView setDelegate:self];		// set ContourView's delegete to myself
    [contourView enableContinuousNotify:YES];
    [contourView setBipolar:YES];
    [contourView setEnabled: NO];		// start up disabled
    [contourView setFillEnable:YES];		// if color fill should be used
    [contourView setMinNumberOfPointsPerContour: 4];	// (optional) eliminates tiny noise
    [[contourView window] makeKeyAndOrderFront:self];
    return self;
}

- actionPrintCrossHair:sender
{
    if([sender state])
        [contourView enablePrintCrossHair:YES];
    else
        [contourView enablePrintCrossHair:NO];
    return self;
}

- markerDidMove:(int)viewtag :(int)marker :(float)x :(float)y :(int)mup
{
    // Marker 2 is generated by drag of cross point.
    if(marker == 0 || marker == 2) {
        [yposTextField setFloatValue: y];	// Y coord of Horiz line
	[contourView horizCrossSectionAtY:y :nxInterp forXs:xt :xcurve];
	[horizCSView setNewTrace: xcurve];
	[horizCSView drawIt:self];
    }
    if(marker == 1 || marker == 2) {
	[xposTextField setFloatValue: x];	// X coord of Vertical line
	[contourView vertCrossSectionAtX:x :nyInterp forYs:yt :ycurve];
	[vertCSView setNewTrace: ycurve];
	[vertCSView drawIt:self];
    }
    return self;
}



// Loads the sample data file from app wrapper
- openSample:sender
{
char        buf[MAXPATHLEN + 1];
NXBundle   *bundle;

    bundle = [NXBundle mainBundle];
    if( [bundle getPath:buf forResource:"SpaceTimeRF" ofType:"2d"] ) {
	[self openFile: buf];
    }
    return self;
}

- viewSampleFile:sender
{
char       buf[MAXPATHLEN + 1];
NXBundle   *bundle;
NXStream   *stream;

    bundle = [NXBundle mainBundle];
    if( [bundle getPath:buf forResource:"SpaceTimeRF" ofType:"2d"] ) {
	if ((stream = NXMapFile(buf, NX_READONLY)) == NULL) {
	    NXRunAlertPanel("File Error",
			"Cannot open file %s","OK",NULL,NULL, buf);
	    return nil;
	}
	[commentText readText: stream];
	NXClose(stream);
	[[[commentText window] setTitleAsFilename: buf] orderFront:self];
    }
    return self;
}


/*
 * appOpenFile:type: is invoked by Workspace via command-dragging of file to app
 * icon.  ContourPlot does not have any special doc type, so double-clicking
 * does not apply.
 */
- (int)appOpenFile:(const char *)path type:(const char *)type
{
    if ([self openFile:path] == nil) return NO;
    else return YES;
}

/*
 * The following method indicates that Yap is ready to open multiple
 * files at one time.  Really NO, but without this, appicon does not
 * accept command-dragged data file.
 */
- (BOOL)appAcceptsAnotherFile:sender
{
    return YES;
}


// connect to Menu's File -> Open button.
//
- open:sender
{
static int FirstFile = 1;
const char *fileName;
const char *const types[2] = {NULL, NULL};
  if(FirstFile) {
	if ([openPanel runModalForDirectory: NXHomeDirectory() file: NULL types: types] 
		&& (fileName =[openPanel filename])) {
	    [self openFile:fileName];
	    FirstFile = 0;
	}
  }
  else {
	/* Use last directory again */
	if ([openPanel runModalForTypes:types] && (fileName =[openPanel filename])) {
	    [self openFile:fileName];
	}
  }
    return self;
}



static char *keyword[] =
	{
	"NUMPOINTS",	"XRANGE",	"YRANGE",	"ZDATA",	"GRIDSTEP",
	"NINTERPOL",    "ZRANGE",
	""
	};
#define NKEYWORD	  7		/* # of names in *keyword[] */


- openFile:(const char *)fileName
{
FILE *fpin;
char linebuf[256], wdbuf[256];
int i, n2read;
int Done = 0;		// raised when ZDATA keyword is detected
int BadData = 0;	// raised if bad data detected
int prmnumber = 0;
int nzRead = 0;		// # of Z values read from file
NXStream *scStream;
char *scBuffer, *mbuf, *fptr;
int scLen, dummy;
float zmaxtmp = -1.0e30, zmintmp = 1.0e30;
float xstep = 0.0, ystep = 0.0;
BOOL bipolar = YES;
	if((fpin = fopen(fileName, "r")) == NULL) {
	    NXRunAlertPanel("File Error",
			"Cannot open file %s","OK",NULL,NULL, fileName);
	    return nil;
	}

	fptr = rindex(fileName, '/');
	fptr++;
	if(fptr) strncpy(datafile, fptr, 254);	// for later use in EPS save.

	[[contourView window] setTitleAsFilename: fileName];
	// Invalidate data until new file loaded successfully.
	[contourView setEnabled:NO];	// disable plot until OK
	[contourView setComment:"% None" :4];
	nx = ny = -1;
	zrange_in_file = 0;
        scStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);	// create memory stream
        NXPrintf(scStream, "%% Contour Plot by: %s\n", VERSION);
	NXPrintf(scStream, "%% From Grid File: %s\n", fileName);

	// Read line by line until we see ZDATA keyword, upon which
	// we switch to reading nx*ny float numbers regardless of line breaks.
	//
	while(!Done && !BadData)
	{
	    if(fgets(linebuf, 250, fpin) == NULL)
		break;
	    linebuf[250] = '\0';		/* for safety */
	    wdbuf[0] = '\0';
	    sscanf(linebuf, "%s", wdbuf);	/* first word */
	    if(wdbuf[0] == '%' || wdbuf[0] == '#' || wdbuf[0] == ';' || strlen(wdbuf) == 0) {
		if(wdbuf[0] == '%')
                    NXPrintf(scStream, "%s", linebuf);
		continue;	/* skip comment or blank lines */
	    }
	    prmnumber = -1;
	    for(i=0; i<NKEYWORD; i++) {
		   if( strcasecmp(wdbuf, keyword[i]) == 0 ) {
			prmnumber = i;
			break;
		   }
	    }

	    /* --- lookup in *parname[] done ----- */
	    if( prmnumber == -1 ) {
		fprintf(stderr, "...Unknown parameter: %s\n", wdbuf);
	    }
	    else {    /* valid prmnumber .. param name found */
		switch(prmnumber) {
		   case 0:	/* NUMPOINTS */
			sscanf(linebuf, "%*s %d %d", &nx, &ny);
			if(nx >= 5 && ny >= 5) {
			    if(zdata) free(zdata);
			    zdata = (float *)malloc((nx*ny)*sizeof(float));
			}
			else {
			    Done = BadData = 1;
	    		    NXRunAlertPanel("File Error",
			       "Bad number of points: (%d %d)","OK",NULL,NULL, nx, ny);
			}
			break;
		   case 1:	/* XRANGE */
			sscanf(linebuf, "%*s %f %f", &xmin, &xmax);
			break;
		   case 2:	/* YRANGE */
			sscanf(linebuf, "%*s %f %f", &ymin, &ymax);
			break;
		   case 3:	/* ZDATA */
			Done = 1;
			break;
		   case 4:	/* GRIDSTEP -- Optional */
			sscanf(linebuf, "%*s %f %f", &xgstep, &ygstep);
			break;
		   case 5:	/* NINTERPOL -- Optional */
			sscanf(linebuf, "%*s %d %d", &nxInterp, &nyInterp);
			break;
		   case 6:	/* ZRANGE -- Optional */
			sscanf(linebuf, "%*s %f %f", &min_zval, &max_zval);
			zrange_in_file = 1;
			if(min_zval < 0.0 && max_zval > 0.0) bipolar = YES;
			else bipolar = NO;
			break;

		} /* end switch(prmnumber) */
	    } /* end if( prmnumber == ..){} else {} */
	} /* end while( ... ) */

	if(BadData) {
	    fclose(fpin);
	    return nil;
	}
	// Now, start reading zdata[] values one by one
	nzRead = 0;
	n2read = nx*ny;
	for(i=0; i<n2read; i++) {
	    if(fscanf(fpin, "%f", &zdata[i]) == EOF) {
		BadData = 1;
	    	NXRunAlertPanel("File Error",
			"Premature end of file (%d read, %d expected)",
			"OK",NULL,NULL, nzRead, n2read);
		break;
	    }
	    nzRead++;
	    if(zmaxtmp < zdata[i]) zmaxtmp = zdata[i];
	    if(zmintmp > zdata[i]) zmintmp = zdata[i];
	} /* end of for(i=0...) */

	fclose(fpin);
	if(BadData) return nil;

	// Data successfully read
	// Copy comments in the data file to ContourView's comment to
	// keep track of data source.

        NXPrintf(scStream, "%% ---\n");
        NXFlush(scStream);
        NXGetMemoryBuffer(scStream, &scBuffer, &scLen, &dummy);
	// This may not be needed, but I don't know if scBuffer above is null terminated.
	// If it is, malloc() stuff is not needed.
	mbuf = (char *)malloc(scLen+1);
	strncpy(mbuf, scBuffer, scLen);
	mbuf[scLen] = '\0';
	[contourView setComment: mbuf : scLen];
	[horizCSView setComment: mbuf : scLen];
	[vertCSView setComment: mbuf : scLen];
	[[commentText window] setTitle: "Comments from Data File"];
	[commentText setText: mbuf];
	free(mbuf);
        NXCloseMemory(scStream, NX_FREEBUFFER);

	// Set info fields from file
	[nxField setIntValue: nx];
	[nyField setIntValue: ny];
	[xminField setFloatValue: xmin];
	[xmaxField setFloatValue: xmax];
	[yminField setFloatValue: ymin];
	[ymaxField setFloatValue: ymax];
	// These may be optionally specified in file.
	[xgStepField setFloatValue: xgstep];
	[ygStepField setFloatValue: ygstep];
	[nxInterpField setIntValue: nxInterp];
	[nyInterpField setIntValue: nyInterp];


// #### Check logic here ####
        [self setMinMaxZdata:zmintmp :zmaxtmp];
	if(!manual_scaling)
	    [self loadPlotRangeFromData:self];
	if(zmintmp < 0.0 && zmaxtmp > 0.0) bipolar = YES;
	else bipolar = NO;
	if(zrange_in_file && !manual_scaling)
            [self setMinMaxZplot:min_zval :max_zval];
	else
	    [self getMinMaxZplot];

	[contourView setBipolar: bipolar];
	// Set real data portion here.
	[contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
				ofSize: nx : ny
				withInterpolationTo: nxInterp : nyInterp];
	[contourView setGridLineWidth:0.2
				spacing: xgstep : ygstep];	// if grid is used

	// Horizontal Cross Section display initialization
        [horizCSView freeTraceCache];
	[horizCSView allocTraceCache: 1 : nxInterp];
	[horizCSView setScaleX: xmin : xmax];
	[horizCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
			      : max_zval + (max_zval - min_zval)*0.1];
	xstep = (xmax - xmin)/(nxInterp-1);
	if(xt) free(xt);
	if(xcurve) free(xcurve);
	xt = (float *) malloc(sizeof(float)*nxInterp);
	xcurve = (float *) malloc(sizeof(float)*nxInterp);
	for(i=0; i<nxInterp; i++) {
	    xt[i] = xmin + xstep * i;
	    xcurve[i] = 0.0;
	}
	[horizCSView setXvalues: xt];
	[horizCSView setDataPoints: NULL : NULL : 0];
	[horizCSView cacheDataPointsAndBackGround];
	[horizCSView setNewTrace: xcurve];


        [vertCSView freeTraceCache];
	[vertCSView allocTraceCache: 1 : nyInterp];
	[vertCSView setScaleX: ymin : ymax];
	[vertCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
			      : max_zval + (max_zval - min_zval)*0.1];
	ystep = (ymax - ymin)/(nyInterp-1);
	if(yt) free(yt);
	if(ycurve) free(ycurve);
	yt = (float *) malloc(sizeof(float)*nyInterp);
	ycurve = (float *) malloc(sizeof(float)*nyInterp);
	for(i=0; i<nyInterp; i++) {
	    yt[i] = ymin + ystep * i;
	    ycurve[i] = 0.0;
	}
	[vertCSView setXvalues: yt];
	[vertCSView setDataPoints: NULL : NULL : 0];
	[vertCSView cacheDataPointsAndBackGround];
	[vertCSView setNewTrace: ycurve];

	[self redisplayView];
	return self;
}


// For saving ContourView to an EPS file.
// This way, you don't have to go through a 2-step process of using BBFig or Yap
// to save as EPS.
//
- saveEPSRequest:sender
{
char pathbuf[1024];
char msgbuf[256];
char filename[256];
char *cptr, *sptr;
id savePanel;

    sptr = (char *)NXGetDefaultValue(APPNAME, "SaveDir");
    if(sptr[0] == '~')
        strcpy(pathbuf, NXHomeDirectory());
    else
	strcpy(pathbuf, sptr);

    strcpy(filename, datafile);
    strcat(filename, ".eps");	/* make it an EPS file */

    savePanel = [SavePanel new];

    if ([savePanel runModalForDirectory:pathbuf file:filename] 
	   && (saveFile = [savePanel filename])) {
	// Assume filename is OK.
	if([contourView saveEPStoFile: saveFile] == -1) {
	    // Couldn't save to EPS file
	    sprintf(msgbuf, "Cannot open %s for saving EPS file.", saveFile);
  	    NXRunAlertPanel("Save Error",msgbuf,"OK",NULL,NULL);
	}
	else {
	    // Save was successful.
	    strcpy(pathbuf, saveFile);
	    cptr = rindex(pathbuf, '/');
	    cptr = '\0';
            NXWriteDefault(APPNAME, "SaveDir", pathbuf);
	}
    }
    return self;
}



- redisplayView
{
	if(manual_scaling || zrange_in_file) {
	    [self getMinMaxZplot];
	    [contourView setMinMaxOfContourLevels:min_zval :max_zval];
	}

	[contourView setEnabled: YES];
	[contourView updateImageCache];
	[contourView display];
	[horizCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
			      : max_zval + (max_zval - min_zval)*0.1];
	[vertCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
			      : max_zval + (max_zval - min_zval)*0.1];
	[horizCSView drawIt:self];
	[vertCSView drawIt:self];

	return self;
}


/* from button to set plot range */
- loadPlotRangeFromData:sender
{
    [self getMinMaxZdata];
    [self setMinMaxZplot:min_zval :max_zval];
    [self redisplayView];
    return self;
}

- getMinMaxZplot
{
    max_zval = [maxZplotField floatValue];
    min_zval = [minZplotField floatValue];
    return self;
}

- getMinMaxZdata
{
    max_zval = [maxZdataField floatValue];
    min_zval = [minZdataField floatValue];
    return self;
}

- setMinMaxZplot:(float)min :(float)max
{
    [maxZplotField setFloatValue: max];
    [minZplotField setFloatValue: min];
    return self;
}

- setMinMaxZdata:(float)min :(float)max
{
    [maxZdataField setFloatValue: max];
    [minZdataField setFloatValue: min];
    return self;
}


/* from Z-scaling Min Max fields */
- setZscaleMinMax:sender
{
    zrange_in_file = 0;		// once manually set, ignore file's
    [self getMinMaxZplot];
    [self redisplayView];
    return self;
}

- setAutoManualScaling:sender
{
    switch([scaleSelectRadio selectedRow])
    {
	case 0:		/* Auto selected */
	    manual_scaling = 0;
	    [contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
				ofSize: nx : ny
				withInterpolationTo: nxInterp : nyInterp];
	    break;
	case 1:		/* manual selected */
	    manual_scaling = 1;
	    [self getMinMaxZplot];
	    break;
	default:
		break;
    }

    [self redisplayView];
    return self;
}




- setNcontourLevels:sender
{
static int new, old=16;
//    new = [sender intValue]/2 *2;	// force to even number
    new = [sender intValue];	// force to even number
    if(new == old) return self;
    old = new;
    [nlevelField setIntValue:new];
    [contourView setNumberOfContourLevels:new];
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- setLineWidth:sender
{
float newwidth;
    newwidth = [sender floatValue];
    if(newwidth < 0.0) newwidth = 0.0;
    if(newwidth > 20.0) newwidth = 20.0;
    [contourView setContourLineWidthMinor:newwidth andMajor: newwidth];
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- setGridSpacing:sender
{
float gx, gy;
    gx = [xgStepField floatValue];
    xgstep = gx;

    gy = [ygStepField floatValue];
    ygstep = gy;

    [contourView setGridLineWidth:0.2
			spacing: xgstep : ygstep];	// if grid is used
    if([tickSwitch state] || [gridSwitch state]) {
        [contourView updateImageCache];
        [contourView display];
    }
    return self;
}

- setNInterpolation:sender
{
int ix, iy;
int i;
float xstep, ystep;
    ix = [nxInterpField intValue];
    if(ix < 5) [nxInterpField setIntValue: nxInterp];
    else nxInterp = ix;
    
    iy = [nyInterpField intValue];
    if(iy < 5) [nyInterpField setIntValue: nyInterp];
    else nyInterp = iy;
    
    [contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
			    ofSize: nx : ny
			    withInterpolationTo: nxInterp : nyInterp];
    [contourView updateImageCache];
    [contourView display];

    [horizCSView freeTraceCache];
    [horizCSView allocTraceCache: 1 : nxInterp];	// realloc trace cache
    xstep = (xmax - xmin)/(nxInterp-1);
    if(xt) free(xt);
    if(xcurve) free(xcurve);
    xt = (float *) malloc(sizeof(float)*nxInterp);
    xcurve = (float *) malloc(sizeof(float)*nxInterp);
    for(i=0; i<nxInterp; i++) {
	xt[i] = xmin + xstep * i;
    }
    [horizCSView setXvalues: xt];
    [horizCSView cacheDataPointsAndBackGround];
    [contourView fakeMakerMovedFor:0];		// have fake delegate message
    
    [vertCSView freeTraceCache];
    [vertCSView allocTraceCache: 1 : nyInterp];	// realloc trace cache
    ystep = (ymax - ymin)/(nyInterp-1);
    if(yt) free(yt);
    if(ycurve) free(ycurve);
    yt = (float *) malloc(sizeof(float)*nyInterp);
    ycurve = (float *) malloc(sizeof(float)*nyInterp);
    for(i=0; i<nyInterp; i++) {
	yt[i] = ymin + ystep * i;
    }
    [vertCSView setXvalues: yt];
    [vertCSView cacheDataPointsAndBackGround];
    [contourView fakeMakerMovedFor:1];

    return self;
}


- clearAction:sender
{
    [contourView clear:self];
    return self;
}

- drawAction:sender
{
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- colorWellsAction:sender
{
NXColor cB, cD, cBG, cLine;
    cB =[brightCWell color];
    cD =[darkCWell color];
    cBG = [bgCWell color];
    cLine = [lineCWell color];
    [contourView setFillColors:cB :cD];
    [contourView setBackgroundColor:cBG];
    [contourView setContourLineColor:cLine];
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- useColorAction:sender
{
int state;
	state = [sender state];
	[contourView setFillEnable:(BOOL)state];
    	[contourView updateImageCache];
	[contourView display];
	return self;
}

- setGridMesh:sender
{
    if([sender state])
	[contourView setGridEnable:YES];
    else
	[contourView setGridEnable:NO];
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- setTickMark:sender
{
    if([sender state])
	[contourView setTicksEnable:YES];
    else
	[contourView setTicksEnable:NO];
    [contourView updateImageCache];
    [contourView display];
    return self;
}


- setAxis:sender
{
    if([sender state])
	[contourView setAxisEnable:YES];
    else
	[contourView setAxisEnable:NO];
    [contourView updateImageCache];
    [contourView display];
    return self;
}

- flipYAction:sender
{
int state;
	state = [sender state];
	[contourView setInvertY:(BOOL)state];
    	[contourView updateImageCache];
	[contourView display];
	return self;
}

- setMinNumberContour:sender
{
	[contourView setMinNumberOfPointsPerContour:[sender intValue]];
    	[contourView updateImageCache];
	[contourView display];
	return self;
}


- setDash:sender			/* set Dash pattern of contour */
{
float cdash[2];

    cdash[0] = [dashMarkField floatValue];
    cdash[1] = [dashSpaceField floatValue];
    [contourView setContourDash: cdash : 2];
    [contourView updateImageCache];
    [contourView display];
    return self;
}


- setStrokeContour:sender
{
int state;
	state = [sender state];
	[contourView setContourStrokeEnable:(BOOL)state];
    	[contourView updateImageCache];
	[contourView display];
	return self;
}




// This fixes the problem of messed up text when you resize the
// document window.
- windowDidResize:sender
{
	[commentText calcLine];
	[commentText sizeToFit];
	return self;
}


@end
