/*  gnutrition - a nutrition and diet analysis program.
 *  Copyright( C) 2000, 2001 Edgar Denny( e.denny@ic.ac.uk)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 * ( at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gnome.h>
#include <glade/glade.h>
#include <ctype.h>

#include "support.h"
#include "text_util.h"
#include "wrap_mysql.h"
#include "food.h"
#include "load_data.h"
#include "food_srch_dlg.h"
#include "food_srch_res.h"

typedef struct Constr_s {
	char *nutr_desc;
	char *weighting;
} Constr_t;

typedef struct Score_s {
	char *fd_no;
	float food_score;
} Score_t;

static enum { PLAN_VIEW, RECIPE_VIEW, FOOD_VIEW} active_view;

static GladeXML *xml = NULL;
static int selected_row_clist_max;
static int selected_row_clist_min;
static int n_rows_clist_max;
static int n_rows_clist_min;

static void load_xml( void);
static void initialize_dlg( void);

static gboolean check_no_nutr_match( GtkCList *, char *, int);
static GList *gnutr_nutrient_constraint_search( char *, char *, int, GList*);
static GList * add_to_score_list( GList *score_list, char  *fd_no, 
	float  food_score, int no_to_list);
static int compare_score( Score_t *, Score_t *);
static void show_none_found_warn( void);

/* the callbacks. */
static void on_search_button_released( GtkButton *, gpointer);
static void on_cancel_button_released( GtkButton *, gpointer);
static void on_nut_max_add_button_released( GtkButton *, gpointer);
static void on_nut_max_remove_button_released( GtkButton *, gpointer);
static void on_nut_min_add_button_released( GtkButton *, gpointer);
static void on_nut_min_remove_button_released( GtkButton *, gpointer);
static void on_nut_max_clist_select_row( GtkCList *, int, int, 
	GdkEvent *, gpointer);
static void on_nut_max_clist_unselect_row( GtkCList *, int, int, 
	GdkEvent *, gpointer);
static void on_nut_min_clist_select_row( GtkCList *, int, int, 
	GdkEvent *, gpointer);
static void on_nut_min_clist_unselect_row( GtkCList *, int, int, 
	GdkEvent *, gpointer);


static void
connect_signals()
{
	gtk_signal_connect( GTK_OBJECT( 
		glade_xml_get_widget( xml, "search_button")),
		"released", GTK_SIGNAL_FUNC( on_search_button_released), NULL);

	gtk_signal_connect( GTK_OBJECT( 
		glade_xml_get_widget( xml, "cancel_button")),
		"released", GTK_SIGNAL_FUNC( on_cancel_button_released), NULL);

	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_max_add_button")), "released", 
		GTK_SIGNAL_FUNC( on_nut_max_add_button_released), NULL);
	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_max_remove_button")), "released", 
		GTK_SIGNAL_FUNC( on_nut_max_remove_button_released), NULL);

	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_min_add_button")), "released", 
		GTK_SIGNAL_FUNC( on_nut_min_add_button_released), NULL);
	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_min_remove_button")), "released", 
		GTK_SIGNAL_FUNC( on_nut_min_remove_button_released), NULL);

	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_max_clist")), "select_row", 
		GTK_SIGNAL_FUNC( on_nut_max_clist_select_row), NULL);
	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_max_clist")), "unselect_row", 
		GTK_SIGNAL_FUNC( on_nut_max_clist_unselect_row), NULL);

	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_min_clist")), "select_row", 
		GTK_SIGNAL_FUNC( on_nut_min_clist_select_row), NULL);
	gtk_signal_connect( GTK_OBJECT( glade_xml_get_widget( 
		xml, "nut_min_clist")), "unselect_row", 
		GTK_SIGNAL_FUNC( on_nut_min_clist_unselect_row), NULL);
}

/* load the glade xml if not already loaded. */
static void
load_xml()
{
	static gboolean loaded_xml = FALSE;

	/* load the glade interface. */
	if ( !loaded_xml) {
		xml = glade_xml_new( 
			GNUTRITION_GLADEDIR "/food_search_dlg.glade", NULL);
		loaded_xml = TRUE;

		if ( xml) {
			connect_signals();
		} else {
			g_log( "Gnutrition", G_LOG_LEVEL_ERROR,
				"cannot load file: food_search_dlg.glade\n");
			return;
		}
	}
}

