/*  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 "text_util.h"
#include "food.h"
#include "recipe.h"
#include "plan.h"
#include "recipe_win.h"
#include "support.h"
#include "food_srch_dlg.h"
#include "food_srch_res.h"
#include "plan_win.h"
#include "plan_add_dlg.h"
#include "food_win.h"
#include "wrap_mysql.h"

static char *selected_food_no = NULL;
static GladeXML *xml = NULL; 

static enum { PLAN_VIEW, RECIPE_VIEW, FOOD_VIEW } active_view;

static void load_xml( void);
static void connect_signals( void);
static void food_srch_res_add_as_tree( GtkTree *, GtkTreeItem *, GList *, int);
static char * adj_at_level( const char *, int);
static char * str_at_level( const char *, int);
static void clear_tree( GtkTree *);
static void clear_tree_sub( GtkTree *);

/* callbacks. */
static void on_tree_selection_changed( GtkTree *, gpointer);
static void on_food_entry_changed( GtkEditable *, gpointer);
static void on_combo_entry_changed( GtkEditable *, gpointer);
static void on_ok_button_released( GtkButton *, gpointer);
static void on_cancel_button_released( GtkButton *, gpointer);

/* recursively clear the sub trees. */
static void
clear_tree_sub( GtkTree *tree)
{
	GList *ptr1, *ptr2, *child_list;
	GtkTreeItem *item1, *item2;
	char *fd_no;

	for ( ptr1 = tree->children; ptr1; ptr1 = ptr1->next) {
		item1 = GTK_TREE_ITEM( ptr1->data);

		if ( item1->subtree) {
			clear_tree_sub( GTK_TREE( item1->subtree));

			child_list = GTK_TREE( item1->subtree)->children;

			for( ptr2 = child_list; ptr2; ptr2 = ptr2->next) {
				item2 = GTK_TREE_ITEM( ptr2->data);

				fd_no = (char *)gtk_object_get_user_data( 
						GTK_OBJECT( item2));
				if ( fd_no) g_free( fd_no);
			}

			gtk_tree_clear_items( GTK_TREE( item1->subtree), 0, 
				g_list_length( child_list) - 1);
		}
	}
}

/* clear the tree. */
static void
clear_tree( GtkTree *tree)
{
	GtkTreeItem *item;
	GList *ptr;
	char *fd_no;

	clear_tree_sub( tree);

	for( ptr = tree->children; ptr; ptr = ptr->next)
	{
		item = GTK_TREE_ITEM( ptr->data);
		fd_no = (char *)gtk_object_get_user_data( GTK_OBJECT( item));
		if (fd_no) g_free( fd_no);
	}

	gtk_tree_clear_items( tree, 0, g_list_length( tree->children) - 1);
}

/* connect the signals. */
static void
connect_signals()
{
	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, "ok_button")),
		"released", GTK_SIGNAL_FUNC( on_ok_button_released), NULL);

	gtk_signal_connect( GTK_OBJECT( 
		glade_xml_get_widget( xml, "tree")), "selection-changed", 
		GTK_SIGNAL_FUNC( on_tree_selection_changed), NULL);

	gtk_signal_connect( GTK_OBJECT( 
		glade_xml_get_widget( xml, "food_entry")),
		"changed", GTK_SIGNAL_FUNC( on_food_entry_changed), NULL);

	gtk_signal_connect( GTK_OBJECT( 
		glade_xml_get_widget( xml, "combo_entry")),
		"changed", GTK_SIGNAL_FUNC( on_combo_entry_changed), NULL);
}

