/**  File gg_nonlwin.c
 *  Transcripted from Motif to GTK+ by P. Vincent to
 *   Replace nonlwin.c
 *   
 *    Non linear curve fitting
 *    with extensions from Nicola Ferralis <feranick@hotmail.com>
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <math.h>    /* added djconnel Feb 2010 */

#include "globals.h"
#include "graphs.h"
#include "utils.h"
#include "files.h"
#include "parser.h"
#include "noxprotos.h"

#include "gw_pannel.h"
#include "gw_choice.h"
#include "gw_list.h"

#include "gg_gutil.h"
#include "gg_gtkinc.h"
#include "gg_protos.h"
#include "ge_protos.h"
#include "gg_events.h"

typedef enum {
  NL_RESET
  ,NL_GAUSS_1
  ,NL_GAUSS_2
  ,NL_GAUSS_3
  ,NL_GAUSS_1A
  ,NL_LORENTZ_1
  ,NL_LORENTZ_2
  ,NL_LORENTZ_3
  ,NL_PSVOIGHT_1
  ,NL_PSVOIGHT_2
  ,NL_DS
  ,NL_ASYM2SIG
  ,NL_LOGNORMAL
  ,NL_GCAS
  ,NL_ECS
  ,NL_INVPOLY
  ,NL_SINE
  ,NL_SINE_SQ
  ,NL_SINE_DAMP
  ,NL_EXP_DEC_1
  ,NL_EXP_DEC_2
  ,NL_EXP_GROW_1
  ,NL_EXP_GROW_2
  ,NL_HYPERBOLE
  ,NL_BRADLEY
  ,NL_LOG3
  ,NL_WEIBULLPD
  ,NL_WEIBULLCD
  ,NL_LAST
} NL_library;

static gint popup_width = 350 ,popup_height = 600; // ,list_height = 250;
extern GdkColor  bleuciel ,wheat;

/* Callbacks */
static void gg_nonl_AAC_CB	(GtkWidget *w ,gint reponse);
static void gg_openfit_CB	(GtkWidget *w ,gpointer p);
static void gg_savefit_CB	(GtkWidget *w ,gpointer p);
static void gg_nonl_reset_CB	(GtkWidget *w ,gpointer p);
static void gg_nonl_load_CB	(GtkWidget *w ,gpointer p);
static void gg_nonl_update_CB	(GtkWidget *w ,gpointer p);
static void gg_constr_CB        (GtkWidget *w ,gpointer p);
static void gg_nonl_toggle_CB   (GtkWidget *w ,gpointer p);

static int gg_load_nonl_fit (int gno1 ,int set1 ,int force);

/* nonlprefs.load possible values */
#define LOAD_VALUES         0
#define LOAD_RESIDUALS      1
#define LOAD_FUNCTION       2

#define  WEIGHT_NONE    0
#define  WEIGHT_Y       1
#define  WEIGHT_Y2      2
#define  WEIGHT_DY      3
#define  WEIGHT_CUSTOM  4

/* prefs for non-linear fit */
typedef struct {
    int autoload;       /* do autoload */
    int load;           /* load to... */
    int npoints;        /* # of points to evaluate function at */
    double start;       /* start... */
    double stop;        /* stop ... */
    int logpts;         /* whether to progress logarithmically (djconnel Feb 2010)*/
} gg_nonlprefs;

static char buf[256];

static gg_nonlprefs nonl_prefs = {TRUE ,LOAD_VALUES ,10 ,0.0 ,1.0 ,FALSE};


static GtkWidget *nonl_frame = NULL;
static GtkWidget *nonl_autol_item;

static gg_SrcDestStructure *nonl_set_item;
static GtkWidget *nonl_tab          ,*nonl_main;
static GtkWidget *nonl_formula_item ,*nonl_nparm_item;
static GtkWidget *nonl_tol_item     ,*nonl_nsteps_item;
static GtkWidget *nonl_logpts_item;   /* djconnel Feb 2010 */
static GtkWidget *nonl_nprint_item;
static GtkWidget *nonl_parm_item[MAXPARM];
static GtkWidget *nonl_value_item[MAXPARM];
static GtkWidget *nonl_constr_item[MAXPARM];
static GtkWidget *nonl_lowb_item[MAXPARM];
static GtkWidget *nonl_uppb_item[MAXPARM];
static GtkWidget *nonl_advanced;
static gg_RestrictionStructure  *restr_item;
static GtkWidget *nonl_weigh_item;
static GtkWidget *nonl_wfunc_item;
static GtkWidget *nonl_load_item;
static GtkWidget *nonl_fload_rc;
static GtkWidget *nonl_start_item;
static GtkWidget *nonl_stop_item;
static GtkWidget *nonl_npts_item;

/*Hacked  from  nonlinear_extended by Nicola Ferralis */
static void nonl_lib_CB (GtkWidget *w ,gpointer p);

static void gg_do_nparm_CB (GtkWidget *widget ,gpointer p);
static void gg_nonl_wf_CB  (GtkWidget *widget ,gpointer p);

static int dest_gno;


/* ARGSUSED */
/**
 *  Replace  create_nonl_frame       nonlwin.c   115
 */
