/* InspectPFunc.h		by Paul Kunz	November 1992
 * HippoDraw inspector for Minuit fitting
 *
 * Copyright (C)  1992  The Board of Trustees of
 * The Leland Stanford Junior University.  All Rights Reserved.
 */

#import "Draw.subproj/draw.h"
#import "InspectPFunc.h"

const char InspectPFunc_h_rcsid[] = INSPECTPFUN_H_ID;
const char InspectPFunc_m_rcsid[] = "$Id: InspectPFunc.m,v 1.71.2.5 1994/02/08 20:29:36 rensing Exp $";

#import "FineSlider.h"
#import "HDrawApp.h"
#import "HGraphicView.h"
#import "NewInspector.h"
#import "Plot.h"

#import "PFunction.h"
#import "LinearX.h"
#import "LinearY.h"
#import "Gaussian.h"
#import "Quadratic.h"
#import "Exponential.h"
#import "Zshape.h"
#import "PowerLaw.h"

#import "Minuit.h"
#import <sys/dir.h>
#import <float.h>

#define	FUNC_ALL	0
#define FUNC_PLOT	1

#define VALUE_TAG	0
#define LIMITS_TAG	1

#define	PARM_MAX	0
#define PARM_VALUE	1
#define PARM_MIN	2
#define PARM_STEP       3

#define CHISQ_POS 	0
#define DOF_POS		1
	      
#define PLOT_INDIVIDUALS	0
#define PLOT_SUM		1

#define GPPCOMPILE
 //#define GPPLIBRARY "/usr/local/lib/libg++.a"
#define GPPLIBRARY ""
#define GPPINCLUDEDIR "/usr/local/lib/g++-include"

#define PANEL_NAME "Plot Functions"    

@interface InspectPFunc(Private)

- (NXBundle *) compile:(const char *)directory :(const char *)file;
 /*
  * Compiles, links, source file into a bundle and returns the intialized
  * bundle 
  */
  
- loadFunction:(const char *)directory :(const char *)file;
 /*
  * Loads the PFunction in directory/file and adds it to the
  * available function list and to the dynamically loaded function
  * list.
  */
- getFitData;
 /*
  * Get the data needed for a fit out of the display
  */
    
- loadMinuit;
 /*
  * Load Minuit with the current list of parameters
  */

- enableButtons:(BOOL) flag;
 /*
  * Enables or disables al buttons according to value of flag.
  */
  
- updateChiSq: (float) chi2;
 /*
  * Update chi^2 form with given value.
  */
- updateDoF;
 /* 
  * update the degrees of freedom form
  */
  
- doSingleFit:sender;
 /* fit a single plot */
@end

static id 	fitObject = nil;
static NXRect   stopButtonFrame;
static double       old_fcn_value;

static char *emptyBinTitle[] = 
    {"ignore","set error to 1","disable fitting"};
static char *doFitTitle[] = 
    {"unbinned","integral/fixed norm","integral over bin",
    "average over bin","1 sample per bin"};
static char *lineStyleTitle[] = 
    {"Solid","Dash","Dot","DotDash","Invisible"};
        

@implementation InspectPFunc
	
/* Function called by Minuit FCN */
void refreshdisplay_(double *fcn_value, int *iflag, int *quit_minuit)
{
    NXEvent            *event;
    NXPoint             where;
    float               cutoff = 0.025;
    double              delta;

    if ( *iflag >= 2 ) {
	delta = (*fcn_value - old_fcn_value) / old_fcn_value;
	if (delta < -cutoff) {
	    old_fcn_value = MAX(fabs(*fcn_value), 1e-06);
	    [fitObject updateParmForm];
	    [fitObject updateChiSq: *fcn_value];
	    [fitObject updatePlot];
	}
    }
    *quit_minuit = 0;
    if ((event = [NXApp peekAndGetNextEvent:NX_MOUSEDOWNMASK]) != NULL) {
	where = event->location;
	if (NXPointInRect(&where, &stopButtonFrame)) {
	    if (NXRunAlertPanel("Cancel fitting warning",
				 "Do you really want to cancel this fit ?",
				 "OK", "continue fitting", NULL)
		== NX_ALERTDEFAULT) {
		*quit_minuit = 1;
	    }
	}
    }
    return;
}
void loadParams(double *parm)
{
    [fitObject loadParams:parm];
}