/* initialize all the widgets to their default values. */
static void
initialize_dlg()
{
	static GList *fg_desc_list = NULL; 
	static GList *nutr_desc_list = NULL; 

	/* load the glade interface. */
	if ( !xml) load_xml();

	/* Initialize all the widgets in the text search notebook page. */

	/* reset any text entries to empty. */
	gtk_entry_set_text( GTK_ENTRY( glade_xml_get_widget( 
		xml, "txt_fd_entry")), "");

	/* extract the food group descriptions and put in the combo box. */
	if ( !fg_desc_list) fg_desc_list = gnutr_get_fd_group_desc_glist();

	gtk_combo_set_popdown_strings( GTK_COMBO( glade_xml_get_widget ( 
		xml, "txt_fg_combo")), fg_desc_list);

	/* Initialize all the widgets in the nutrient search notebook page. */

	/* set the food group combo box. */
	gtk_combo_set_popdown_strings( GTK_COMBO( glade_xml_get_widget( 
		xml, "nut_fg_combo")), fg_desc_list);

	/* set the nutrient combo boxes. */
	if ( !nutr_desc_list) nutr_desc_list = gnutr_get_nutr_desc_glist();

	gtk_combo_set_popdown_strings( GTK_COMBO( glade_xml_get_widget( 
		xml, "nut_max_combo")), nutr_desc_list);
	gtk_combo_set_popdown_strings( GTK_COMBO( glade_xml_get_widget( 
		xml, "nut_min_combo")), nutr_desc_list);

	/* reset the max and min selected clist rows as unselected. */
	selected_row_clist_max = -1;
	selected_row_clist_min = -1;

	/* reset the number of rows in the clists to zero. */
	n_rows_clist_max = 0;
	n_rows_clist_min = 0;

	/* clear any existing clist entries. */
	gtk_clist_clear( ( GtkCList *)glade_xml_get_widget( 
		xml, "nut_max_clist"));
	gtk_clist_clear( ( GtkCList *)glade_xml_get_widget( 
		xml, "nut_min_clist"));

	/* reset the spin buttons to their default values. */
	gtk_spin_button_set_value( GTK_SPIN_BUTTON( glade_xml_get_widget( 
		xml, "nut_max_spin")), 1);
	gtk_spin_button_set_value( GTK_SPIN_BUTTON( glade_xml_get_widget( 
		xml, "nut_min_spin")), -1);

	/* reset the number of foods to be found. */
	gtk_entry_set_text( GTK_ENTRY( glade_xml_get_widget( 
		xml, "nut_no_fd_entry")), "40");
}

/* Show the food selection/search dialog and initialize all
 * the relevant widgets. */
void
gnutr_show_srch_dlg( int view)
{
	GtkWidget *widget;

	g_return_if_fail( view == PLAN_VIEW || view == RECIPE_VIEW ||
		view == FOOD_VIEW);

	initialize_dlg();

	/* store the active view. */
	active_view = view;

	/* show the search dialog. */
	widget = glade_xml_get_widget( xml, "gnutr_food_srch_dlg");
	gnome_dialog_close_hides( GNOME_DIALOG( widget), TRUE);
	gtk_widget_show( widget);
}

/* create a glist of foods that match the search criteria. */
GList *
gnutr_search_food_by_text()
{
	GList *ret_list = NULL; 
	GtkWidget *widget;
	char *group_desc, *group_no; 
	char *search_text;
	char *query;
	GHashTable *htbl;

	htbl = get_htbl_fd_gp_desc_fd_gp_no();

	widget = glade_xml_get_widget( xml, "txt_fd_entry");
	search_text = gtk_entry_get_text( GTK_ENTRY( widget));

	/* return if search string is not defined. */
	if ( strcmp( search_text, "") == 0) return NULL;

	widget = glade_xml_get_widget( xml, "txt_fg_entry");
	group_desc = gtk_entry_get_text( GTK_ENTRY( widget));

	/* find food group number form food group desc. */
	group_no = (char *)g_hash_table_lookup( htbl, (gpointer)group_desc);


	if ( strcmp( group_desc, "All Foods") == 0) {
		query = g_strconcat( "SELECT fd_no FROM food_des ",
			"WHERE fd_desc REGEXP '", search_text, "'", NULL);
	} else {
		query = g_strconcat( "SELECT fd_no FROM food_des ",
			"WHERE fd_gp = '", group_no, "' ",
			"AND fd_desc REGEXP '", search_text, "'", NULL);
	}
	ret_list = get_glist_fields_query_db( query);

	g_free( query);

	return ret_list;
}

