/*  gnutrition - a nutrition and diet analysis program.
 *  Copyright (C) 2000 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_win.h"
#include "support.h"

static GList *create_nutr_glist( void);
static GList *create_food_des_glist( void);
static GList *create_food_nutr_glist( GList **);
static GList *create_msre_desc_glist( void);
static GList *create_msre_weight_glist( void);
static Msre_Desc_t *matching_measure_desc( gchar *, GList **);

static void free_food_desc( Food_Des_t **);
static void free_food_group( Food_Group_t **);
static void free_food_nutrs( Food_Nutr_t **);
static void free_msre_desc( Msre_Desc_t **);
static void free_nutr( Nutr_t **);
static void free_msre_weight( Msre_Weight_t **);

static GList* private_ptr_food_group_list = NULL;
static GList* private_ptr_msre_desc_list = NULL;
static GList* private_ptr_nutr_list = NULL;
static GList* private_ptr_msre_weight_list = NULL;

/* create glist of nutrient descriptions. */
static GList *
create_nutr_glist()
{
	FILE *strm;
	Nutr_t *elm;
	gchar line[300], *column;
	GList *ret_list = NULL;

	g_return_val_if_fail( ( 
		strm = fopen( GNUTRITION_GLADEDIR"/data/NUTR_DEF.TXT", "r")) != NULL,
		NULL); 

	while ( fgets( line, 300, strm) != NULL) 
	{
		elm = (Nutr_t *)g_malloc( sizeof( Nutr_t));

		column = extract_column( line, 1, 3);
		elm->nutr_no = g_strdup( column);
		
		column = extract_column( line, 4, 61);
		elm->nutr_desc = g_strdup( column);

		ret_list = g_list_prepend( ret_list, ( gpointer)elm);
	}
	fclose( strm);

	ret_list = g_list_reverse( ret_list);

	return ret_list;
}

/* Create a list of the descriptions of each food in the data base. */
static GList * 
create_food_des_glist()
{
	gchar line[300], *column;
	FILE *strm;
	Food_Des_t *elm;
	GList *ret_list = NULL;

    g_return_val_if_fail( 
		( strm = fopen( GNUTRITION_GLADEDIR"/data/FOOD_DES.TXT", "r")) != NULL,
		NULL);

	while ( fgets( line, 300, strm) != NULL) 
	{
		elm = ( Food_Des_t *) g_malloc( sizeof( Food_Des_t));

		column = extract_column( line, 1, 5);
		elm->fd_no = g_strdup( column);

		column = extract_column( line, 2, 4);
		elm->gp_no = g_strdup( column);

		column = extract_column( line, 3, 200);
		elm->fd_desc = g_strdup( column); 

		ret_list = g_list_prepend( ret_list, ( gpointer)elm);
	} 
	fclose( strm);

	ret_list = g_list_reverse( ret_list);

	return ret_list;
}

/* Create a list of the food group descriptions in the database. */
GList * 
gnutr_create_food_group_glist()
{
	gchar line[300], *column;
	FILE *strm;
	Food_Group_t *elm;
	GList *ret_list = NULL;

    g_return_val_if_fail( 
		( strm = fopen( GNUTRITION_GLADEDIR"/data/FD_GROUP.TXT", "r")) != NULL,
		NULL);

	elm = ( Food_Group_t *)g_malloc( sizeof( Food_Group_t));
		
	/* First add an extra food group not present in the food data. */
	elm->gp_no = g_strdup( "0001");
	elm->gp_desc = g_strdup( "All Foods");

	ret_list = g_list_prepend( ret_list, ( gpointer)elm);

	while ( fgets( line, 300, strm) != NULL) 
	{
		elm = ( Food_Group_t *)g_malloc( sizeof( Food_Group_t));
		
		column = extract_column( line, 1, 5);
		elm->gp_no = g_strdup( column);

		column = extract_column( line, 2, 60);
		elm->gp_desc = g_strdup( column);

		ret_list = g_list_prepend( ret_list, ( gpointer)elm);
	}	 
	fclose( strm);

	return ret_list;
}