- initInspFor:aDraw
{
    NXBundle		*bundle;
    PopUpList		*popup;
    PFunction   	*func;
    char		buf[MAXPATHLEN+1];
    int			itype;
    
    [super initInspFor:aDraw];
    
 /* Initial Function List */
    availFuncList = [[List allocFromZone:[self zone]] initCount:0];
    dynamFuncList = [[List allocFromZone:[self zone]] initCount:0];

    func = [[Gaussian allocFromZone:[self zone]] init];
    [availFuncList addObject:func];
    
    func = [[LinearX allocFromZone:[self zone]] init];
    [availFuncList addObject:func];

    func = [[LinearY allocFromZone:[self zone]] init];
    [availFuncList addObject:func];

    func = [[Quadratic allocFromZone:[self zone]] init];
    [availFuncList addObject:func];
    
    func = [[Exponential allocFromZone:[self zone]] init];
    [availFuncList addObject:func];
    
    func = [[PowerLaw allocFromZone:[self zone]] init];
    [availFuncList addObject:func];
    
    func = [[Zshape allocFromZone:[self zone]] init];
    [availFuncList addObject:func];

  /* Load interface */
    bundle = [NXBundle bundleForClass:[self class]];
    if ( [bundle getPath:buf forResource:"InspectPFunc" ofType:"nib"] ) {
        [NXApp loadNibFile:buf owner:self
	       withNames:NO fromZone:[self zone]];
    }
    [funcBrowser setMinColumnWidth:1];
    [funcBrowser setMaxVisibleColumns:2];
    [funcBrowser setTitle:"Functions" ofColumn:0];
    [funcBrowser loadColumnZero];
    [funcBrowser addColumn];
    [self updateBrowser];

    [funcRadio sendAction];
    
    [theInspector addView:[contentBox contentView]
                  withName:"Plot Functions" withSupervisor:self];
    [theInspector showPanelWithName:PANEL_NAME];
    	
    popup = [emptyBinButton target];
    emptyBinMatrix = [popup itemList];
    [popup setTarget:self];
    [popup setAction:@selector(ebTypeChanged:)];
    
    popup = [fitTypeButton target];
    fitTypeMatrix = [popup itemList];
    [popup setTarget:self];
    [popup setAction:@selector(fitTypeChanged:)];
    itype = FIT_ONESAMPLE;
    [fitTypeMatrix selectCellWithTag:itype];
    [fitTypeButton setTitle:doFitTitle[itype]];
    
    popup = [plotFuncButton target];
    plotFuncMatrix = [popup itemList];
    [popup setTarget:self];
    [popup setAction:@selector(plotFuncLineStyleChanged:)];
    [plotFuncMatrix selectCellWithTag:0];
    [plotFuncButton setTitle:lineStyleTitle[0]];
    
    popup = [plotFSumButton target];
    plotFSumMatrix = [popup itemList];
    [popup setTarget:self];
    [popup setAction:@selector(plotFSumLineStyleChanged:)];
    [plotFSumMatrix selectCellWithTag:0];
    [plotFSumButton setTitle:lineStyleTitle[0]];
    
    fitObject = self;

    return self;
}
- setFitter:anObject
{
    fitter = anObject;
    return self;
}
/* Action Methods */

- add:sender
{
    List 	*list;
    parm_t 	ptype;
    id		matrix;
    display	disp;
    graphtype_t type;
    id          func, newFunc;
    int         i, irc;
    
    if ( !selectedPlot ) return self;
    
    ptype = [parmRadio selectedRow];
    if ( ptype != PLAY_VALUES ) {
	list = [selectedPlot functionList];
	[list makeObjectsPerform:@selector(copyToVaried:) with:(id)&ptype];
	[parmRadio selectCellAt:PLAY_VALUES :0];
	ptype = PLAY_VALUES;
    }
    matrix = [funcBrowser matrixInColumn:0];
    i = [matrix selectedRow];
    if ( i < 0 ) {
        return self;
    }
    disp = [selectedPlot histDisplay];
    type = h_getDispType( disp );
    if ( (type == XYPLOT || type == STRIPCHART ) && 
          h_getBinding(disp,YERROR) < 0 ) {
        irc = NXRunAlertPanel( "Warning",
	        "Chi-Square and errors from fitting will have no"
		" meaning without y-errors assigned to plot.",
		"Proceed", "Cancel", NULL );
	if ( irc != NX_ALERTDEFAULT ) return self;
    }
    func = [availFuncList objectAt:i];
    newFunc = [func copyFromZone:[selectedPlot zone]];
    [selectedPlot addFunction:newFunc];
    list = [selectedPlot functionList];
    [newFunc setInitialValues];
    i = [[fitTypeMatrix selectedCell] tag];
    [newFunc setFitType:&i];

    [[funcRadio selectCellAt:0 :FUNC_PLOT] sendAction];

    i = [plotFuncMatrix selectedRow];
    [newFunc setFuncLineStyle:&i];

    [list makeObjectsPerform:@selector(updateFromType:)with:(id)&ptype];
    [self updatePlotMatrix];
    [self updatePlot];
    [self updateChiSq];
    [[[graphicView window] delegate] dirty:self];
    return self;
}

- remove:sender
{
    if (!selectedPlot) return self;
    if (!plotFunction) return self;
    
    [selectedPlot removeFunction:plotFunction];
    [plotFunction free];
    lastPlot = nil;
    [self updatePlotMatrix];
    [self updatePlot];
    [self updateView];
    [self updateChiSq];
    [[[graphicView window] delegate] dirty:self];
    return self;
}
- browserClicked:sender
{
    List		*funcList;
    Matrix		*matrix;
    int			i;
    
    if ( [funcRadio selectedCol] == FUNC_ALL ) {
        [self enableButtons:NO];
        return self;
    }
    if ( !selectedPlot ) {
        [self enableButtons:NO];
        return self;
    }
    
    funcList = [selectedPlot functionList];
    matrix = [funcBrowser matrixInColumn:0];
    i = [matrix selectedRow];
    if ( (i < 0) && [matrix cellCount] ) {
    	i = 0;		/* on coming forward, it's not yet set */
    } 
    if ( i < 0 ) {
        plotFunction = nil;
        [self enableButtons:NO];
        return self;
    }
    plotFunction = [funcList objectAt:i];
    matrix = [funcBrowser matrixInColumn:1];
    i = [matrix selectedRow];
    if ( (i < 0) && [matrix cellCount] ) {
    	i = 0;		/* on coming forward, it's not yet set */
    } 
    if ( i < 0 ) {
        [self enableButtons:NO];
        return self;
    }
    
    pfIndex = i;
    [self enableButtons:YES];
    [self updateParmForm];
    [self updatePlotMatrix];
    return self;
}
- animateLimits:sender
{
    Storage	*parmList;
    fitParm	*parm;
    parm_t	type;
    double   	amplitude, initialAngle, angle;
    double    	initialValue;
    float     	twoPi;
    double    	min_value, mean_value, max_value;
    int       	steps = 30;
 
    type = [parmRadio selectedRow];
    parmList = [plotFunction parmListOfType:type];
    parm = [parmList elementAt:pfIndex];
    initialValue = parm->value;
    min_value = [ parmSlider mminValue ];
    max_value = [ parmSlider mmaxValue ];
    mean_value = 0.5*(max_value + min_value);
    if (max_value == min_value) return self;
    amplitude = 0.5*(max_value - min_value);
    initialAngle = asin( (initialValue-mean_value)/amplitude );
    twoPi = 2.0 * M_PI;
    for (angle = 0.; angle <= twoPi; angle += twoPi / steps) {
	parm->value = mean_value + amplitude * sin(initialAngle + angle);
	[parmSlider setFloatValue:parm->value];
        [plotFunction updateFromType:&type];
	[self updatePlot];
  	[self updateChiSq];
    }
    
    parm->value = initialValue;
    [plotFunction updateFromType:&type];
    [parmSlider setFloatValue:initialValue ];
 
    return self;
}
- sliderDragged:sender
{
    List                *list;
    parm_t              type;
    float		value;
    
    type = [parmRadio selectedRow];
    if ( type != PLAY_VALUES ) {
        list = [selectedPlot functionList];
	[list makeObjectsPerform:@selector(copyToVaried:) with:(id)&type];
	[[parmRadio selectCellAt:PLAY_VALUES :0] sendAction];
    }
    value = [parmSlider floatValue];
    [plotFunction setFloatValue:value at:pfIndex];
    [parmForm setFloatValue:value at:PARM_VALUE];
    [self updatePlot];
    [self updateChiSq];
    return self;
}
- sliderNudged:sender
{
    Storage	*parmList;
    fitParm	*parm;
    List                *list;
    parm_t              type;
    float		value;
	int			nudge;
    
    type = [parmRadio selectedRow];
    if ( type != PLAY_VALUES ) {
        list = [selectedPlot functionList];
	[list makeObjectsPerform:@selector(copyToVaried:) with:(id)&type];
	[[parmRadio selectCellAt:PLAY_VALUES :0] sendAction];
    }
    parmList = [plotFunction parmListOfType:type];
    parm = [parmList elementAt:pfIndex];
	nudge= [sender tag];
	if ( nudge < 0 )	{
	    value = parm->value - parm->step_size;
	}
	else	{
	    value = parm->value + parm->step_size;
	}
    [plotFunction setFloatValue:value at:pfIndex];
    [parmForm setFloatValue:value at:PARM_VALUE];
    [self updatePlot];
    [self updateChiSq];
    return self;
}