/* a warning dialog that tells the user that no foods were
 * found that match the search criteria. */
static void
show_none_found_warn()
{
	GtkWidget *warn_dlg;
	warn_dlg = gnome_warning_dialog( "No foods found.");
	gtk_widget_show( warn_dlg);
}

/* The callback when the "search" button is pressed in the food
 * search dialog. */
void
on_search_button_released( GtkButton *button,
                           gpointer   user_data)
{
	GList *food_res_list = NULL;
	int page;
	GtkWidget *widget;

	widget = glade_xml_get_widget( xml, "notebook");

	/* find out what notebook page is showing. */
	page = gtk_notebook_get_current_page( GTK_NOTEBOOK( widget));

	if ( page == 0) {   /* search for foods by text match. */
		/* the food_res_list consists of food numbers as text. */
		food_res_list = gnutr_search_food_by_text();
		if ( !food_res_list) {
			show_none_found_warn();
			return;
		}
		gnutr_show_food_srch_res_dlg( food_res_list, active_view);

	} else {   /* search for foods by nutrient composition. */
		food_res_list = gnutr_search_food_by_nutrient();
		if ( !food_res_list) {
			show_none_found_warn();
			return;
		}
		gnutr_show_food_srch_res_dlg( food_res_list, active_view);
	}
}

/* The callback when the "cancel" button is pressed in the food
 * search dialog. Close the dialog, and the search result dialog
 * if it visible. */
void
on_cancel_button_released( GtkButton *button,
                           gpointer   user_data)
{	
	GtkWidget *search_dialog = glade_xml_get_widget( 
		xml, "gnutr_food_srch_dlg");

	gtk_widget_hide( search_dialog);
	gnutr_hide_srch_result_dialog();
}

/* when the "Add" button for the nutrients to maximize is
 * released in the nutrient search dialog. */
void
on_nut_max_add_button_released( GtkButton *button,
                                gpointer user_data)
{
	char *clist_text[2];
	gboolean match;
	GtkWidget *entry = glade_xml_get_widget( xml, "nut_max_entry");
	GtkWidget *spin = glade_xml_get_widget( xml, "nut_max_spin");
	GtkCList *clist_max = ( GtkCList *)glade_xml_get_widget( xml,
		"nut_max_clist");
	GtkCList *clist_min = ( GtkCList *)glade_xml_get_widget( xml,
		"nut_min_clist");

	/* get the text to append to the clist. */
	char *nutrient_text = gtk_entry_get_text( GTK_ENTRY( entry));
	int nutrient_weighting = gtk_spin_button_get_value_as_int( 
		GTK_SPIN_BUTTON( spin));

	/* check that the entry to be added does not already exist
	 * in the maximize clist. */
	if ( n_rows_clist_max != 0) {
		match = check_no_nutr_match( clist_max, nutrient_text,
			n_rows_clist_max);

		if ( match) {
			GtkWidget *warn_dlg = gnome_warning_dialog( 
				"The nutrient has already been selected.");
			gtk_widget_show( warn_dlg);
			return;
		}
	}

	/* check that the entry to be added does not already exist
	 * in the minimize clist. */
	if ( n_rows_clist_min != 0) {
		match = check_no_nutr_match( clist_min, nutrient_text,
				n_rows_clist_min);

		if ( match) {
			GtkWidget *warn_dlg;
			char *text = g_strconcat( "Cannot maximize and ",
				"minimize the same nutrient.", NULL);
			warn_dlg = gnome_warning_dialog( text);
			gtk_widget_show( warn_dlg);
			return;
		}
	}

	clist_text[0] = nutrient_text;
	clist_text[1] = itoa( nutrient_weighting);

	/* append to the clist. */
	gtk_clist_append( clist_max, clist_text);
	n_rows_clist_max++;
}

/* when the "Remove" button for the nutrients to maximize is
 * released. */
void
on_nut_max_remove_button_released( GtkButton *button,
                                   gpointer user_data)
{
	/* check that a row has been selected. */
	if ( selected_row_clist_max != -1) {
		GtkCList *clist = ( GtkCList *)glade_xml_get_widget( xml,
			"nut_max_clist");
		gtk_clist_remove( clist, selected_row_clist_max);
		selected_row_clist_max = -1;
		n_rows_clist_max--;
	} else {
		GtkWidget *warn_dlg = gnome_warning_dialog( 
			"No row is selected.");
		gtk_widget_show( warn_dlg);
		return;
	}
}