/* Create a list of nutrient values for each food in the database. */
static GList *
create_food_nutr_glist( GList **nutr_list)
{
	FILE *strm;
	gchar line[300], *nutrient_no, *nutrient_value;
	gchar food_no[6], *food_no_next;
	Food_Nutr_t *elm;
	gint nutr_no;
	GList *nutr_ptr;
	GList *ret_list = NULL;

    g_return_val_if_fail( 
		( strm = fopen( GNUTRITION_GLADEDIR"/data/NUT_DATA.TXT", "r")) != NULL,
		NULL);

	/* initalise. */
	nutr_ptr = g_list_first( *nutr_list);
	nutr_no = 0;
	strcpy( food_no, "01001\0");	

	elm = ( Food_Nutr_t *) g_malloc( sizeof( Food_Nutr_t));
	elm->fd_no = g_strdup( food_no);

	/* read the data file. */
	while ( fgets( line, 300, strm) != NULL) 
	{
		food_no_next = g_strdup( extract_column( line, 1, 5));
		nutrient_no = g_strdup( extract_column( line, 2, 3));
		nutrient_value = g_strdup( extract_column( line, 3, 60));

		/* In this case we've moved onto a new food. */
		if ( strcmp( food_no_next, food_no) != 0) 
		{
			/* set final remaining undefined nutrients to zero. */
			while ( nutr_no < NO_NUTR )
			{
				elm->value[ nutr_no++ ] = g_strdup( "0.0");
			}

			/* Append old food nutrient elm to glist. */
			ret_list = g_list_prepend( ret_list, ( gpointer)elm);

			/* reset. */
			nutr_ptr = g_list_first( *nutr_list);
			strcpy( food_no, food_no_next);
			nutr_no = 0;

			/* allocate a new food nutrient elm. */
			elm = ( Food_Nutr_t *)g_malloc( sizeof( Food_Nutr_t));
			elm->fd_no = g_strdup( food_no);
		}

		/* set any missing nutrients for the food to
		 * to zero. There could be several consequetive ones. */
		while ( strcmp( nutrient_no, 
					((Nutr_t *)nutr_ptr->data)->nutr_no) != 0 ) 
		{
			elm->value[ nutr_no++ ] = g_strdup( "0.0");

			if ( nutr_no == NO_NUTR  ) break;

			nutr_ptr = g_list_next( nutr_ptr);
		}

		/* The nutrient is in that database, assign its value. */
		if ( strcmp( nutrient_no, 
					(( Nutr_t *)nutr_ptr->data)->nutr_no) == 0 ) 
		{
			elm->value[ nutr_no++ ] = g_strdup( nutrient_value);

			if ( nutr_no == NO_NUTR  ) continue;

			nutr_ptr = g_list_next( nutr_ptr);
		}

		g_free( food_no_next);
		g_free( nutrient_no);
		g_free( nutrient_value);
	}
	fclose( strm);

	/* Append final food nutrient elm to glist. */
	ret_list = g_list_prepend( ret_list, ( gpointer)elm);

	ret_list = g_list_reverse( ret_list);

	return ret_list;
}

/* create a list of descriptions of each of the food measures. */
static GList*
create_msre_desc_glist()
{
	FILE *strm;
	gchar line[301], *column;
	Msre_Desc_t *elm;
	GList *list_ret = NULL;

    g_return_val_if_fail( 
		( strm = fopen( GNUTRITION_GLADEDIR"/data/MEASURE.TXT", "r")) != NULL,
		NULL);

	elm = ( Msre_Desc_t *)g_malloc( sizeof( Msre_Desc_t));

	/* First add and extra measure no present in the food data. */
	elm->msre_no = g_strdup( "0");
	elm->msre_desc = g_strdup( "1 gm");

	list_ret = g_list_prepend( list_ret, (gpointer)elm);

	while( fgets( line, 300, strm) != NULL) 
	{
		elm = ( Msre_Desc_t *)g_malloc( sizeof( Msre_Desc_t));

		column = extract_column( line, 1, 6);
		elm->msre_no = g_strdup( column);

		column = extract_column( line, 2, 61);
		elm->msre_desc = g_strdup( column);

		list_ret = g_list_prepend( list_ret, (gpointer)elm);
	}
	fclose( strm);

	list_ret = g_list_reverse( list_ret);

	return list_ret;
}