- checkFitValid
{
    List                *list;
    parm_t              type;
    
    type = [parmRadio selectedRow];
    if ( type == FIT_VALUES && ![selectedPlot isFitted] ) {
        list = [selectedPlot functionList];
        [list makeObjectsPerform:@selector(copyToVaried:) with:(id)&type];
    	[self setParmType:PLAY_VALUES];
    }
    if ( ![selectedPlot isFitted] ) {
        [[parmRadio cellAt:FIT_VALUES :0] setEnabled:NO ];
    }
    return self;
}


- textDidEnd:sender endChar:(unsigned short)whyEnd
{
    float          lower, upper, value, step;
    parm_t type = [parmRadio selectedRow];

    if (selectedPlot == NULL) return self;
    
    upper = [parmForm floatValueAt:PARM_MAX];
    value = [parmForm floatValueAt:PARM_VALUE];
    lower = [parmForm floatValueAt:PARM_MIN];
    step  = [parmForm floatValueAt:PARM_STEP];

    if ( [parmRadio selectedRow] != PLAY_VALUES ) {
	[[selectedPlot functionList]
	    makeObjectsPerform:@selector(copyToVaried:) with:(id)&type];
	[[parmRadio selectCellAt:PLAY_VALUES :0] sendAction];
    }
    
    switch ([parmForm selectedRow]) {
      case PARM_MIN :
	  if ( lower > upper ) {
	      upper = lower + (upper - value);
	  }
	  if ( lower > value ) {
	      value = lower;
	  }
	  step = (upper - lower)/ 100.0;
	  break;
      case PARM_MAX:
	  if ( upper < lower ) {
	      lower = upper - (value - lower);
	  }
	  if ( upper < value ) {
	      value = upper;
	  }
	  step = (upper - lower)/ 100.0;
	  break;
      case PARM_VALUE:
	  if ( value < lower ) {
	      lower = value - (upper-value);
	  }
	  if ( value > upper ) {
	      upper = value + (value-lower);
          }
	  step = (upper - lower)/ 100.0;
	  break;
      case PARM_STEP:
	  if ( upper < (lower + 100*step) ) {
	      upper = lower + 100*step;
	  }
	  if ( lower > (upper + 100*step) ) {
	      lower = upper - 100*step;
	  }
	  break;
    }
    [parmForm setFloatValue:upper at:PARM_MAX];
    [parmForm setFloatValue:value at:PARM_VALUE];
    [parmForm setFloatValue:lower at:PARM_MIN];
    [parmForm setFloatValue:step  at:PARM_STEP];
    [[parmForm window] disableDisplay];
    [parmSlider setMaxValue:upper];
    [parmSlider setMinValue:lower];
    [parmSlider setFloatValue:value];
    [[parmForm window] reenableDisplay];
    [[parmForm window] displayIfNeeded];

    return self;
}
- parmFormChanged:sender
{
    Storage		*parmList;
    fitParm		*parm;
    parm_t		type;
    
    type = [parmRadio selectedRow];
    parmList = [plotFunction parmListOfType:type];
    parm = [parmList elementAt:pfIndex];
    if ( !parm ) return self;
    
    parm->upper_limit = [parmForm floatValueAt:PARM_MAX];
    parm->value       = [parmForm floatValueAt:PARM_VALUE];
    parm->lower_limit = [parmForm floatValueAt:PARM_MIN];
    parm->step_size   = [parmForm floatValueAt:PARM_STEP];
    [plotFunction updateFromType:&type];
    [self updatePlot];
    [self updateChiSq];
    return self;
    return self;
}
- changeFix:sender
{
    Storage		*parmList;
    fitParm		*parm;
    parm_t		type;
    int                 row;
    BOOL                yesno;

    type = [parmRadio selectedRow];
    parmList = [plotFunction parmListOfType:type];
    parm = [parmList elementAt:pfIndex];
    row = [sender selectedRow];
    switch (row) {
    case VALUE_TAG:
    	yesno = [[fixMatrix cellAt:VALUE_TAG :0] state];
	parm->fixedValue = yesno;
	if (yesno) {
	    parm->step_size = 0.0;
	} else {
	    parm->step_size = 0.01*(parm->upper_limit - parm->lower_limit);
	    if (parm->step_size == 0.0) parm->step_size = 0.1;
	}
	[[parmForm cellAt:PARM_VALUE :0] setEnabled:!(yesno)];
	[[parmForm cellAt:PARM_STEP  :0] setEnabled:!(yesno)];
    	[self updateDoF];
	break;
    case LIMITS_TAG:
    	yesno = [[fixMatrix cellAt:LIMITS_TAG :0] state];
	parm->fixedLimits = yesno;
	[[parmForm cellAt:PARM_MAX :0] setEnabled:!(yesno)];
	[[parmForm cellAt:PARM_MIN :0] setEnabled:!(yesno)];
	break;
    }    
    return self;
}
- funcRadioChanged:sender
{
    unsigned int	functype;
    
    functype = [funcRadio selectedCol];
    switch (functype) {
        case FUNC_ALL:
	    [addButton  setTitle:"Add"];
	    [addButton  setAction:@selector(add:)];
	    break;
	case FUNC_PLOT:
	    [addButton  setTitle:"Remove"];
	    [addButton  setAction:@selector(remove:)];
	    break;
	default:
	    ;
    }
    [self updateBrowser];
    [self browserClicked:self];
    return self;
}
- parmRadioChanged:sender
{   
    List	*funcList;	/* current function List */
    int		type;
    
    if ( !selectedPlot ) return self;
    
    type = [parmRadio selectedRow];
    funcList = [selectedPlot functionList];
    [funcList makeObjectsPerform:@selector(updateFromType:)with:(id)&type];
    [self updatePlot];  /* updating the plot will force the rest */
    [self updateChiSq];
    [self updateParmForm];
    return self;
}