void gg_create_nonl_popup (GtkWidget *w ,gpointer p)
{
  GtkWidget *vb ,*menubar ,*menupane ,*rc1 ,*rc2 ,*sw ,*lab ,*sp;
  int i;

  if (nonl_frame == NULL) {
    nonl_frame = gg_CreateAACDialog ("Non-linear curve fitting" ,gg_nonl_AAC_CB ,0 ,0 ,&vb);
 
    /* ------------ Menubar -------------- */
    menubar = gg_CreateMenuBar (vb);
    menupane = gg_CreateMenu (menubar,  "File", 'F', FALSE);
    gg_CreateMenuButton      (menupane, "Open...", 'O', NULL ,gg_openfit_CB ,-1);
    gg_CreateMenuButton      (menupane, "Save...", 'S' ,NULL ,gg_savefit_CB ,-1);
    gg_CreateMenuSeparator   (menupane);
    gg_CreateMenuCloseButton (menupane ,nonl_frame);

    menupane = gg_CreateMenu (menubar,  "Edit", 'E', FALSE);
    gg_CreateMenuButton      (menupane, "Reset fit parameters" , 'R' ,NULL , gg_nonl_reset_CB ,-1);
    gg_CreateMenuSeparator   (menupane);
    gg_CreateMenuButton      (menupane, "Load current fit"     , 'L' ,NULL , gg_nonl_load_CB  ,-1);

    menupane = gg_CreateMenu (menubar, "View", 'V', FALSE);
    nonl_autol_item = gg_CreateMenuToggle (menupane, "Autoload" ,'A' ,NULL ,NULL ,TRUE);
    gg_CreateMenuSeparator (menupane);
    gg_CreateMenuButton    (menupane , "Update" ,'U'  ,NULL ,gg_nonl_update_CB  ,-1);
    
    menupane = gg_CreateMenu (menubar ,"Library", 'L', FALSE);

    sp = gg_CreateMenu (menupane ,"Gaussian Functions", 'G', FALSE);
    gg_CreateMenuButton    (sp ,"Single" 		      ,'S'  ,NULL ,nonl_lib_CB ,NL_GAUSS_1);
    gg_CreateMenuButton    (sp ,"Double" 		      ,'D'  ,NULL ,nonl_lib_CB ,NL_GAUSS_2);
    gg_CreateMenuButton    (sp ,"Triple" 		      ,'T'  ,NULL ,nonl_lib_CB ,NL_GAUSS_3);
    gg_CreateMenuSeparator (sp);
    gg_CreateMenuButton    (sp ,"Single (chromatography)" ,'c'  ,NULL ,nonl_lib_CB ,NL_GAUSS_1A);

    sp = gg_CreateMenu (menupane ,"Lorentzian Functions", 'L', FALSE);
    gg_CreateMenuButton    (sp ,"Single" 		      ,'S'  ,NULL ,nonl_lib_CB ,NL_LORENTZ_1);
    gg_CreateMenuButton    (sp ,"Double" 		      ,'D'  ,NULL ,nonl_lib_CB ,NL_LORENTZ_2);
    gg_CreateMenuButton    (sp ,"Triple" 		      ,'T'  ,NULL ,nonl_lib_CB ,NL_LORENTZ_3);

    sp = gg_CreateMenu (menupane ,"Peak Functions", 'P', FALSE);
    gg_CreateMenuButton    (sp ,"Pseudo Voigt 1" 	      ,'V'  ,NULL ,nonl_lib_CB ,NL_PSVOIGHT_1);
    gg_CreateMenuButton    (sp ,"Pseudo Voigt 2" 	      ,'o'  ,NULL ,nonl_lib_CB ,NL_PSVOIGHT_2);
    gg_CreateMenuButton    (sp ,"Doniach-Sunjic" 	      ,'D'  ,NULL ,nonl_lib_CB ,NL_DS);
    gg_CreateMenuButton    (sp ,"Asymmetric Double Sigmoidal" ,'S'  ,NULL ,nonl_lib_CB ,NL_ASYM2SIG);
    gg_CreateMenuButton    (sp ,"LogNormal"                   ,'L'  ,NULL ,nonl_lib_CB ,NL_LOGNORMAL);
    gg_CreateMenuButton    (sp ,"Gram-Charlier A-Series"      ,'C'  ,NULL ,nonl_lib_CB ,NL_GCAS);
    gg_CreateMenuButton    (sp ,"Edgeworth-Cramer Series"     ,'E'  ,NULL ,nonl_lib_CB ,NL_ECS);
    gg_CreateMenuButton    (sp ,"Inverse Polynomial"          ,'I'  ,NULL ,nonl_lib_CB ,NL_INVPOLY);

    sp = gg_CreateMenu (menupane ,"Periodic Peak Functions", 'e', FALSE);
    gg_CreateMenuButton    (sp ,"Sine" 	              	      ,'S'  ,NULL ,nonl_lib_CB ,NL_SINE);
    gg_CreateMenuButton    (sp ,"Sine Square" 	      	      ,'q'  ,NULL ,nonl_lib_CB ,NL_SINE_SQ);
    gg_CreateMenuButton    (sp ,"Sine Damp" 	      	      ,'D'  ,NULL ,nonl_lib_CB ,NL_SINE_DAMP);

    sp = gg_CreateMenu (menupane ,"Baseline Functions", 'e', FALSE);
    gg_CreateMenuButton    (sp ,"Exponential Decay 1" 	      ,' '  ,NULL ,nonl_lib_CB ,NL_EXP_DEC_1);
    gg_CreateMenuButton    (sp ,"Exponential Decay 2" 	      ,' '  ,NULL ,nonl_lib_CB ,NL_EXP_DEC_2);
    gg_CreateMenuButton    (sp ,"Exponential Growth 1" 	      ,' '  ,NULL ,nonl_lib_CB ,NL_EXP_GROW_1);
    gg_CreateMenuButton    (sp ,"Exponential Growth 2" 	      ,' '  ,NULL ,nonl_lib_CB ,NL_EXP_GROW_2);
    gg_CreateMenuButton    (sp ,"Hyperbolic Function" 	      ,' '  ,NULL ,nonl_lib_CB ,NL_HYPERBOLE);
    gg_CreateMenuSeparator (sp);
    gg_CreateMenuButton    (sp ,"Bradley" 	              ,' '  ,NULL ,nonl_lib_CB ,NL_BRADLEY);
    gg_CreateMenuButton    (sp ,"Logarithm 3" 	              ,' '  ,NULL ,nonl_lib_CB ,NL_LOG3);
    gg_CreateMenuButton    (sp ,"Weibull Probability Density" ,' '  ,NULL ,nonl_lib_CB ,NL_WEIBULLPD);
    gg_CreateMenuButton    (sp ,"Weibull Cumulative"          ,' '  ,NULL ,nonl_lib_CB ,NL_WEIBULLCD);
    gg_CreateMenuSeparator (sp);



    menupane = gg_CreateMenu (menubar, "Help", 'H', TRUE);
    // A COMPLETER

    /* Create sets_lists */
    nonl_set_item = gg_CreateSrcDestSelector (vb ,GTK_SELECTION_BROWSE);

    /* Notebook */
    nonl_tab = gtk_notebook_new ();
    gtk_box_pack_start_defaults (GTK_BOX (vb) ,nonl_tab);

    /* ------------ Main tab -------------- */

    nonl_main = gg_CreateTabPage (nonl_tab ,"Main");

    nonl_formula_item = gg_entry_new (nonl_main ,"Formula:" ,70);
    rc1 = gg_CreateHContainer (nonl_main);
    nonl_nparm_item   = gg_combo_new (rc1 ,"Parameters:" ,12
					      ,"0" ,"1" ,"2" ,"3" ,"4" ,"5"
					      ,"6" ,"7" ,"8" ,"9" ,"10");
    nonl_tol_item     = gg_entry_new  (rc1 ,"Tolerance:" ,8);
    nonl_nsteps_item  = gg_spin_new (rc1 ,"Iterations:" ,0.0 ,500.0 ,5.0);
    nonl_nprint_item  = gg_check_new (rc1 ,"Trace on");
    gg_set_int  (nonl_nprint_item ,FALSE);
    gg_set_dble (nonl_nsteps_item ,5.0);
    gg_set_int  (nonl_nparm_item  ,0);

    sw = gtk_scrolled_window_new (NULL,NULL);
    gtk_scrolled_window_set_policy  (GTK_SCROLLED_WINDOW(sw)
				     ,GTK_POLICY_AUTOMATIC
				     ,GTK_POLICY_AUTOMATIC);
    gtk_widget_set_size_request (GTK_WIDGET (sw) ,popup_width ,180);

    rc2 = gtk_vbox_new (FALSE ,2);
    for (i = 0; i < MAXPARM; i++) {
      nonl_parm_item[i] = gg_CreateHContainer(rc2);
      sprintf(buf, "A%1d: ", i);
      nonl_value_item[i]  = gg_entry_new   (nonl_parm_item[i] ,buf ,10);
      nonl_constr_item[i] = gg_check_new (nonl_parm_item[i], "Bounds:");
      nonl_lowb_item[i]   = gg_entry_new (nonl_parm_item[i] ,""  ,6);
      sprintf(buf, "< A%1d < ", i);
      lab                 = gg_label_new (nonl_parm_item[i], buf);
      nonl_uppb_item[i]   = gg_entry_new (nonl_parm_item[i] ,""  ,6);
      g_signal_connect (nonl_constr_item[i] ,"toggled"
			,G_CALLBACK (gg_constr_CB) ,GINT_TO_POINTER (i));
    }
    gtk_widget_show (rc2);
    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw) ,rc2);
    gtk_widget_show (sw); 
    gtk_box_pack_start (GTK_BOX (nonl_main) ,sw ,FALSE ,FALSE , 1);

    /* ------------ Advanced tab --------------*/
    
    nonl_advanced = gg_CreateTabPage (nonl_tab ,"Advanced");

    restr_item = gg_CreateRestrictionChoice (nonl_advanced ,"Source data filtering");

    rc1 = gg_frame (nonl_advanced ,"Weighting" ,&wheat ,1 ,0);
    nonl_weigh_item = gg_combo_new (rc1 ,"Weights" ,6
					    ,"None"       /* WEIGHT_NONE 	 */
					    ,"1/Y"        /* WEIGHT_Y    	 */
					    ,"1/Y^2"      /* WEIGHT_Y2   	 */
					    ,"1/dY^2"     /* WEIGHT_DY   	 */
					    ,"Custom"     /* WEIGHT_CUSTOM */
					    );
    nonl_wfunc_item = gg_entry_new   (rc1 ,"Function:" ,30);

    rc1 = gg_frame (nonl_advanced ,"Load options" ,&wheat ,0 ,1);
    nonl_load_item  = gg_combo_new (rc1 ,"Load" ,4
					    ,"Fitted values"  /* LOAD_VALUES    */
					    ,"Residuals"      /* LOAD_RESIDUALS */
					    ,"Function"       /* LOAD_FUNCTION  */
					    );
    nonl_fload_rc    = gg_CreateHContainer (rc1);
    nonl_start_item  = gg_entry_new  (nonl_fload_rc ,"Start load at:" ,6);
    nonl_stop_item   = gg_entry_new  (nonl_fload_rc ,"Stop load at:"  ,6);
    nonl_npts_item   = gg_entry_new  (nonl_fload_rc ,"# of points:"   ,4);
    nonl_logpts_item = gg_check_new  (nonl_fload_rc, "logarithmic");

    g_signal_connect (nonl_load_item  ,"changed" ,G_CALLBACK (gg_nonl_toggle_CB) ,NULL);
    g_signal_connect (nonl_nparm_item ,"changed" ,G_CALLBACK (gg_do_nparm_CB) ,NULL);
    g_signal_connect (nonl_weigh_item ,"changed" ,G_CALLBACK (gg_nonl_wf_CB) ,NULL);



  reset_nonl ();
  nonl_opts.parnum = 2;
  }
  gtk_widget_show_all (nonl_frame);
  gg_nonl_update ();

}