static GList*
create_msre_weight_glist()
{
	FILE *strm;
	gchar line[301], *column;
	Msre_Weight_t *elm;
	GList *list_ret = NULL;

    g_return_val_if_fail( 
		( strm = fopen( GNUTRITION_GLADEDIR"/data/WEIGHT.TXT", "r")) != NULL,
		NULL);

	while( fgets( line, 300, strm) != NULL) 
	{
		elm = ( Msre_Weight_t *)g_malloc( sizeof( Msre_Weight_t));

		column = extract_column( line, 1, 6);
		elm->fd_no = g_strdup( column);

		column = extract_column( line, 2, 6);
		elm->msre_no = g_strdup( column);

		column = extract_column( line, 3, 30);
		elm->msre_weight = g_strdup( column);

		list_ret = g_list_prepend( list_ret, (gpointer)elm);
	}
	fclose( strm);

	list_ret = g_list_reverse( list_ret);

	return list_ret;
}

/* return the measure description with the matching msre_no */
static Msre_Desc_t *
matching_measure_desc( gchar *msre_no, GList **msre_desc_ptr)
{
	Msre_Desc_t *elm;

	while( *msre_desc_ptr)
	{
		elm = ( Msre_Desc_t *)( ( *msre_desc_ptr)->data);

		if ( strcmp( elm->msre_no, msre_no) == 0) 
		{
			return elm;
		}
		*msre_desc_ptr = g_list_next( *msre_desc_ptr);
	}
	g_assert_not_reached();

	return NULL;
}


/* create an array of measures for a specific food, return the number of
 * measures in the parameter list. */
static Food_Msre_t **
create_food_measure_array( gchar *selected_fd_no, gint *no_msre,
                           GList **msre_desc_list, GList **msre_weight_ptr)
{
	GList *msre_desc_list_ptr;
	Msre_Weight_t *elm;
	Msre_Desc_t *measure_desc;
	Food_Msre_t **ret;

	ret = ( Food_Msre_t **)g_malloc( 17 * sizeof( Food_Msre_t *));

	*no_msre = 0;
	ret[ *no_msre ] = ( Food_Msre_t *)g_malloc( sizeof( Food_Msre_t));

	msre_desc_list_ptr = g_list_first( *msre_desc_list);

	/* All foods have a measure that is one gram weight. */
	measure_desc = ( Msre_Desc_t *)msre_desc_list_ptr->data;
	ret[ *no_msre ]->msre_no = measure_desc->msre_no;
	ret[ *no_msre ]->msre_desc = measure_desc->msre_desc;
	ret[ *no_msre ]->msre_weight = g_strdup( "1.0");
	++( *no_msre);

	/* Nearly all foods have at least one other measure. */
	if ( *msre_weight_ptr)
	{
		elm = ( Msre_Weight_t *)(( *msre_weight_ptr)->data);

		while ( strcmp( elm->fd_no, selected_fd_no) == 0) 
		{
			ret[ *no_msre] = ( Food_Msre_t *)g_malloc( sizeof( Food_Msre_t));

			measure_desc = matching_measure_desc( elm->msre_no, 
					&msre_desc_list_ptr);

			ret[ *no_msre]->msre_no = measure_desc->msre_no;
			ret[ *no_msre]->msre_desc = measure_desc->msre_desc;
			ret[ *no_msre]->msre_weight = elm->msre_weight;
			++( *no_msre);

			*msre_weight_ptr = g_list_next( *msre_weight_ptr);

			if ( ( *no_msre == 17) || ( *msre_weight_ptr == NULL) ) break;

			elm = ( Msre_Weight_t *)(( *msre_weight_ptr)->data);
		}
	}

	/* Q: Does it matter that 17 pointers are allocated, but only
	 * a portion of them are typically used? */

	return ret;
}