- fitTypeChanged:sender
{
    List		*funcList;
    enum FCNType_t type;

    if (!selectedPlot ) return self;
    funcList = [selectedPlot functionList];
    if (!funcList ) return self;
    
    type = (enum FCNType_t) [[fitTypeMatrix selectedCell] tag];
    [funcList makeObjectsPerform:@selector(setFitType:) with:(id)&type];

    if ( type == FIT_MAXLIKE ) 
        [emptyBinButton setEnabled:NO];
    else if (h_getDispType( [selectedPlot histDisplay] ) == HISTOGRAM)
        [emptyBinButton setEnabled:YES];
    [self updateChiSq];
    
    return self;
}

- ebTypeChanged:sender
{
    List		*funcList;
    int	type;
    
    if (!selectedPlot ) return self;
    funcList = [selectedPlot functionList];
    if (!funcList ) return self;

    type = [emptyBinMatrix selectedRow];
    [funcList makeObjectsPerform:@selector(setEbType:) with:(id)&type];
    [self updateChiSq];
    
    return self;
}

- plotFuncLineStyleChanged:sender
{
    List		*funcList;
    int	type;
    id matrix;
    int i;
    PFunction          *func;
    
    if (!selectedPlot ) return self;
    funcList = [selectedPlot functionList];
    if (!funcList ) return self;

    type = [[plotFuncMatrix selectedCell] tag];

    matrix = [funcBrowser matrixInColumn:0];
    i = [matrix selectedRow];
    if (i < 0) return self;

    func = [funcList objectAt:i];
    [func setFuncLineStyle:&type];
    [self updatePlotMatrix];
    [self updatePlot];
    [[[graphicView window] delegate] dirty:self];
    
    return self;
}
  
- plotFSumLineStyleChanged:sender
{
    display	disp;
    int	type;
    
    if (!selectedPlot ) return self;
    disp = [selectedPlot histDisplay];

    type = [[plotFSumMatrix selectedCell] tag];
    h_setFSumLineStyle(disp, (linestyle_t)type);
    [self updatePlotMatrix];
    [self updatePlot];
    [[[graphicView window] delegate] dirty:self];
        
    return self;
}

- doFit:sender
{
    List               *myslist;
    int                 scount;
    Graphic            *g;
    id s1, s2;
    NXRect bbox;
    
    myslist = [graphicView selectedGraphics];
    scount = [myslist count];
    if (scount == 1)
	[self doSingleFit:sender];
    else {
	if (NXRunAlertPanel("Multi-plot Fit",
		    "Do you really want to fit all plots in the selection?",
			    "Yes", "NO", NULL)
	    != NX_ALERTDEFAULT) {
	    return self;
	}
	s1 = selectedPlot;   /* save values and restore after */
	s2 = firstPlot;
	
	while (scount--) {
	    g = [myslist objectAt:scount];
	    if ([g isKindOf:[Plot class]]) {
		selectedPlot = (Plot *)g;
		firstPlot = (Plot *)g;
		[g getBounds:&bbox];
		[graphicView scrollRectToVisible:&bbox];
		[self doSingleFit:sender];
	    }
	}
	selectedPlot = s1;
	firstPlot = s2;
    }

    return self;
}