/* when the "Add" button for the nutrients to minimize is
 * released. */
void
on_nut_min_add_button_released( GtkButton *button,
                                gpointer user_data)
{
	char *clist_text[2];
	gboolean match;
	GtkWidget *entry = glade_xml_get_widget( xml, 
		"nut_min_entry");
	GtkWidget *spin = glade_xml_get_widget( xml, "nut_min_spin");
	GtkCList *clist_min = ( GtkCList *)glade_xml_get_widget( xml,
		"nut_min_clist");
	GtkCList *clist_max = ( GtkCList *)glade_xml_get_widget( xml,
		"nut_max_clist");

	/* get the text to append to the clist. */
	char *nutrient_text = gtk_entry_get_text( GTK_ENTRY( entry));
	int nutrient_weighting = gtk_spin_button_get_value_as_int( 
		GTK_SPIN_BUTTON( spin));

	clist_text[0] = nutrient_text;
	clist_text[1] = itoa( nutrient_weighting);

	/* check that the entry to be added does not already exist
	 * in the clist. */
	if ( n_rows_clist_min != 0) {
		match = check_no_nutr_match( clist_min, nutrient_text,
			n_rows_clist_min);

		if ( match) {
			GtkWidget *warn_dlg = gnome_warning_dialog( 
				"The nutrient is already selected.");
			gtk_widget_show( warn_dlg);
			return;
		}
	}

	/* check that the entry to be added does not already exist
	 * in the clist. */
	if ( n_rows_clist_max != 0) {
		match = check_no_nutr_match( clist_max, nutrient_text,
			n_rows_clist_max);

		if ( match) {
			GtkWidget *warn_dlg;
			char *text;

			text = g_strconcat( "Cannot maximize and minimize ",
				"the same nutrient.", NULL);
			warn_dlg = gnome_warning_dialog( text);
			gtk_widget_show( warn_dlg);
			return;
		}
	}

	/* append to the clist. */
	gtk_clist_append( clist_min, clist_text);
	n_rows_clist_min++;
}

/* when the "Remove" button for the nutrients to minimize is
 * released. */
void
on_nut_min_remove_button_released( GtkButton *button,
                                   gpointer user_data)
{
	/* check that a row has been selected. */
	if ( selected_row_clist_min != -1) {
		GtkCList *clist = ( GtkCList *)glade_xml_get_widget( xml,
			"nut_min_clist");
		gtk_clist_remove( clist, selected_row_clist_min);
		selected_row_clist_min = -1;
		n_rows_clist_min--;
	} else {
		GtkWidget *warn_dlg = gnome_warning_dialog( 
			"No row is selected.");
		gtk_widget_show( warn_dlg);
		return;
	}
}

/* callback when a row is selected from the clist of nutrients to
 * maximize. */
void
on_nut_max_clist_select_row( GtkCList *clist, 
                             int       row, 
                             int       column,
                             GdkEvent *event,
                             gpointer  user_data)
{
	selected_row_clist_max = row;
}

/* callback when a row is unselected from the clist of nutrients to
 * maximize. */
void
on_nut_max_clist_unselect_row( GtkCList *clist, 
                               int       row, 
                               int       column,
                               GdkEvent *event,
                               gpointer  user_data)
{
	selected_row_clist_max = -1;
}

/* callback when a row is selected from the clist of nutrients to
 * minimize. */
void
on_nut_min_clist_select_row( GtkCList *clist, 
                             int       row, 
                             int       column,
                             GdkEvent *event,
                             gpointer  user_data)
{
	selected_row_clist_min = row;
}

/* callback when a row is unselected from the clist of nutrients to
 * minimize. */
void
on_nut_min_clist_unselect_row( GtkCList *clist, 
                               int       row, 
                               int       column,
                               GdkEvent *event,
                               gpointer  user_data)
{
	selected_row_clist_min = -1;
}

/* check that the text to be added to the clist does not already
 * exist in the clist. If it does, return TRUE. */
static gboolean
check_no_nutr_match( GtkCList *clist, 
                     char     *text, 
                     int       n_rows)
{
	char *clist_text;
	int row;

	g_return_val_if_fail( clist, FALSE);
	g_return_val_if_fail( text, FALSE);

	for ( row=0; row<n_rows; row++) {
		gtk_clist_get_text( clist, row, 0, &clist_text);
		if ( strcmp( clist_text, text) == 0) return TRUE;
	}
	return FALSE;
}