/* create a list of foods. */
GList *
gnutr_create_food_glist()
{
	Food_t *food;
	GList *des_ptr, *group_ptr, *nutrs_ptr, *msre_weight_ptr;
	GList *nutr_list = create_nutr_glist();
	GList *food_desc_list = create_food_des_glist();
	GList *food_group_list = gnutr_create_food_group_glist();
	GList *msre_desc_list = create_msre_desc_glist();
	GList *msre_weight_list = create_msre_weight_glist();
	GList *food_nutr_list;
	GList *ret_food_list = NULL;

	private_ptr_food_group_list = food_group_list;
	private_ptr_msre_desc_list = msre_desc_list;
	private_ptr_nutr_list = nutr_list;
	private_ptr_msre_weight_list = msre_weight_list;

	food_nutr_list = create_food_nutr_glist(&nutr_list);
	
	g_return_val_if_fail( nutr_list != NULL, NULL);
	g_return_val_if_fail( food_desc_list != NULL, NULL);
	g_return_val_if_fail( food_group_list != NULL, NULL);
	g_return_val_if_fail( food_nutr_list != NULL, NULL);
	g_return_val_if_fail( msre_desc_list != NULL, NULL);
	g_return_val_if_fail( msre_weight_list != NULL, NULL);


	des_ptr = g_list_first( food_desc_list);

	nutrs_ptr = g_list_first( food_nutr_list);

	msre_weight_ptr = g_list_first( msre_weight_list);

	while ( des_ptr)
	{
		food = ( Food_t *)g_malloc( sizeof( Food_t));

		/* Food description. */

		food->desc = ( Food_Des_t *)des_ptr->data;

		/* Food group. */
		food->group = NULL;
		group_ptr = g_list_first( food_group_list);
		while ( group_ptr)
		{
			if ( strcmp( food->desc->gp_no, 
						((Food_Group_t *)group_ptr->data)->gp_no) == 0)
			{
				food->group = ( Food_Group_t *)group_ptr->data;
				break;
			}
			group_ptr = g_list_next( group_ptr);
		}
		g_assert( food->group != NULL);

		/* Food nutrients these sould be in the same order as the
		 * food descriptions. */
		food->nutrs = NULL;
		while ( nutrs_ptr)
		{
			if ( strcmp( food->desc->fd_no, 
						((Food_Nutr_t *)nutrs_ptr->data)->fd_no) == 0)
			{
				food->nutrs = ( Food_Nutr_t *)nutrs_ptr->data;
				break;
			}
			nutrs_ptr = g_list_next( nutrs_ptr);
		}
		g_assert( food->nutrs != NULL);

		food->sel_msre = 0;

		/* Food weight measures should also be in the same order as the
		 * food descriptions. */
		food->msre = create_food_measure_array( food->desc->fd_no, 
					&food->no_msre, &msre_desc_list, &msre_weight_ptr);

		/* Add food element to the list. */
		ret_food_list = g_list_prepend( ret_food_list, ( gpointer)food);

		des_ptr = g_list_next( des_ptr);
	}
	return ret_food_list;
}

static void
free_msre_weight( Msre_Weight_t **msre_weight)
{
	g_free( (*msre_weight)->fd_no);
	g_free( (*msre_weight)->msre_no);
	g_free( (*msre_weight)->msre_weight);
	g_free( *msre_weight);
}

static void
free_msre_desc( Msre_Desc_t **desc)
{
	g_free( (*desc)->msre_no);
	g_free( (*desc)->msre_desc);
	g_free( *desc);
}