- addToAvailFuncs:sender
{
    OpenPanel		*openpanel = [[OpenPanel new] allowMultipleFiles:YES];
    Matrix		*matrix;
    const char *const 	fileTypes[2] = { "m", NULL };
    const char 		*directory;
    const char *const 	*files;
    int			row;
         
    if ([openpanel runModalForTypes:fileTypes]) {
	directory = [openpanel directory];
	files = [openpanel filenames];
	while (*files) {
	    [self loadFunction:directory :*files];
	    files++;
	}
    }
    [theInspector showPanelWithName:PANEL_NAME];
    [theInspector orderFrontPanel:self];
    [[funcRadio selectCellAt:0 :FUNC_ALL] sendAction];
    matrix = [funcBrowser matrixInColumn:0];
    row = [matrix cellCount] -1;
    [matrix selectCellAt:row :0];
    [matrix scrollCellToVisible:row :0];
    return self;
}
- startArchivingTo:(const char *)directory
{
    List		*plotList;
    List		*funcList;
    List		*saveFuncList;
    Plot		*plot;
    PFunction		*func, *dynfunc;
    const char		*filename;
    unsigned int	i, j, k;
    
    if ( !(k = [dynamFuncList count]) ) return self;
    
    saveFuncList = [[List allocFromZone:[self zone]] initCount:0];
    plotList = [graphicView plotList];
    if ( !plotList ) return self;
    if ( !(i = [plotList count]) ) return self;
    
    while ( i-- ) {
	plot = [plotList objectAt:i];
	funcList = [plot functionList];
	if ( funcList && (j = [funcList count]) ) {
	    while ( j-- ) {
		func = [funcList objectAt:j];
		k = [dynamFuncList count];
		while ( k-- ) {
		    dynfunc = [dynamFuncList objectAt:k];
		    if ( [dynfunc isKindOf:[func class]] ) {
			[saveFuncList addObjectIfAbsent:dynfunc];
		    }
		}
	    }
	}
    }
    if ( !(i = [saveFuncList count]) ) return self;
    while ( i-- ) {
        dynfunc = [saveFuncList objectAt:i];
	filename = [dynfunc filename];
	[dynfunc writeFile:directory :filename];
    }
    return self;
}
- startUnarchivingFrom:(const char *)directory
{
    DIR			*dirp;
    struct direct	*dp;
    char		*ptr;

    dirp = opendir( directory );
    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
        ptr = strrchr( dp->d_name, '.' );
	if ( ptr && !strcmp(ptr, ".m") ) {
	    [self loadFunction:directory :dp->d_name];
	}
    }
    closedir(dirp);
    return self;
}
/* Internal methods */

- fetchMinuit
{
    List		*funcList;
    Storage		*parmList;
    PFunction		*func;
    fitParm		*parm;
    parm_t		type;
    unsigned int        i, j, k, num_funcs, num_parms;

    type = [parmRadio selectedRow];
    if ( type != FIT_VALUES ) return self;
    
    funcList = [selectedPlot functionList];
    num_funcs = [funcList count];
    k = 0;
    for ( i = 0; i < num_funcs; i++ ) {
        func = [funcList objectAt:i];
	parmList = [func parmListOfType:type];
	num_parms = [parmList count];
	for ( j = 0; j < num_parms; j++ ) {
	    parm = [parmList elementAt:j];
	    k++;
	    [fitter getParms:parm at:k];
	}
	[func updateFromType:&type];
    }
    return self;
}
- loadParams:(double *)param
{
    List		*funcList;
    Storage		*parmList;
    PFunction		*func;
    fitParm		*parm;
    parm_t		type;
    int        i, j, k, num_funcs, num_parms;

    type = [parmRadio selectedRow];
    
    funcList = [selectedPlot functionList];
    num_funcs = [funcList count];
    k = 0;
    for ( i = 0; i < num_funcs; i++ ) 
    {
        func = [funcList objectAt:i];
	parmList = [func parmListOfType:type];
	num_parms = [parmList count];
	for ( j = 0; j < num_parms; j++ ) 
	{
	    parm = [parmList elementAt:j];
	    parm->value = param[k++];
	}
	[func updateFromType:&type];
    }
    return self;
}

- setParmType:(int)type
{
    [[parmRadio cellAt:type :0] setEnabled:YES ];
    [parmRadio  selectCellAt:type :0 ];
    return self;
}
- updatePlot
{
    [graphicView graphicsPerformNOP:selectedPlot];
    [[[graphicView window] flushWindow] makeKeyWindow];
    return self;
}
- (List *) functionList
{
    List		*funcList;
    unsigned int	functype;
    
    functype = [funcRadio selectedCol];
    switch (functype) {
        case FUNC_ALL:
	    funcList = availFuncList;
	    break;
	case FUNC_PLOT:
	    funcList = [selectedPlot functionList];
	    break;
	default:
	    funcList = nil;
    }
    return funcList;
}

/* Methods for updating Inspector */

- fullUpdate
{
    graphtype_t		type;
    
    if ( [selectedPlot isKindOf:[Plot class]]  )  {    
   	[addButton setEnabled:YES];
    } else {
   	[addButton setEnabled:NO];
    }
    type = h_getDispType( [selectedPlot histDisplay] );
    if ( !( type == XYPLOT || type == STRIPCHART || type == HISTOGRAM ) ) {
   	[addButton setEnabled:NO];
    }
    [self updateDispType:selectedPlot];
    if ( [selectedPlot hasFunction] ) {
	 [funcRadio selectCellAt:0 :FUNC_PLOT];
    } else {
	 [funcRadio selectCellAt:0 :FUNC_ALL];
    }
    [self updateParmType];
    [self funcRadioChanged:self];
    [self browserClicked:self];
    [self updatePlotMatrix];
    
    // update chi^2 AFTER updating parmType
    if ( [selectedPlot hasFunction] ) {
	 [self updateChiSq];
    }
    [self checkFitValid];
    lastPlot = selectedPlot;
    [self updateBrowser];
    return self;
}
     
- updateView
{
    if ( selectedPlot != lastPlot ) [self fullUpdate];
    return self;
}