/* gather the info necessary to perform the search of foods, perform
 * the search, return the result. */
GList *
gnutr_search_food_by_nutrient()
{
	GtkWidget *option_menu;
	char *norm_option, *food_group, *no_foods, *text;
	int no_foods_to_list, row;
	Constr_t *elm;
	GtkCList *clist;
	GList *ret_list = NULL;
	GList *constr_list = NULL;

	/* check that search constraints have been specified, else
	 * return. */
	if ( n_rows_clist_max == 0 && n_rows_clist_min == 0) {
		return NULL;
	}

	/* get the selected food group. */
	food_group = gtk_entry_get_text( GTK_ENTRY( glade_xml_get_widget( 
		xml, "nut_fg_entry")));

	/* get the label of the active menu item in the GtkOptionMenu.
	 * i.e. either search nutrient density by calorie or weight. */
	option_menu = glade_xml_get_widget( xml, "nut_optionmenu");
	if ( GTK_BIN( option_menu)->child) {
		GtkWidget *child = GTK_BIN( option_menu)->child;
		if ( GTK_IS_LABEL( child)) {
			gtk_label_get( GTK_LABEL( child), &norm_option);
		}
	}

	/* get the number of foods to display in search result. */
	no_foods = gtk_entry_get_text( GTK_ENTRY( glade_xml_get_widget( 
		xml, "nut_no_fd_entry")));
	no_foods_to_list = atoi( no_foods);

	/* get the constraint data, and put into a glist. */
	clist = ( GtkCList *)glade_xml_get_widget( xml, "nut_max_clist");
	for ( row = 0; row < n_rows_clist_max; row++) {
		elm = ( Constr_t *)g_malloc( sizeof( Constr_t));

		gtk_clist_get_text( clist, row, 0, &text);
		elm->nutr_desc = g_strdup( text);

		gtk_clist_get_text( clist, row, 1, &text);
		elm->weighting = g_strdup( text);

		constr_list = g_list_prepend( constr_list, (gpointer)elm);
	}

	clist =( GtkCList *)glade_xml_get_widget( xml, "nut_min_clist");
	for ( row = 0; row < n_rows_clist_min; row++) {
		elm = ( Constr_t *)g_malloc( sizeof( Constr_t));

		gtk_clist_get_text( clist, row, 0, &text);
		elm->nutr_desc = g_strdup( text);

		gtk_clist_get_text( clist, row, 1, &text);
		elm->weighting = g_strdup( text);

		constr_list = g_list_prepend( constr_list, (gpointer)elm);
	}

	/* perform the search. */
	ret_list = gnutr_nutrient_constraint_search( food_group, 
		norm_option, no_foods_to_list, constr_list);

	return ret_list;
}

static void
sum_nutrient_values( GList  *fd_no_nutr_no_nutr_val_list, 
                     GList **nutr_total_list, 
                     char   *option)
{
	GList *ptr1, *ptr2 = NULL;
	char *energy_val = NULL;
	char **elm1, **elm2;
	float total;

	g_return_if_fail( fd_no_nutr_no_nutr_val_list);
	g_return_if_fail( *nutr_total_list);
	g_return_if_fail( option);

	/* get the nutrient value for nutr_no=208, the energy
	 * in kcals. */
	for ( ptr1 = fd_no_nutr_no_nutr_val_list; ptr1; ptr1 = ptr1->next) {
		elm1 = ( char **)ptr1->data;
		/* elm1[0] = fd_no, elm1[1] = nutr_no, elm1[2] = nutr_val. */
		if ( strcmp( elm1[1], "208") == 0) {
			energy_val = elm1[2];
		}
	}

	/* ignore foods whose energy in kcals is undefined. */
	if ( !energy_val || atof( energy_val) == 0.0) return;

	/* sum the nutrient values for the food to the total nutrient
	 * values. Divide, if necessary, by the energy in kcals. If not, the
	 * values are per 100gm rather than per kcal. */
	for ( ptr1 = *nutr_total_list; ptr1; ptr1 = ptr1->next) {
		elm1 = ( char **)ptr1->data;
		/* elm1[0]=nutr_no, elm1[1]=nutr_val_total, 
		 * elm1[2]=constraint. */

		for ( ptr2 = fd_no_nutr_no_nutr_val_list; ptr2; 
			ptr2 = ptr2->next) 
		{
			elm2 = ( char **)ptr2->data;
			/* elm2[0] = fd_no, elm2[1] = nutr_no, 
			 * elm2[2] = nutr_val. */

			if ( strcmp( elm1[0], elm2[1]) == 0) {
				total = atof( elm1[1]);
				g_free( elm1[1]);

				if ( strcmp( option, "calorie") == 0) {
					total += atof( elm2[2]) / 
						atof( energy_val);
				} else {
					total += atof( elm2[2]);
				}
				elm1[1] = g_strdup( ftoa( total));
				break;
			}
		}
	}
}