/**
 *  Replace  do_nparm_toggle	    nonlwin.c   261
 */
static void gg_do_nparm_CB (GtkWidget *widget ,gpointer p)
{
  int i  ,n;

  n = gg_get_int (widget);
  for (i = 0; i < MAXPARM; i++) {
    if (i < n) {
      gtk_widget_show_all (nonl_parm_item[i]);
    } else {
      gtk_widget_hide_all (nonl_parm_item[i]);
    }
  }
}

/**
 *  Replace  reset_nonl_frame_cb    nonlwin.c   273
 */
static void  gg_nonl_reset_CB	(GtkWidget *w ,gpointer p)
{
  reset_nonl ();
  gg_nonl_update ();
}



/**
 * Replace  update_nonl_frame_cb   nonlwin.c   279
 */
static void gg_nonl_update_CB (GtkWidget *w ,gpointer p)
{
  gg_nonl_update (); 
}


/**
 *  Replace  update_nonl_frame	    nonlwin.c   284
 */
void gg_nonl_update (void)
{
  int i;
  
  if (nonl_frame) {
    gg_setstr   (nonl_formula_item      ,nonl_opts.formula);
    gg_setstr_d (nonl_tol_item    ,"%g" ,nonl_opts.tolerance ,buf);
    gg_set_int  (nonl_nparm_item        ,nonl_opts.parnum);
    for (i = 0; i < MAXPARM; i++) {
      gg_setstr_d (nonl_value_item[i] ,"%g" ,nonl_parms[i].value ,buf);
      gg_set_int  (nonl_constr_item[i]      ,nonl_parms[i].constr    );
      gg_setstr_d (nonl_lowb_item[i]  ,"%g" ,nonl_parms[i].min   ,buf);
      gg_setstr_d (nonl_uppb_item[i]  ,"%g" ,nonl_parms[i].max   ,buf);
      gtk_widget_set_sensitive (nonl_lowb_item[i] ,nonl_parms[i].constr);
      gtk_widget_set_sensitive (nonl_uppb_item[i] ,nonl_parms[i].constr);
      if (i < nonl_opts.parnum) {
	gtk_widget_show_all (nonl_parm_item[i]);
      } else {
	gtk_widget_hide_all (nonl_parm_item[i]);
      }
    }
    gg_set_int (nonl_autol_item ,nonl_prefs.autoload);
    gg_set_int (nonl_load_item  ,nonl_prefs.load);
    if (nonl_prefs.load == LOAD_FUNCTION) {
      gtk_widget_set_sensitive (nonl_fload_rc ,TRUE);
    } else {
      gtk_widget_set_sensitive (nonl_fload_rc ,FALSE);
    }
    if (gg_get_int (nonl_weigh_item) == WEIGHT_CUSTOM) {
      gtk_widget_set_sensitive (nonl_wfunc_item ,TRUE);
    } else {
      gtk_widget_set_sensitive (nonl_wfunc_item ,FALSE);
    }
    gg_setstr_d (nonl_start_item  ,"%g" ,nonl_prefs.start   ,buf);
    gg_setstr_d (nonl_stop_item   ,"%g" ,nonl_prefs.stop    ,buf);
    sprintf(buf ,"%d" ,nonl_prefs.npoints);
    gg_setstr(nonl_npts_item ,buf);
    gg_set_int (nonl_logpts_item ,nonl_prefs.logpts); /* added djconnel Feb 2010 */
  }
}