- updateEmptySelection
{
    if ( lastPlot == nil ) return self;
    [addButton setEnabled:NO];
    [self enableButtons:NO];
    [self updateBrowser];
    [self updatePlotMatrix];
    [self updateChiSq];
    lastPlot = nil;
    return self;
}
- updateMultiSelection
{
    if ( lastPlot == nil ) return self;
    [addButton setEnabled:NO];
    [self updateBrowser];
    lastPlot = nil;
    return self;
}
- updateBrowser
{
    [funcBrowser loadColumnZero];
    [funcBrowser addColumn];
    [funcBrowser displayAllColumns];
    return self;
}
- updateChiSq
{
    List		*funcList;
    Storage		*parmList;
    PFunction		*func;
    fitParm		*parm;
    double		*values;
    double		fcnValue = 0.0;
    unsigned int        i, j, k, num_funcs, num_parms, tot_parms;
    parm_t		type;
    
    funcList = [selectedPlot functionList];
     
    if (!funcList) 
    {
    	[chiSqForm setEnabled:NO];
        return self;
    } else {
        [chiSqForm setEnabled:YES];
    }

    [self getFitData];
    
    type = [parmRadio selectedRow];
    funcList = [selectedPlot functionList];
    num_funcs = [funcList count];
    tot_parms = 0;
    for ( i = 0; i < num_funcs; i++ ) {
        func = [funcList objectAt:i];
	tot_parms += [func numberArgs];
    }
    NX_MALLOC( values, double, tot_parms );
    k = 0;
    for ( i = 0; i < num_funcs; i++ ) {
        func = [funcList objectAt:i];
	parmList = [func parmListOfType:type];
	num_parms = [parmList count];
	for ( j = 0; j < num_parms; j++ ) {
	    parm = [parmList elementAt:j];
	    values[k++]   = parm->value;
	}
    }

    minuitfcn_(&fcnValue, values, &tot_parms, &fcnParms);
    [self updateChiSq:fcnValue];
    
    [self updateDoF];
    NX_FREE(values);
    return self;
}

- updateParmForm
{
    Storage		*parmList;
    fitParm		*parm;
    parm_t		type;
    
    type = [parmRadio selectedRow];
    parmList = [plotFunction parmListOfType:type];
    parm = [parmList elementAt:pfIndex];
    if ( !parm ) return self;
    
    [parmForm setFloatValue:parm->upper_limit at:PARM_MAX];
    [parmForm setFloatValue:parm->value       at:PARM_VALUE];
    [parmForm setFloatValue:parm->lower_limit at:PARM_MIN];
    if ( type == FIT_VALUES ) {
    	[parmForm setTitle:"Error"              at:PARM_STEP];
        [parmForm setFloatValue:parm->error     at:PARM_STEP];
    } else {
    	[parmForm setTitle:"Step"               at:PARM_STEP];
    	[parmForm setFloatValue:parm->step_size at:PARM_STEP];
    }
    [[parmForm cellAt:PARM_VALUE :0] setEnabled:!(parm->fixedValue)];
    [[parmForm cellAt:PARM_STEP  :0] setEnabled:!(parm->fixedValue)];
    [[parmForm cellAt:PARM_MAX   :0] setEnabled:!(parm->fixedLimits)];
    [[parmForm cellAt:PARM_MIN   :0] setEnabled:!(parm->fixedLimits)];
    
    [[fixMatrix cellAt:VALUE_TAG  :0] setState:parm->fixedValue ];
    [[fixMatrix cellAt:LIMITS_TAG :0] setState:parm->fixedLimits];
    [fixMatrix display];

     [[parmSlider window] disableDisplay];
    [parmSlider setMaxValue:  [parmForm floatValueAt:PARM_MAX]];
    [parmSlider setMinValue:  [parmForm floatValueAt:PARM_MIN]];
    [parmSlider setFloatValue:[parmForm floatValueAt:PARM_VALUE]];
    [[parmSlider window] reenableDisplay];
    [parmSlider display];
    
    return self;
}

- updateParmType
{
    List	*funcList;
    PFunction	*func;
    parm_t	type;
    
    if ( !selectedPlot ) return self;
    funcList = [selectedPlot functionList];
    if ( funcList && [funcList count] ) {
        func = [funcList lastObject];
	type = [func currentType];
	[self setParmType:type];
    }
    return self;
}
- updatePlotMatrix
{
    display             disp;
    List               *funcList;
    PFunction          *func;
    int                 itype;
    id                  matrix;
    int                 i;

    if (!selectedPlot || [funcRadio selectedCol] == FUNC_ALL) {
	[plotFuncButton setEnabled:NO];
	[plotFSumButton setEnabled:NO];
	return self;
    }

    disp = [selectedPlot histDisplay];
    h_setPlotFuncs(disp, 1);     // just to be sure
    h_setPlotFuncSum(disp, 1);

    funcList = [selectedPlot functionList];
    matrix = [funcBrowser matrixInColumn:0];
    i = [matrix selectedRow];
    if (i < 0) {
	[plotFuncButton setEnabled:NO];
    } else {
        [plotFuncButton setEnabled:YES];
	func = [funcList objectAt:i];
	itype = [func funcLineStyle];
	[plotFuncMatrix selectCellWithTag:itype];
	[plotFuncButton setTitle:lineStyleTitle[itype]];
    }
    
    itype = h_getFSumLineStyle(disp);
    [plotFSumMatrix selectCellWithTag:itype];
    [plotFSumButton setTitle:lineStyleTitle[itype]];
    [plotFSumButton setEnabled:YES];

    return self;
}