static float
calc_food_score( GList *fd_no_nutr_no_nutr_val_list, 
                 GList *nutr_mean_list,
                 char  *option)
{
	float food_score = 0.0, nutr_val;
	char *energy_val = NULL;
	GList *ptr1, *ptr2;
	char **elm1, **elm2;

	g_return_val_if_fail( fd_no_nutr_no_nutr_val_list, 0.0);
	g_return_val_if_fail( nutr_mean_list, 0.0);
	g_return_val_if_fail( option, 0.0);

	/* get the nutrient value for nutr_no=208, the energy
	 * in kcals. */
	for ( ptr1 = fd_no_nutr_no_nutr_val_list; ptr1; ptr1 = ptr1->next) {
		elm1 = ( char **)ptr1->data;
		/* elm1[0] = fd_no, elm1[1] = nutr_no, elm1[2] = nutr_val. */
		if ( strcmp( elm1[1], "208") == 0) {
			energy_val = elm1[2];
		}
	}

	/* ignore foods whose energy in kcals is undefined. */
	if ( !energy_val || atof( energy_val ) == 0.0) return 0.0;

	/* compute the food score. */
	for ( ptr1 = fd_no_nutr_no_nutr_val_list; ptr1; ptr1 = ptr1->next) {
		elm1 = ( char **)ptr1->data;
		/* elm1[0] = fd_no, elm1[1] = nutr_no, elm1[2] = nutr_val. */

		for ( ptr2 = nutr_mean_list; ptr2; ptr2 = ptr2->next) {
			elm2 = ( char **)ptr2->data;
			/* elm2[0]=nutr_no, elm2[1]=nutr_mean_val, 
			 * elm2[2]=constraint */

			if ( strcmp( elm1[1], elm2[0]) == 0) {
				if ( strcmp( option, "calorie") == 0) {
					nutr_val = atof( elm1[2]) / 
						atof( energy_val);
				} else {
					nutr_val = atof( elm1[2]);
				}
				food_score += ( atof( elm2[2]) * nutr_val) / 
					atof( elm2[1]);
				break;
			}
		}
	}
	return food_score;
}

/* perform the search of the foods, and list those that satisfy the
 * constraints in order. */