/* load the glade xml interface. */
static void
load_xml()
{
	static gboolean loaded_xml = FALSE;

	if ( !loaded_xml) {
		xml = glade_xml_new( 
			GNUTRITION_GLADEDIR "/food_search_result_dlg.glade", 
			NULL);
		loaded_xml = TRUE;

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

/* Show the food search dialog. food_res_list contains a
 * list of fd_no that satisfy the search criteria. */
void
gnutr_show_food_srch_res_dlg( GList *food_res_list, 
                              int view)
{
	GList *measure_text_list = NULL; 
	GtkWidget *search_dialog, *widget;
	GtkTree *tree;

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

	if ( !xml) load_xml();

	/* store the active view. */
	active_view = view;
	fprintf( stderr, "active_view = %d\n", active_view);

	search_dialog = glade_xml_get_widget( xml, "gnutr_srch_result_dlg");
	tree = (GtkTree *) glade_xml_get_widget( xml, "tree");

	/* Reset the widgets. */
	measure_text_list = g_list_append( measure_text_list, "");

	widget =  glade_xml_get_widget( xml, "msre_combo");
	gtk_combo_set_popdown_strings( GTK_COMBO( widget ), measure_text_list);
	widget =  glade_xml_get_widget( xml, "amount_entry");
	gtk_entry_set_text( GTK_ENTRY( widget), "");
	widget = glade_xml_get_widget( xml, "food_entry");
	gtk_entry_set_text( GTK_ENTRY( widget), "");

	/* create the tree of foods. */
	food_srch_res_add_as_tree( tree, NULL, food_res_list, 1);

	gnutr_free_field_list( food_res_list);

	widget = glade_xml_get_widget( xml, "msre_amount_box");
	gtk_widget_show( widget);
	if ( active_view == FOOD_VIEW) {
		gtk_widget_hide( widget);
	}

	/* show the search dialog. */
	gnome_dialog_close_hides( GNOME_DIALOG(search_dialog), TRUE);
	gtk_widget_show( search_dialog);
}

/*  When item in the tree is selected, add the selected food to the 
 *  text entry below it. */
static void
on_tree_selection_changed( GtkTree *tree,
                           gpointer user_data)
{
	GList *selection = NULL;
	GtkWidget *widget;

	g_return_if_fail( tree);

	selection = GTK_TREE_SELECTION( tree);

	if ( selection) {
		GtkObject *sel_obj;
		sel_obj = GTK_OBJECT( g_list_first( selection)->data);
		/* get the hidden data from the tree list - the food number
		 * stored as a char. */
		selected_food_no =( char *)gtk_object_get_user_data( sel_obj);
	} else {
		selected_food_no = NULL;
	}

	if ( selected_food_no) {
		GHashTable *hash_tbl;
		char *fd_desc;

		hash_tbl = get_htbl_fd_no_fd_desc();
		fd_desc =( char *)g_hash_table_lookup( hash_tbl, 
			selected_food_no);

		widget = glade_xml_get_widget( xml, "food_entry");
		gtk_entry_set_text( GTK_ENTRY( widget), fd_desc);
	} else {    
		/* set fields to blank. Reset the widgets. */
		GList *msre_list = NULL;
		msre_list = g_list_append( msre_list, "");
		
		widget = glade_xml_get_widget( xml, "msre_combo");
		gtk_combo_set_popdown_strings( GTK_COMBO( widget), msre_list);

		widget = glade_xml_get_widget( xml, "amount_entry");
		gtk_entry_set_text( GTK_ENTRY( widget), "");

		widget = glade_xml_get_widget( xml, "food_entry");
		gtk_entry_set_text( GTK_ENTRY( widget), "");
	}
}

/* When the selected food is changed, also change the measure_combo 
 * to list the measures for the new food. */
static void
on_food_entry_changed( GtkEditable *editable,
                       gpointer     user_data)
{
	char *food_name;
	GtkWidget *widget;
	
	widget = glade_xml_get_widget(xml, "food_entry");
	food_name = gtk_entry_get_text( GTK_ENTRY( widget));

	/* return if the food_entry is empty. */
	if ( strcmp( food_name, "") == 0) return;

	/* return if no food has been selected. */
	if ( !selected_food_no) return;

	/* set the measure combo box to the new measure descriptions. */
	{
		GList *desc_list = NULL;

		desc_list = gnutr_get_fd_no_msre_desc_list( selected_food_no);

		widget = glade_xml_get_widget( xml, "msre_combo");
		gtk_combo_set_popdown_strings( GTK_COMBO( widget), desc_list);
		g_list_free( desc_list);
	}
}

/* When the measure is changed, reset the amount. */
static void
on_combo_entry_changed( GtkEditable *editable,
                        gpointer     user_data)
{
	GtkWidget *entry;
	
	entry = glade_xml_get_widget( xml, "amount_entry");
	gtk_entry_set_text( GTK_ENTRY( entry), "1.00");
}

/* Exit the Search Result Dialog with the "OK" button. Gather the necessary
 * data for the food tree in the recipe window. */
static void
on_ok_button_released( GtkButton *button,
                       gpointer   user_data)
{
	char *amount;
	char *msre_desc, *msre_no;
	GtkTree *tree;
	GtkWidget *widget;
	GHashTable *htbl;

	/* check that a food has been selected. If not just return. */
	if ( selected_food_no == NULL)  return;

	htbl = get_htbl_msre_desc_msre_no();

	/* amount. */
	widget = glade_xml_get_widget( xml, "amount_entry");
	amount = gtk_entry_get_text( GTK_ENTRY( widget));

	/* measure. */
	widget = glade_xml_get_widget( xml, "combo_entry");
	msre_desc = gtk_entry_get_text( GTK_ENTRY( widget));
	if ( strcmp( msre_desc, "gm") == 0) {
		msre_no = g_strdup( "99999");
	} else {
		msre_no =( char *)g_hash_table_lookup( htbl,
			( gpointer)msre_desc);
	}

	/* perform different actions depending on the active view. */
	if ( active_view == PLAN_VIEW) {
		gnutr_add_ingredient_to_plan( g_strdup( selected_food_no), 
			amount, msre_no);
		gnutr_hide_plan_add_dlg();
	} else if ( active_view == RECIPE_VIEW) {
		gnutr_add_ingredient_to_recipe_clist( 
			g_strdup( selected_food_no), amount, msre_desc);
		gnutr_hide_srch_dlg();
	} else {    /* FOOD_VIEW. */
		gnutr_update_food_win( g_strdup( selected_food_no));
	}


	/* If the search dialog is visible, hide it. */
	gnutr_hide_srch_dlg();

	/* clear the food result tree. */
	tree =( GtkTree *)glade_xml_get_widget( xml, "tree");
	clear_tree( tree);

	/* Hide the result dialog. */  
	gtk_widget_hide( glade_xml_get_widget( xml, "gnutr_srch_result_dlg"));
}

/* exit the Search Result Dialog with the "Cancel" button. */
static void
on_cancel_button_released( GtkButton *button,
                           gpointer   user_data)
{	
	GtkTree *tree;

	/* clear the food result tree. */
	tree =( GtkTree *)glade_xml_get_widget( xml, "tree");
	clear_tree( tree);

	/* Hide the result dialog. */
	gtk_widget_hide( glade_xml_get_widget( xml, "gnutr_srch_result_dlg"));
}

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

/* Ian's new stuff from here on */

/* By "tree view" I mean forming a hierachy of linked treea
 * when a complex list of food options would otherwise be presented
 * to the user, so:
 * Apples, green, big
 * Apples, green, small
 * Apples, red .
 * 
 * is presented to the user as
 * Apples
 *     +-- green
 *     |    +-- big
 *     |    +-- small
 *     +-- red
 * With very large lists this makes it a lot easier */

/* Adds foods to the GtkTree in a tree arrangement 
 * by their adjectives. Call level = 1, GtkTreeItem = NULL. */
static void
food_srch_res_add_as_tree( GtkTree *tree, 
                           GtkTreeItem *parent, 
                           GList *food_match_list, 
                           int level)
{
	char *template, *this_food, *name, *label;
	GList *first, *ptr; 
	GList *match_list = NULL, *remainder_list = NULL;
	GtkTreeItem *item;
	GtkTree *subtree;
	char *fd_no;
	char *fd_desc;
	GHashTable *hash_table = get_htbl_fd_no_fd_desc();

	g_return_if_fail( tree);
	g_return_if_fail( level >= 1);
	
	/* point to the first in the list. */
	first = food_match_list;
	fd_no =( char *)first->data;
	fd_desc =( char *)g_hash_table_lookup( hash_table, fd_no);

	template = adj_at_level( fd_desc, level);

	if ( !template) {
		 /* Empty string	- label as none */
		item = GTK_TREE_ITEM( gtk_tree_item_new_with_label( " none"));

		/* append visible text to recipe tree. */
		gtk_tree_append( tree, GTK_WIDGET( item));
		gtk_widget_show( GTK_WIDGET( item));

		/* append invisible food gpointer to food item. */
		gtk_object_set_user_data( GTK_OBJECT( item),
			(gpointer)g_strdup( fd_no));

		first = first->next;     /* grab next in line and continue */
		if ( !first) return;      /* No more in list */

		fd_no =( char *)first->data;
		fd_desc =( char *)g_hash_table_lookup( hash_table, fd_no);
		template = adj_at_level( fd_desc, level);

		/* cannot modify parent now, as must be more than one 
		 * category at this level */
		parent = NULL;
	}

	match_list = g_list_prepend( match_list, first->data);

	 /* sort all foods into two lists. The two lists are match_list and
	  * remainder_list. match_list = matching foods at the first food
	  * level, remainder_list = the rest. */
	for ( ptr = first->next; ptr; ptr = ptr->next) {
		fd_no =( char *)ptr->data;
		fd_desc =( char *)g_hash_table_lookup( hash_table, fd_no);

		this_food = adj_at_level( fd_desc, level);

		if ( this_food && strcmp( this_food, template) == 0) { 
			/* add to match */
			match_list = g_list_prepend( match_list, ptr->data);
		} else {    
			/* add to remainder */
			remainder_list = g_list_prepend( remainder_list, 
				ptr->data);
		}
		free( this_food);
	}

	if ( g_list_length( match_list) > 1) {
		if ( !remainder_list && parent) {
			/* This means ALL foods at this level match the 
			 * template, so we dont want to make another sub-
			 * category, instead add to parent category */
			gtk_label_get( GTK_LABEL( GTK_BIN( parent)->child), 
				&label);
			name = g_strdup_printf( "%s, %s", label, template);
			gtk_label_set_text( GTK_LABEL( GTK_BIN( parent)->child),
				name);
			g_free( template);

			/* repeat at next level - by recursion. */
			food_srch_res_add_as_tree( tree, parent, match_list, 
				level+1);
		} else {
			/* make new category */
			item = GTK_TREE_ITEM( gtk_tree_item_new_with_label( 
				template));
			g_free( template);

			/* NULL flags item as category only */
			gtk_object_set_user_data( GTK_OBJECT( item), NULL);
			subtree = GTK_TREE( gtk_tree_new());

			/* repeat at next level - by recursion. */
			food_srch_res_add_as_tree( subtree, item, match_list, 
				level+1); 

			/* sub-hierachies */
			gtk_tree_append( tree, GTK_WIDGET( item));
			gtk_tree_item_set_subtree( item, GTK_WIDGET( subtree));
			gtk_widget_show( GTK_WIDGET( item));
		}
	} else {     /* no matches at this level */
		g_free( template);
		fd_no =( char *)first->data; 
		fd_desc =( char *)g_hash_table_lookup( hash_table, fd_no);

		/* add first value as full string - no sub-category possible */
		item = GTK_TREE_ITEM( gtk_tree_item_new_with_label(
			str_at_level( fd_desc, level)));

		/* append visible text to recipe tree. */
		gtk_tree_append( tree, GTK_WIDGET( item));
		gtk_widget_show( GTK_WIDGET( item));

		/* append invisible food gpointer to food item. */
		gtk_object_set_user_data( GTK_OBJECT( item),
			(gpointer)g_strdup( fd_no));
	}
	g_list_free( match_list);

	if ( remainder_list) {    
		/* add remainder by recursion of the function. */
		food_srch_res_add_as_tree( tree, NULL, remainder_list, level);

		/* parent set to null, as we have had multiple 
		 * adjectives at this level so cannot set parent */
		g_list_free( remainder_list);
	}
} 

/* "level", ie no. of commas, then adjective removed, so
 * adj_at_level( "Apples, green, big", 2) = "green"
 * if not enough commas - return NULL. */
static char *
adj_at_level( const char *s, 
              int n)
{
	int i, j;
	char *r;

	for ( i = 0, j= 0; s[i] != '\0' && n > 0; i++) {
		if ( s[i] == ',') {
			n--;
			/* j is position of comma. */
			if ( n > 0) j = i;
		}
	}
	/* ran off end of string. */
	if ( n > 1) return NULL;
	/* move forward beyond ", " */
	if ( j > 0) j = j+2;
	/* reverse final increment back before the comma */
	if ( n == 0) i--;
	/* we have stepped beyond the end of the string, possible
	 * if the string ends in a comma. */
	if ( i-j+1 <= 0) return NULL;
	r = g_malloc( ( i+1-j) * sizeof( char));
	memcpy( r, s+j, i-j);
	r[i-j] = '\0';

	return r;
} 

/* Remainder of adjective string, at the level, so
 * adj_at_level( "Apples, green, big", 2) = "green, big" */
static char *
str_at_level( const char *s, 
              int n)
{
	int i;

	/* Comma before */
	n--; 
	for ( i = 0; s[i] != 0 && n > 0; i++) {
		if ( s[i] == ',') n--;
	}

	return ( g_strdup( s+i));
}