/**
 *  Replace  nonl_wf_cb		    nonlwin.c   348
 */
static void gg_nonl_wf_CB (GtkWidget *w ,gpointer p)
{
  int value = gg_get_int (nonl_weigh_item);

  if (value == WEIGHT_CUSTOM) {
    gtk_widget_set_sensitive (nonl_wfunc_item ,TRUE);
  } else {
    gtk_widget_set_sensitive (nonl_wfunc_item ,FALSE);
  }
}

 /**
  *  Replace  do_nonl_toggle	    nonlwin.c   359
  */
static void gg_nonl_toggle_CB  (GtkWidget *w ,gpointer p)
{
  int value = gg_get_int (nonl_load_item);

  if (value == LOAD_FUNCTION) {
    gtk_widget_set_sensitive (nonl_fload_rc ,TRUE);
  } else {
    gtk_widget_set_sensitive (nonl_fload_rc ,FALSE);
  }
}

/**
 *  Replace  do_constr_toggle	    nonlwin.c   370
 */
static void gg_constr_CB (GtkWidget *w ,gpointer p)
{
  int value = GPOINTER_TO_INT (p);
  int onoff = gg_get_int (w);

  if (onoff) {
    gtk_widget_set_sensitive (nonl_lowb_item[value], TRUE);
    gtk_widget_set_sensitive (nonl_uppb_item[value], TRUE);
    nonl_parms[value].constr = TRUE;
  } else {
    gtk_widget_set_sensitive (nonl_lowb_item[value], FALSE);
    gtk_widget_set_sensitive (nonl_uppb_item[value], FALSE);
    nonl_parms[value].constr = FALSE;
  }
}

/**
 *  Replace  do_nonl_proc	    nonlwin.c   385
 */