static GList *
gnutr_nutrient_constraint_search( char  *food_group,
                                  char  *norm_option, 
                                  int    no_to_list,
                                  GList *constr_list)
{
	GList *ptr = NULL;
	GList *score_list = NULL;
	GList *ret_list = NULL;
	GList *temp_list = NULL;
	GList *fd_no_nutr_no_nutr_val_list = NULL;
	Score_t *score;
	float food_score, total;
	int no_foods;
	char *fd_no, *fd_no_prev;
	char *query, *query_temp;
	char **elm, **elm2;
	char *nutr_no, *fd_gp_no;
	GHashTable *htbl;
	GList *nutr_total_list;

	g_return_val_if_fail( food_group, NULL);
	g_return_val_if_fail( norm_option, NULL);
	g_return_val_if_fail( constr_list, NULL);

	/* get the hash table whey the nutrient description is the key 
	 * and the nutrient number the value. */
	htbl = get_htbl_nutr_desc_nutr_no();

	/* create a list with the ( nutr_no, nutr_val, constraint) that 
	 * contain the nutrient total. Initialize to zero. */
	nutr_total_list = NULL;
	for( ptr = constr_list; ptr; ptr = ptr->next) {
		elm = ( char **)ptr->data;
		/* elm[0] = nutr_desc, elm[1] = constraint. */
		nutr_no = g_hash_table_lookup( htbl, (gpointer)elm[0]);

		elm2 = ( char **)g_malloc( 3 * sizeof( char *));
		elm2[0] = g_strdup( nutr_no);
		elm2[1] = g_strdup( "0.0");
		elm2[2] = g_strdup( elm[1]);

		nutr_total_list = g_list_append( nutr_total_list, 
			(gpointer)elm2);
	}

	/* create the part of the SQL query which selects the nutrients
	 * that are constraints. */
	query = g_strdup( " ( ");
	for( ptr = constr_list; ptr; ptr = ptr->next) {
		elm = ( char **)ptr->data;
		/* elm[0] = nutr_desc, elm[1] = constraint. */
		nutr_no = g_hash_table_lookup( htbl, (gpointer)elm[0]);

		query_temp = g_strconcat( query,
			" nutr_no ='", nutr_no, "' OR", NULL);
		g_free( query);
		query = g_strdup( query_temp);
		g_free( query_temp);
	}

	/* add the energy ( kcals) nutrient to the query. */
	query_temp = g_strconcat( query, " nutr_no ='208' )", NULL);
	g_free( query);

	if ( strcmp( food_group, "All Foods") == 0 ) {
		query = g_strconcat( "SELECT fd_no, nutr_no, nutr_val "
			"FROM nut_data WHERE ", query_temp, NULL);
	} else {
		/* get the food group number from its description. */
		htbl = get_htbl_fd_gp_desc_fd_gp_no();
		fd_gp_no = g_hash_table_lookup( htbl, (gpointer)food_group);
	
		/* create a list which contains three fields from all the
		 * rows in the nut_data table that are in the chosen food
		 * group, and constraint nutrients. */
		query = g_strconcat( "SELECT nut_data.fd_no, nutr_no, nutr_val"
			" FROM food_des, nut_data WHERE ",
			"food_des.fd_gp = '", fd_gp_no, "' AND "
			"nut_data.fd_no = food_des.fd_no AND ", query_temp, 
			NULL);
	}

	fd_no_nutr_no_nutr_val_list = rows_glist_ret_val_query_db( query);
	g_assert( fd_no_nutr_no_nutr_val_list);
	g_free( query);
	g_free( query_temp);

	/* compute the total nutrient value for each of the nutrients that
	 * are a constraint. We use this to normalize the nutrient
	 * values. */
	no_foods = 0;
	fd_no_prev = g_strdup( " ");
	temp_list = NULL;
	for ( ptr = fd_no_nutr_no_nutr_val_list; ptr; ptr = ptr->next) {
		elm = ( char **)ptr->data;
		fd_no = elm[0];

		/* when we have reached a new food, we act upon the 
		 * temporary list that we have created. The temporary list 
		 * contains all the data for a simgle food. */
		if ( temp_list && strcmp( fd_no, fd_no_prev) !=0 ) {

			sum_nutrient_values( temp_list, &nutr_total_list, 
				norm_option);

			/* free the temporary list. */
			g_list_free( temp_list);
			temp_list = NULL;

			++no_foods;
		}

		/* add a new element to the tempory list. */
		temp_list = g_list_prepend( temp_list, (gpointer)elm);
		g_free( fd_no_prev);
		fd_no_prev = g_strdup( fd_no);
	}

	/* compute the average of each nutrient total. These values are used
	 * to normalize the nutrient values for each food. */
	for ( ptr = nutr_total_list; ptr; ptr = ptr->next) {
		elm = ( char **)ptr->data;
		/* elm[0] = nutr_no, elm[1] = nutr_val_total, 
		 * elm[2] = constraint */
		total = atof( elm[1]);
		g_free( elm[1]);
		elm[1] = g_strdup( ftoa( total/( float)no_foods));
	}

	/* compute the food nutrient score on the basis of the given
	 * constraints. */
	fd_no_prev = g_strdup( " ");
	temp_list = NULL;
	for ( ptr = fd_no_nutr_no_nutr_val_list; ptr; ptr = ptr->next) {
		elm = ( char **)ptr->data;
		fd_no = elm[0];

		/* when we have reached a new food, we act upon the 
		 * temporary list that we have created. The temporary list 
		 * contains all the data for a simgle food. */
		if ( temp_list && strcmp( fd_no, fd_no_prev) !=0 ) {

			food_score = calc_food_score( temp_list, 
				nutr_total_list, norm_option);

			/* free the temporary list. */
			g_list_free( temp_list);
			temp_list = NULL;

			/* add to the list of foods that have been scored 
			 * for their nutrient composition. */
			score_list = add_to_score_list( score_list, fd_no, 
				food_score, no_to_list);
		}

		/* add a new element to the tempory list. */
		temp_list = g_list_prepend( temp_list, (gpointer)elm);
		g_free( fd_no_prev);
		fd_no_prev = g_strdup( fd_no);
	}

	/* free the result from the sql query. */
	gnutr_free_row_list( fd_no_nutr_no_nutr_val_list, 3);

	/* we have a list that consists of fd_no and score values. We want to
	 * return a list that consists of fd_no only. */
	for ( ptr = score_list; ptr; ptr = ptr->next) {
		score = ( Score_t *)ptr->data;
		fd_no = g_strdup( score->fd_no);

		ret_list = g_list_append( ret_list, (gpointer)fd_no);

		/* free score list. */
		g_free( score->fd_no);
	}
	g_list_free( score_list);

	return ret_list;
}