static void 
free_nutr( Nutr_t **nutr)
{
	g_free( (*nutr)->nutr_no);
	g_free( (*nutr)->nutr_desc);
	g_free( *nutr);
}

static void 
free_food_desc( Food_Des_t **desc)
{
	g_free( (*desc)->fd_no);
	g_free( (*desc)->gp_no);
	g_free( (*desc)->fd_desc);
	g_free( *desc);
}

static void
free_food_group( Food_Group_t **group)
{
	g_free( (*group)->gp_no);
	g_free( (*group)->gp_desc);
	g_free( *group);
}

static void
free_food_nutrs( Food_Nutr_t **nutrs)
{
	gint i;

	g_free( (*nutrs)->fd_no);
	for( i=0; i<NO_NUTR; i++) g_free( (*nutrs)->value[i]);
	g_free( *nutrs);
}

/* Free the glist of foods. */
void
gnutr_free_food_glist( GList **food_list)
{
	gint i;
	GList *list_ptr;
	Food_t *food;
	Food_Group_t *food_group = NULL;
	Msre_Desc_t *msre_desc = NULL;
	Nutr_t *nutr = NULL;
	Msre_Weight_t *msre_weight = NULL;

	/* Free food groups. */
	list_ptr = g_list_first( private_ptr_food_group_list);
	while( list_ptr)
	{
		food_group = ( Food_Group_t *)list_ptr->data;
		free_food_group( &food_group);
		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( private_ptr_food_group_list);

	/* Free measure descriptions. */
	list_ptr = g_list_first( private_ptr_msre_desc_list);
	while( list_ptr)
	{
		msre_desc = ( Msre_Desc_t *)list_ptr->data;
		free_msre_desc( &msre_desc);
		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( private_ptr_msre_desc_list);

	/* Free nutrients. */
	list_ptr = g_list_first( private_ptr_nutr_list);
	while( list_ptr)
	{
		nutr = ( Nutr_t *)list_ptr->data;
		free_nutr( &nutr);
		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( private_ptr_nutr_list);

	/* Free weight measures. */
	list_ptr = g_list_first( private_ptr_msre_weight_list);
	while( list_ptr)
	{
		msre_weight = ( Msre_Weight_t *)list_ptr->data;
		free_msre_weight( &msre_weight);
		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( private_ptr_msre_weight_list);

	/* Free the food list itself. */
	list_ptr = g_list_first( *food_list);
	while( list_ptr)
	{
		food = ( Food_t *)list_ptr->data;

		free_food_desc( &(food->desc));
		free_food_nutrs( &(food->nutrs));
		for ( i=0; i<food->no_msre; i++) g_free( food->msre[i]);
		g_free( food->msre);
		g_free( food); 

		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( *food_list);
}

/* Free the glist of foods groups. */
void
gnutr_free_food_group_glist( GList **food_group_list)
{
	GList *list_ptr;
	Food_Group_t *food_group = NULL;

	list_ptr = g_list_first( *food_group_list);
	while( list_ptr)
	{
		food_group = ( Food_Group_t *)list_ptr->data;
		free_food_group( &food_group);

		list_ptr = g_list_next( list_ptr);
	}
	g_list_free( *food_group_list);
}

/* Find the food group number from the food group description. */
gchar * 
gnutr_find_food_group_number( gchar *fd_gp_desc)
{
	GList *list_ptr;
	GList *food_group_glist = gnutr_create_food_group_glist();
	Food_Group_t *elm;
	gchar *ret;

	list_ptr = g_list_first( food_group_glist);

	while ( list_ptr) 
	{
		elm = ( Food_Group_t *)list_ptr->data;

		if ( strcmp( fd_gp_desc, elm->gp_desc) == 0)
		{
			ret = g_strdup( elm->gp_no);
			gnutr_free_food_group_glist( &food_group_glist);
			return ret;
		}
		list_ptr = g_list_next( list_ptr);
	}
	g_assert_not_reached();
	return NULL;
}