static void  gg_nonl_AAC_CB (GtkWidget *w ,gint reponse)
{
  int src_gno, src_setno ,nprint;
  int resno;
  int nsteps ,i ,nlen ,wlen;
  int weight_method;
  const char *fstr;
  double *ytmp, *warray;
  int restr_type, restr_negate;
  char *rarray;

  if (reponse == GTK_RESPONSE_ACCEPT || reponse == GTK_RESPONSE_APPLY   ) {
    if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->src->graphs)  ,&src_gno)) {
      errmsg("Please select single source graph");    
      return;
    }
    if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->src->sets)  ,&src_setno)) {
      errmsg("Please select single source graph");    
      return;
    }
  if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->dest->graphs)  ,&dest_gno)) {
    errmsg ("No destination graph selected");
    return;
  }
    nonl_opts.formula   = copy_string (nonl_opts.formula ,gg_getstr (nonl_formula_item));
    nsteps              = gg_get_int (nonl_nsteps_item);
    nonl_opts.tolerance = atof (gg_getstr (nonl_tol_item));
    nonl_opts.parnum    = gg_get_int (nonl_nparm_item);
    for (i = 0; i < nonl_opts.parnum; i++) {
      strcpy (buf, gg_getstr (nonl_value_item[i]));
      if (sscanf(buf, "%lf", &nonl_parms[i].value) != 1) {
	errmsg ("Invalid input in parameter field");
	return;
      }
      nonl_parms[i].constr = gg_get_int (nonl_constr_item[i]);
      if (nonl_parms[i].constr) {
	strcpy (buf, gg_getstr (nonl_lowb_item[i]));
	if (sscanf (buf, "%lf", &nonl_parms[i].min) != 1) {
	  errmsg ("Invalid input in low-bound field");
	  return;
	}
	strcpy (buf, gg_getstr (nonl_uppb_item[i]));
	if (sscanf (buf, "%lf", &nonl_parms[i].max) != 1) {
	  errmsg( "Invalid input in upper-bound field");
	  return;
	}
	if ((nonl_parms[i].value < nonl_parms[i].min) || (nonl_parms[i].value > nonl_parms[i].max)) {
	  errmsg ("Initial values must be within bounds");
	  return;
	}
      }
    }
    if (nsteps) {
      /* apply weigh function */
      nlen = getsetlength (src_gno, src_setno);
      weight_method = gg_get_int (nonl_weigh_item);
      switch (weight_method) {
      case WEIGHT_Y:
      case WEIGHT_Y2:
	ytmp = getcol (src_gno, src_setno, DATA_Y);
	for (i = 0; i < nlen; i++) {
	  if (ytmp[i] == 0.0) {
	    errmsg ("Divide by zero while calculating weights");
	    return;
	  }
	}
	warray = xmalloc (nlen*SIZEOF_DOUBLE);
	if (warray == NULL) {
	  errmsg ("xmalloc failed in do_nonl_proc()");
	  return;
	}
	for (i = 0; i < nlen; i++) {
	  if (weight_method == WEIGHT_Y) {
	    warray[i] = 1/ytmp[i];
	  } else {
	    warray[i] = 1/(ytmp[i]*ytmp[i]);
	  }
	}
	break;
      case WEIGHT_DY:
	ytmp = getcol (src_gno, src_setno, DATA_Y1);
	if (ytmp == NULL) {
	  errmsg("The set doesn't have dY data column");
	  return;
	}
	for (i = 0; i < nlen; i++) {
	  if (ytmp[i] == 0.0) {
	    errmsg("Divide by zero while calculating weights");
	    return;
	  }
	}
	warray = xmalloc (nlen*SIZEOF_DOUBLE);
	if (warray == NULL) {
	  errmsg("xmalloc failed in do_nonl_proc()");
	}
	for (i = 0; i < nlen; i++) {
	  warray[i] = 1/(ytmp[i]*ytmp[i]);
	}
	break;
      case WEIGHT_CUSTOM:
	if (set_parser_setno (src_gno, src_setno) != RETURN_SUCCESS) {
	  errmsg ("Bad set");
	  return;
	}
	
	fstr = gg_getstr (nonl_wfunc_item);
	if (v_scanner (fstr, &wlen, &warray) != RETURN_SUCCESS) {
	  errmsg ("Error evaluating expression for weights");
	  return;
	}
	if (wlen != nlen) {
	  errmsg ("The array of weights has different length");
	  xfree (warray);
	  return;
	}
	break;
      default:
	warray = NULL;
	break;
      }
      /* apply restriction */
      restr_type   = gg_get_int (restr_item->r_sel) - 1;
      restr_negate = gg_get_int (restr_item->negate);
      resno = get_restriction_array (src_gno, src_setno ,restr_type ,restr_negate ,&rarray);
      if (resno != RETURN_SUCCESS) {
	errmsg ("Error in restriction evaluation");
	xfree (warray);
	return;
      }
      nprint = gg_get_int (nonl_nprint_item);
      /* The fit itself! */
      resno = do_nonlfit (src_gno, src_setno, warray, rarray, nsteps ,dest_gno ,nprint);
      xfree (warray);
      xfree (rarray);
      if (resno != RETURN_SUCCESS) {
	errmsg("Fatal error in do_nonlfit()");  
	return;  	
      }
      for (i = 0; i < nonl_opts.parnum; i++) {
	gg_setstr_d (nonl_value_item[i] ,"%g" ,nonl_parms[i].value ,buf);
      }
    }
    /* Select & activate a set to load results to */    
    gg_load_nonl_fit (src_gno, src_setno, FALSE);
   
  }

  if (reponse == GTK_RESPONSE_ACCEPT || reponse == GTK_RESPONSE_CLOSE) {
    gtk_widget_hide (w);
  }
}


/**
 * Replace   load_nonl_fit_cb	    nonlwin.c   543
 */
static void  gg_nonl_load_CB (GtkWidget *w ,gpointer p)
{
  int src_gno ,set1;

  if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->src->graphs)  ,&src_gno)) {
    errmsg ("No source graph selected");
    return;
  }
  if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->src->sets)  ,&set1)) {
    errmsg ("No source set selected");
    return;
  }
  gg_load_nonl_fit (src_gno ,set1 ,TRUE);
}


/**
 *  Replace  load_nonl_fit	    nonlwin.c   560
 */