/* The compare function( *GCompareFunc) used in g_list_insert_sorted(). */
static int
compare_score( Score_t *a, 
               Score_t *b)
{
	/* by reversing the normal return values for the comparison, the 
	 * list is sorted in the reverse order expected by qsort() - the
	 * highest is first rather than last. */
	if ( a->food_score < b->food_score) return 1;
	if ( a->food_score > b->food_score) return -1;
	return 0;
}

/* compare food score with those already in list. If the score is above
 * any in the list, insert food in order. */
static GList *
add_to_score_list( GList *score_list, 
                   char  *fd_no, 
                   float  food_score, 
                   int    no_to_list)
{
	GList *ret_list;
	Score_t *elm;
	GList *ptr;
	int length;

	g_return_val_if_fail( fd_no, NULL);

	elm =(Score_t *)g_malloc( sizeof(Score_t));
	elm->fd_no = g_strdup( fd_no);
	elm->food_score = food_score;


	/* set the list to be returned to point to the score list passed. */
	ret_list = score_list;
	length = g_list_length( ret_list);

	if ( length == 0) {
		ret_list = g_list_append( ret_list, (gpointer)elm);

	} else if ( length < no_to_list) {
		ret_list = g_list_insert_sorted( ret_list, (gpointer)elm,
				(GCompareFunc)compare_score);

	} else {
		/* if the food score is higher than the lowest value in 
		 * the list, insert it into the list in order, and remove 
		 * the lowest scoring food from the list. */
		ptr = g_list_last( ret_list);
		if ( elm->food_score > ( (Score_t *)ptr->data)->food_score) {

			ret_list = g_list_insert_sorted( ret_list, 
				(gpointer)elm, (GCompareFunc)compare_score);

			/* reset elm to point to the lowest node in list, 
			 * the one to free. */
			elm = ( Score_t *)ptr->data;
			g_free( elm->fd_no);
			g_free( elm);
			ret_list = g_list_remove_link( ret_list, ptr);
		} else {
			/* here elm points to the Score_t allocated at the 
			 * start of the function. */
			g_free( elm->fd_no);
			g_free( elm);
		}
	}
	return ret_list;
}

/* Hide the search dialog if it is visible. */
void
gnutr_hide_srch_dlg()
{
	if ( xml)    /* check that the interface has been loaded. */
	{
		GtkWidget *dlg = glade_xml_get_widget( 
			xml, "gnutr_food_srch_dlg");
		if ( GTK_WIDGET_VISIBLE( dlg)) gtk_widget_hide( dlg);
	}
}

GtkWidget *
gnutr_get_food_text_srch_table()
{
	initialize_dlg();
	return glade_xml_get_widget( xml, "text_srch_table");
}

GtkWidget *
gnutr_get_food_nutr_srch_table()
{
	/* this called with gnutr_get_food_text_srch_table() so
	 * no need to call initialize_dlg(). */
	return glade_xml_get_widget( xml, "nutr_srch_table");
}

GtkWidget *
gnutr_get_food_nutr_srch_container()
{
	/* this called with gnutr_get_food_text_srch_table() so
	 * no need to call initialize_dlg(). */
	return glade_xml_get_widget( xml, "nutr_srch_container");
}

GtkWidget *
gnutr_get_food_text_srch_container()
{
	/* FIXME: I could initialize the two pages separately. */
	initialize_dlg();
	return glade_xml_get_widget( xml, "text_srch_container");
}