- updateDispType:(Plot *)plot
{
    List		*funcList;
    PFunction		*func;
    graphtype_t		dtype;
    int			itype;
    
    dtype = h_getDispType( [plot histDisplay] );
    [ [fitTypeMatrix findCellWithTag:FIT_ONESAMPLE] setEnabled:YES ];
    if ( dtype == XYPLOT || dtype == STRIPCHART ) {
	[ [fitTypeMatrix findCellWithTag:FIT_INTEGRAL] setEnabled:NO ];
 //	[ [fitTypeMatrix findCellWithTag:FIT_INTFIXEDNORM] setEnabled:NO ];
	[ [fitTypeMatrix findCellWithTag:FIT_AVERAGE] setEnabled:YES ];
 //	[ [fitTypeMatrix findCellWithTag:FIT_MAXLIKE] setEnabled:NO ];
	[emptyBinButton setEnabled:NO];
    } else {
	[ [fitTypeMatrix findCellWithTag:FIT_INTEGRAL] setEnabled:YES ];
 //	[ [fitTypeMatrix findCellWithTag:FIT_INTFIXEDNORM] setEnabled:YES ];
	[ [fitTypeMatrix findCellWithTag:FIT_AVERAGE] setEnabled:NO ];
 //	[ [fitTypeMatrix findCellWithTag:FIT_MAXLIKE] setEnabled:YES ];
	[emptyBinButton setEnabled:YES];
    }

    funcList = [plot functionList];
    itype = [fitTypeMatrix selectedRow];
    if ( funcList && [funcList count] ) {
        func = [funcList objectAt:0];

	itype = [func ebType];
	[emptyBinMatrix selectCellAt:itype :0];
    	[emptyBinButton setTitle:emptyBinTitle[itype]];

	itype = [func fitType];
	[fitTypeMatrix selectCellWithTag:itype];
    	[fitTypeButton setTitle:doFitTitle[itype]];
    }
    if (! [[fitTypeMatrix selectedCell] isEnabled]) {
        itype = FIT_ONESAMPLE;
	[fitTypeMatrix selectCellWithTag:itype];
   	[fitTypeButton setTitle:doFitTitle[itype]];
	[self fitTypeChanged:self];
    }
    if ( itype == FIT_MAXLIKE ) [emptyBinButton setEnabled:NO];

    return self;
}
/* Delegate Methods for NXBrowsers */

- (int) browser: sender fillMatrix: matrix inColumn: (int) column
{
    unsigned int	count;
    
    switch (column) {
        case 0:
	    count = [self fillColumnZeroMatrix:matrix];
	    break;
	case 1:
	    count = [self fillColumnOneMatrix:matrix];
	    break;
	default:
	    count = 0;
    }
    return count;
}

- (int) fillColumnZeroMatrix:matrix
{
    List		*funcList;
    NXBrowserCell       *aCell;
    unsigned int        i, count;

    funcList = [self functionList];
    count = [funcList count];
    for ( i = 0; i < count; i++ ) {
	[matrix insertRowAt:i];
	aCell = [matrix cellAt:i :0];
	[aCell setStringValue:[[funcList objectAt:i] title]];
	[aCell setLeaf:NO];
	[aCell setLoaded:YES];
    }
    return count;
}
- (int) fillColumnOneMatrix:matrix
{
    List		*funcList;
    Matrix		*funcMatrix;
    NXBrowserCell	*aCell;
    PFunction		*func;
    unsigned int	count;
    int			i;
    
    count = 0;
    funcMatrix = [funcBrowser matrixInColumn:0];
    i = [funcMatrix selectedRow];
    if ( i < 0 ) {
        i = 0;
    }
    
    funcList = [self functionList];
    func = [funcList objectAt:i];
    if ( !func ) return count;
    
    count = [func numberArgs];
    for ( i = 0; i < count; i++ ) {
        [matrix insertRowAt:i];
	aCell = [matrix cellAt:i :0];
	[aCell setStringValue:[func argNameAt:i]];
	[[aCell setLeaf:YES] setLoaded:YES];
    }
    return count;
}
@end

/* Private Methods */

@implementation InspectPFunc(Private)

- (NXBundle *) compile:(const char *)directory :(const char *)file
{
    NXBundle	*bundle;
    struct stat	statbuf;
    char	basename[MAXNAMLEN+1];
    char	ofile[MAXPATHLEN+1];
    char	sys_cmd[MAXPATHLEN+1];
    char	inclpath[MAXPATHLEN+1];
    char	*ptr;
    BOOL	ok;
    
    strcpy( basename, file );
    ptr = strrchr( basename, '.' );
    *ptr = '\0';
    
    sprintf( ofile, "/tmp/%s", basename );
    strcat( ofile, ".bundle");
    if ( stat(ofile, &statbuf) ) {
	sprintf( sys_cmd, "mkdir %s", ofile );
	if ( system( sys_cmd ) ) {
	    return nil;
	}
    }
    strcat( ofile, "/");
    strcat( ofile, basename );
    chdir(directory);
    ok = [[NXBundle mainBundle] getPath:inclpath
            forResource:"PFunction"  ofType:"h"];
    if ( !ok ) {
        NXRunAlertPanel( "Error",
			 "Can not find needed include files in HippoDraw.app",
			 "OK", NULL, NULL );
	return nil;
    }
    ptr = strrchr(inclpath, '/');
    *ptr = '\0';
#ifdef GPPCOMPILE
    sprintf(sys_cmd, 
         "cc -ObjC++ -g -O -Wall -I%s -I%s -c %s -o %s.o",
	 inclpath, GPPINCLUDEDIR, file, ofile );
#else
    sprintf(sys_cmd, 
         "cc -g -O -Wall -I%s -c %s -o %s.o", inclpath, file, ofile );
#endif
    if ( system( sys_cmd ) ) {
        return nil;
    }
#ifdef GPPCOMPILE
    sprintf( sys_cmd, "ld -r -o %s %s.o %s", ofile, ofile, GPPLIBRARY );
#else
    sprintf( sys_cmd, "ld -r -o %s %s.o", ofile, ofile);
#endif
    if ( system( sys_cmd ) ) {
        return nil;
    }
    sprintf( sys_cmd, "rm -f %s.o", ofile);
    system( sys_cmd );
    ptr = strrchr( ofile, '/' );
    *ptr = '\0';
    bundle = [[NXBundle alloc] initForDirectory:ofile];
    return bundle;
}