static int gg_load_nonl_fit (int src_gno ,int set1 ,int force)
{
  int set2;
  int i, npts = 0;
  double delx, *xfit, *y, *yfit;

  if (!gg_GetSingleListChoice (GW_LIST (nonl_set_item->dest->sets)  ,&set2)) {
    /* no dest sel selected; allocate new one */
    set2 = nextset (dest_gno);
    if (set2 == -1) {
      return RETURN_FAILURE;
    } else {
      activateset (dest_gno ,set2);
    }
  }
  nonl_opts.formula   = copy_string (nonl_opts.formula ,gg_getstr (nonl_formula_item));
  nonl_prefs.autoload = gg_get_int (nonl_autol_item);
  nonl_prefs.logpts   = gg_get_int (nonl_logpts_item);   /* added djconnel Feb 2010 */
  nonl_prefs.load     = gg_get_int (nonl_load_item);
  if (nonl_prefs.load == LOAD_FUNCTION) {
    if (gg_evalexpr (nonl_start_item, &nonl_prefs.start) != RETURN_SUCCESS) {
      errmsg ("Invalid input in start field");
      return RETURN_FAILURE;
    }
    if (gg_evalexpr (nonl_stop_item, &nonl_prefs.stop) != RETURN_SUCCESS) {
      errmsg ("Invalid input in stop field");
      return RETURN_FAILURE;
    }
    if (gg_evalexpri (nonl_npts_item, &nonl_prefs.npoints) != RETURN_SUCCESS) {
      errmsg ("Invalid input in # of points field");
      return RETURN_FAILURE;
    }
    if (nonl_prefs.npoints <= 1) {
      errmsg ("Number of points must be > 1");
      return RETURN_FAILURE;
    }
    /* added djconnel Feb 2010 */
    if (nonl_prefs.logpts &&
	(((nonl_prefs.start <= 0.0) && (nonl_prefs.stop >= 0.0)) ||
	 ((nonl_prefs.start >= 0.0) && (nonl_prefs.stop <= 0.0)))
	) {
      errmsg("Logarithmic points require same sign for start and stop");
      return RETURN_FAILURE;
    }
  }
  if (force || nonl_prefs.autoload) {
    switch (nonl_prefs.load) {
    case LOAD_VALUES:
    case LOAD_RESIDUALS:
      npts = getsetlength (src_gno ,set1);
      setlength (dest_gno ,set2 ,npts);
      copycol2 (src_gno ,set1 ,dest_gno ,set2 ,DATA_X);
      break;
    case LOAD_FUNCTION:
      npts  = nonl_prefs.npoints;
      setlength (dest_gno ,set2 ,npts);
 
      delx = (nonl_prefs.stop - nonl_prefs.start)/(npts - 1);
      xfit = getx(dest_gno ,set2);
      for (i = 0; i < npts; i++) {
	/* logarithmic point progression added djconnel 10 Feb 2010 */
	if (nonl_prefs.logpts) {
	  double f = ((double) i) / (npts - 1);
	  xfit[i] =
	    exp(
		log(fabs(nonl_prefs.start)) * (1.0 - f) +
		log(fabs(nonl_prefs.stop)) * f
		) * ((nonl_prefs.start > 0) ? 1 : -1);
	}
	else
	  xfit[i] =
	    nonl_prefs.start + i * delx;
      }
      break;
    }
    do_compute (dest_gno ,set2 ,dest_gno ,set2 ,NULL ,nonl_opts.formula);
    if (nonl_prefs.load == LOAD_RESIDUALS) { /* load residuals */
      y    = gety (src_gno ,set1);
      yfit = gety (dest_gno ,set2);
      for (i = 0; i < npts; i++) {
	yfit[i] -= y[i];
      }
    }
    comment_and_legend_set (dest_gno ,set2 ,nonl_opts.formula);
    gg_update_all_sets_lists ();
    ge_update_explorer ();
    ge_expand_graph (dest_gno);

    // ??        SelectListChoice(nonl_set_item->dest->set_sel ,set2);
    gg_drawgraph();
  }
  return RETURN_SUCCESS;
}


/**
 *  Replace  create_openfit_popup    nonlwin.c   646
 *  and      do_savefit_proc         nonlwin.c   702
 */
static void  gg_openfit_CB	(GtkWidget *w ,gpointer p)
{
  static GtkWidget *fsb = NULL;
  char *filename;
  int res;

  if (fsb == NULL) {
    fsb = gg_create_file_popup ("Open fit parameter file" ,"*.fit"
				,popup_width ,popup_height
				,NULL
				,FALSE);
    while (gtk_dialog_run (GTK_DIALOG(fsb)) != GTK_RESPONSE_CANCEL) {
      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(fsb));
      res = getparms (filename);
      gg_nonl_update ();
      g_free (filename);
    }
    gtk_widget_destroy (GTK_WIDGET (fsb));
    fsb = NULL;
  }
}

/**
 *  Replace  create_savefit_popup    nonlwin.c   678
 *  and      do_savefit_proc         nonlwin.c   702
 */
static void  gg_savefit_CB (GtkWidget *w ,gpointer p)
{
  static GtkWidget *fsb ,*vb ,*title_item;
  char *filename;
  gint response;
  FILE *pp;
 
  gg_set_wait_cursor();

  /* extra_widget */
  vb =  gtk_vbox_new (FALSE, 2);
  title_item = gg_entry_new (vb ,"Title: " ,25);
  fsb = gg_create_file_popup ("Save fit parameter file" ,"*.fit"
			      ,popup_width ,popup_height
			      ,vb
			      ,TRUE);

  response = gtk_dialog_run (GTK_DIALOG (fsb) ); 
  switch (response) {
  case GTK_RESPONSE_CANCEL :
    break;
  case  GTK_RESPONSE_ACCEPT :
    filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(fsb));
    pp = grace_openw (filename);
    if (pp != NULL) {
      nonl_opts.title = copy_string (nonl_opts.title, gg_getstr (title_item));
      put_fitparms (pp, 0);
      grace_close (pp);
    }
  }
  gtk_widget_destroy (GTK_WIDGET (fsb));
  gg_unset_wait_cursor();
  // if (follow_me_on) nn_cmd_nonlfit_file (); // No differential print for nonlfi
}

/* ------------------------ Extended Library ------------------------ */

typedef struct _nonl_lib_entry nonl_lib_entry;

struct _nonl_lib_entry {
  NL_library entry;     /* to check synchronization */
  int parnum;         /* number of parameters used */
  int  action;        /* to take with the mouse (see CanvasActionFlag in ggevents.h) */
  char *title;        /* displayed */
  char *formula;      /* to perform the fit */
  char *desc;
};


/* Must be synchronized with NL_library enum */
/* entry , parnum ,event , title , formula , param description */
nonl_lib_entry nle[] = {
  {NL_RESET       ,0  ,DO_NOTHING ,"Reset"
  ,""
  ,""
  }
  ,{NL_GAUSS_1    ,4  ,PEAK_POS    ,"Gaussian function"
    ,"y = A0 + (A3*2*sqrt(ln(2)/pi)/A2)*exp(-4*ln(2)*((x-A1)/A2)^2)"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Full width at half maximum\nA3: Peak area\n\n"
  }
  ,{NL_GAUSS_2    ,7 ,PEAK_POS1    ,"Double Gaussian function"
    ,"y = A0 + (A3*2*sqrt(ln(2)/pi)/A2)*exp(-4*ln(2)*((x-A1)/A2)^2) + (A6*2*sqrt(ln(2)/pi)/A5)*exp(-4*ln(2)*((x-A4)/A5)^2)"
    ,"A0: Baseline offset\nA1, A4: Center of peaks 1, 2\nA2, A5: Full width at half maximum of peaks 1, 2\nA3, A6: Area of peaks 1, 2\n\n"
  }
  ,{NL_GAUSS_3   ,10 ,PEAK_POS1B   ,"Triple Gaussian function"
    ,"y = A0 + (A3*2*sqrt(ln(2)/pi)/A2)*exp(-4*ln(2)*((x-A1)/A2)^2) + (A6*2*sqrt(ln(2)/pi)/A5)*exp(-4*ln(2)*((x-A4)/A5)^2)+ (A9*2*sqrt(ln(2)/pi)/A8)*exp(-4*ln(2)*((x-A7)/A8)^2)"
    ,"A0: Baseline offset\nA1, A4: Center of peaks 1, 2\nA2, A5: Full width at half maximum of peaks 1, 2\nA3, A6: Area of peaks 1, 2\n\n"
  }
  ,{NL_GAUSS_1A  ,4  ,PEAK_POS    ,"Gaussian (chromatography) function"
    //    ,"y = A0 + (1/sqrt(2*pi))*(A3/A2)*exp(-(x-A1)^2/2*A2^2)"
    ,"y = A0 + (1/sqrt(2*pi))*(A3/A2)*exp(-(x-A1)^2/2/A2^2)"
    ,"A0: Baseline offset\nA1: Center of the peak (retention time)\nA2: Standard deviation of the peak\nA3: Peak area\n\n"
  }
  ,{NL_LORENTZ_1 ,4  ,PEAK_POS    ,"Lorentzian function"
    ,"y = A0 + (2*A2*A3/pi)/(4*(x-A1)^2 + A2^2)"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Full width at half maximum\nA3: Peak area\n\n"
  }
  ,{NL_LORENTZ_2 ,7 ,PEAK_POS1    ,"Double Lorentzian function"
    ,"y = A0 + (2*A2*A3/pi)/(4*(x-A1)^2 + A2^2) + (2*A5*A6/pi)/(4*(x-A4)^2 + A5^2)"
    ,"A0: Baseline offset\nA1, A4: Center of peaks 1, 2\nA2, A5: Full width at half maximum of peaks 1, 2\nA3, A6: Area of peaks 1, 2\n\n"
  }
  ,{NL_LORENTZ_3 ,10 ,PEAK_POS1B  ,"Triple Lorentzian function"
    ,"y = A0 + (2*A2*A3/pi)/(4*(x-A1)^2 + A2^2) + (2*A5*A6/pi)/(4*(x-A4)^2 + A5^2) + (2*A8*A9/pi)/(4*(x-A7)^2 + A8^2)"
    ,"A0: Baseline offset\nA1, A4, A7: Center of peaks 1, 2, 3\nA2, A5, A7: Full width at half maximum of peaks 1, 2, 3\nA3, A6, A9: Area of peaks 1, 2, 3\n\n"
  }
  ,{NL_PSVOIGHT_1 ,5 ,PEAK_POS   ,"Pseudo Voigt 1 function"
    ,"y = A0 + A3 * (A4*(2/pi)*A2/(4*(x-A1)^2+A2^2) + (1-A4)*exp(-4*ln(2)*(x-A1)^2/A2^2)*(sqrt(4*ln(2))/(A2*sqrt(pi))))"
    ,"Gaussian and Lorentzian have the same width\nA0: Baseline offset\nA1: Center of the peak\nA2: Full width at half maximum\nA3: Amplitude\nA4: Profile shape factor \n\n"
  }
  ,{NL_PSVOIGHT_2 ,6 ,PEAK_POS   ,"Pseudo Voigt 2 function"
    ,"y = A0 + A3 * (A5*(2/pi)*A2/(4*(x-A1)^2+A2^2) + (1-A5)*exp(-4*ln(2)*(x-A1)^2/A4^2)*(sqrt(4*ln(2))/(A2*sqrt(pi))))"
    ,"Gaussian and Lorentzian have different width\nA0: Baseline offset\nA1: Center of the peak\nA2: Full width at half maximum (Lorentzian)\nA3: Amplitude\nA4: Full width at half maximum (Gaussian) \nA5: Profile shape factor \n\n"
  }
  ,{NL_DS        ,5  ,PEAK_POS   ,"Doniach-Sunjic function"
    ,"y = A0 + A3*cos((pi*A4/2)+(1-A4)*atan((x-A1)/A2))/(A2^2+(x-A1)^2)^((1-A4)/2)"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Full width at half maximum\nA3: Peak area\nA4: Asymmetry parameter \n\n"
  }
  ,{NL_ASYM2SIG   ,6 ,PEAK_POS   ,"Asymmetric double sigmoidal function"
    ,"y = A0 + A3*(1/(1+exp(-(x-A1+A2/2)/A4)))*(1-(1/(1+exp(-(x-A1-A2/2)/A5))))"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Width 1\nA3: Amplitude\nA4: Width 2\nA5: Width 5 \n\n"
  }
  ,{NL_LOGNORMAL  ,4  ,PEAK_POS  ,"Log Normal Function"
    ,"y = A0 + A3*exp(-((ln(x)-ln(A1))^2)/(2*A2))"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Width\nA3: Amplitude\n\n"
  }
  ,{NL_GCAS       ,5  ,PEAK_POS  ,"Gram-Charlier A-Series"
    ,"y = A0 + A3/(A2*sqrt(2*pi))*exp(-0.5*((x-A1)/A2)^2)*(1+(A4/6)*(((x-A1)/A2)^3-3*(x-A1)/A2)+(A5/24)*(((x-A1)/A2)^4-6*((x-A1)/A2)^3+3))"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Standard deviation\nA3: Peak Area\nA4: Skew\nA5: Excess\n\n"
  }
  ,{NL_ECS        ,5  ,PEAK_POS  ,"Edgeworth-Cramer Series"
    ,"y = A0 + A3/(A2*sqrt(2*pi))*exp(-0.5*((x-A1)/A2)^2)*(1+(A4/6)*(((x-A1)/A2)^3-3*(x-A1)/A2)+(A5/24)*(((x-A1)/A2)^4-6*((x-A1)/A2)^3+3) + (A5^2/720)*(((x-A1)/A2)^6-15*((x-A1)/A2)^4+45*((x-A1)/A2)^2-15))"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Standard deviation\nA3: Peak Area\nA4: Skew\nA5: Excess\n\n"
  }
  ,{NL_INVPOLY    ,7 ,PEAK_POS   ,"Inverse Polynomial Function"
    ,"y = A0 + A3/(1+ A4*(2*(x-A1)/A2)^2 + A5*(2*(x-A1)/A2)^4 + A6*(2*(x-A1)/A2)^6)"
    ,"A0: Baseline offset\nA1: Center of the peak\nA2: Standard deviation\nA3: Peak Area\nA4, A5, A6: Parameters\n\n"
  }
  ,{NL_SINE       ,4 ,PEAK_POS   ,"Sine Function"
    ,"y = A0 + A3*sin(pi*(x-A1)/A2)"
    ,"A0: Baseline offset\nA1: Center\nA2: Width\nA3: Amplitude\n\n"
  }
  ,{NL_SINE_SQ    ,4 ,PEAK_POS   ,"Sine Square Function"
    ,"y = A0 + A3*sin(pi*(x-A1)/A2)^2"
    ,"A0: Baseline offset\nA1: Center\nA2: Width\nA3: Amplitude\n\n"
  }
  ,{NL_SINE_DAMP  ,5 ,PEAK_POS   ,"Sine Damp Function"
    ,"y = A0 + A3*exp(-x/A4)*sin(pi*(x-A1)/A2)"
    ,"A0: Baseline offset; A1: Center; A2: Width; A3: Amplitude; A4: Decay time. "
  }
  ,{NL_EXP_DEC_1  ,4 ,DO_NOTHING ,"Exponential Decay 1"
    ,"y = A0 + A3*exp(-(x-A1)/A2)"
    ,""
  }
  ,{NL_EXP_DEC_2  ,7 ,DO_NOTHING ,"Exponential Decay 2"
    ,"y = A0 + A3*exp(-(x-A1)/A2)+A6*exp(-(x-A4)/A5)"
    ,""
  }
  ,{NL_EXP_GROW_1 ,4 ,DO_NOTHING ,"Exponential Growth 1"
    ,"y = A0 + A3*exp((x-A1)/A2)"
    ,""
  }
  ,{NL_EXP_GROW_2 ,7 ,DO_NOTHING ,"Exponential Growth 2"
    ,"y = A0 + A3*exp((x-A1)/A2)+A6*exp((x-A4)/A5)"
    ,""
  }
  ,{NL_HYPERBOLE  ,3 ,DO_NOTHING ,"Hyperbolic"
    ,"y = A0 + (A1*x)/(A2+x)"
    ,""
  }
  ,{NL_BRADLEY    ,2 ,DO_NOTHING ,"Bradley"
    ,"y = A0 * ln(-A1*ln(x))"
    ,""
  }
  ,{NL_LOG3       ,3 ,DO_NOTHING ,"Logarithm 3 Par."
    ,"y = A0 - A1*ln(x+A2)"
    ,""
  }
  ,{NL_WEIBULLPD  ,2 ,DO_NOTHING ,"Weibull Probability Density"
    ,"y = (A0/A1) * ((x/A1)^(A0-1)) * exp(-(x/A1)^A0)"
    ,""
  }
  ,{NL_WEIBULLCD  ,2 ,DO_NOTHING ,"Weibull Cumulative Distribution"
    ,"y = 1 - exp(-(x/A1)^A0)"
    ,""
  }
};

static void nonl_lib_CB (GtkWidget *w ,gpointer p)
{
  NL_library n = GPOINTER_TO_INT (p);
  int i;
  if (nle[n].entry != n) {
    errmsg ("nonl_lib_CB internal ERROR: desync nle entry");
    return;
  }
  nonl_opts.title   = copy_string (nonl_opts.title   ,nle[n].title);
  nonl_opts.formula = copy_string (nonl_opts.formula ,nle[n].formula);
  nonl_opts.parnum  = nle[n].parnum;
  for (i = 0; i < nonl_opts.parnum; i++) nonl_parms[i].value = 1;
  /* Some functions use special parameters settings */
  switch (n) {
  case NL_PSVOIGHT_1:
  case NL_DS:
    nonl_parms[4].value = 0.5;
    break;
  case NL_PSVOIGHT_2:
    nonl_parms[5].value = 0.5;
    break;
  default:
    break;
  }
  if (strcmp ("" ,nle[n].desc)) {
    stufftext (nle[n].title);
    stufftext ("\n");
    stufftext (nle[n].desc);
  }
  gg_set_action  (nle[n].action);
  gg_nonl_update ();
}