- enableButtons:(BOOL) flag
{
    [parmForm    setEnabled:flag];
    [parmSlider  setEnabled:flag];
    [parmSliderU setEnabled:flag];
    [parmSliderD setEnabled:flag];
    [fixMatrix   setEnabled:flag];
    [doFitButton setEnabled:flag];
    return self;
}    

- getFitData
{
     id funcList = [selectedPlot functionList];
     
     if (!funcList) return self;
     
     if (fitNtuple == NULL) fitNtuple = h_new( 4 );

     fcnParms.disp = [selectedPlot histDisplay];
     fcnParms.fitType = (FCNType) [[funcList objectAt:0] fitType];
     
     h_fillWithFitData(fcnParms.disp, fitNtuple, 
                        (fcnParms.fitType == FIT_MAXLIKE) );
     fcnParms.x = h_getNtColumn( fitNtuple, 0 );
     fcnParms.xerr = h_getNtColumn( fitNtuple, 1 );
     fcnParms.y = h_getNtColumn( fitNtuple, 2 );
     fcnParms.yerr = h_getNtColumn( fitNtuple, 3 );
     fcnParms.npts = h_getNtNdata( fitNtuple );
     fcnParms.ebType = [[funcList objectAt:0] ebType];

     return self;
}

- loadFunction:(const char *)directory :(const char *)file
{
    NXBundle		*bundle;
    PFunction		*func;
    
    bundle = [self compile:directory :file];
    if ( bundle ) {
	func = [[[bundle principalClass] allocFromZone:[self zone]] init];
	[availFuncList addObject:func];
	[dynamFuncList addObject:func];
	[func readFile:directory :file];
    }
    return self;
}

- loadMinuit
{
     List *funcList;		/* current function List */
     int nFunc;
     Storage *parmList;
     int nParms;
     int type;
     
     int i, j;
     int iparm = 1;		/* the Minuit index of a parameter */
     fitParm *parm;
     
     if ( ![selectedPlot hasFunction] ) return self;

     [fitter clearParms];
     
     funcList = [selectedPlot functionList];
     nFunc = [funcList count];
     type = [parmRadio selectedRow];
     
     for ( i = 0; i < nFunc; i++) {
	  parmList = [[funcList objectAt:i] parmListOfType:type];
	  nParms = [parmList count];

	  for ( j = 0; j < nParms; j++ ) {
	       parm = (fitParm *)[parmList elementAt:j];
	       [fitter setParms:parm at:iparm];
	       iparm++;
	  }
     }

     /*
      * For fixed normalization fits, add Lagrange multiplier.
      */
     if ([[funcList objectAt:0] fitType] == FIT_INTFIXEDNORM) {
         [fitter setLagrangeAt:iparm];
     }
     
     return self;
}

- updateChiSq: (float) chi2
{
    [chiSqForm setDoubleValue:chi2 at:CHISQ_POS];
    // don't update DoF form. it won't have changed
    return self;
}

- updateDoF
{
    List		*funcList = [selectedPlot functionList];
    Storage		*parmList;
    PFunction		*func;
    fitParm		*parm;
    parm_t		type = [parmRadio selectedRow];
    int        i, j, num_parms;
    int			dof;
    
    if (fitNtuple == NULL) [self getFitData];
    dof = h_getNtNdata( fitNtuple );

    if (!funcList) 
    {
    	[chiSqForm setEnabled:NO];
        return self;
    } else {
        [chiSqForm setEnabled:YES];
    }

    for ( i = [funcList count]-1; i>=0 ; i-- ) {
        func = [funcList objectAt:i];
	dof -= [func numberArgs];
	parmList = [func parmListOfType:type];
	num_parms = [parmList count];
	for ( j = 0; j < num_parms; j++ ) {
	    parm = [parmList elementAt:j];
	    if (parm->fixedValue) dof++;
	}
    }
    
    [chiSqForm setIntValue:dof at:DOF_POS];
    return self;
}

- doSingleFit:sender
{
    List		*funcList;	/* current function List */
    parm_t		type;
    int			i;

    if ( !selectedPlot ) return self;
    
    funcList = [selectedPlot functionList];
    if ( !funcList ) return self;

    if ( !fitter ) {
    	[hDraw loadMinuit:self];
    }
    [sender getFrame:&stopButtonFrame];
    old_fcn_value = FLT_MAX;
    type = [parmRadio selectedRow];
    [funcList makeObjectsPerform:@selector(copyToInitial:) with:(id)&type];
    [self getFitData];
    
    /* 
     * check if there are bins with error = 0 and flag if desired.
     */
    if ( (fcnParms.ebType == EB_NOFIT) &&
         (fcnParms.fitType == FIT_ONESAMPLE 
	  || fcnParms.fitType == FIT_INTEGRAL
	  || fcnParms.fitType == FIT_INTFIXEDNORM) ) {
    	for ( i = 0; i < fcnParms.npts; i++ ) {
	    if ( fcnParms.y[i] == 0.0 ) {
	        NXRunAlertPanel( "Alert", 
		        "Histogram has bins that are empty.  "
			"No fit will been done. "
		        "Check empty bins options in Fit Options Inspector.",
		        NULL, NULL, NULL );
		return self;
	    }
	}
    }
    [self loadMinuit];
    [funcList makeObjectsPerform:@selector(copyToFitted:) with:(id)&type];
    [self setParmType:FIT_VALUES];
    
    [fitter doFitWith:&fcnParms];

    // Fit is done. Update the displays

    NXBeep();
    [self fetchMinuit];
    [self updateParmForm];
    [self updateChiSq];
    [self updatePlot];
    [selectedPlot setFitted:YES];
    [[[graphicView window] delegate] dirty:self];
    return self;
}

@end
